@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,675 @@
1
+ import { create } from "zustand"
2
+ import { getLayerDefinition } from "@/lib/editor/config/layer-registry"
3
+ import {
4
+ clampLayerAdjustments,
5
+ cloneLayer,
6
+ createLayer,
7
+ resetLayerParameters,
8
+ } from "@/lib/editor/layers"
9
+ import {
10
+ cloneParameterValue,
11
+ getParameterDefinition,
12
+ } from "@/lib/editor/parameter-schema"
13
+ import { useEditorStore } from "@/store/editor-store"
14
+ import type {
15
+ BlendMode,
16
+ EditorLayer,
17
+ LayerCompositeMode,
18
+ LayerType,
19
+ ParameterValue,
20
+ } from "@/types/editor"
21
+
22
+ export interface LayerStoreState {
23
+ hoveredLayerId: string | null
24
+ layers: EditorLayer[]
25
+ selectedLayerId: string | null
26
+ }
27
+
28
+ export interface LayerStoreActions {
29
+ addLayer: (type: LayerType, insertIndex?: number) => string
30
+ duplicateLayer: (id: string) => string | null
31
+ getLayerById: (id: string) => EditorLayer | null
32
+ getRenderableLayers: () => EditorLayer[]
33
+ getSelectedLayer: () => EditorLayer | null
34
+ removeLayer: (id: string) => void
35
+ renameLayer: (id: string, name: string) => void
36
+ replaceState: (
37
+ layers: EditorLayer[],
38
+ selectedLayerId?: string | null,
39
+ hoveredLayerId?: string | null
40
+ ) => void
41
+ reorderLayers: (fromIndex: number, toIndex: number) => void
42
+ resetLayerParams: (id: string) => void
43
+ selectLayer: (id: string | null) => void
44
+ setHoveredLayer: (id: string | null) => void
45
+ setLayerAsset: (id: string, assetId: string | null) => void
46
+ setLayerBlendMode: (id: string, blendMode: BlendMode) => void
47
+ setLayerCompositeMode: (id: string, compositeMode: LayerCompositeMode) => void
48
+ setLayerExpanded: (id: string, expanded: boolean) => void
49
+ setLayerHue: (id: string, hue: number) => void
50
+ setLayerLocked: (id: string, locked: boolean) => void
51
+ setLayerOpacity: (id: string, opacity: number) => void
52
+ setLayerRuntimeError: (id: string, error: string | null) => void
53
+ setLayerSaturation: (id: string, saturation: number) => void
54
+ setLayerVisibility: (id: string, visible: boolean) => void
55
+ updateLayerParam: (id: string, key: string, value: ParameterValue) => void
56
+ }
57
+
58
+ export type LayerStore = LayerStoreState & LayerStoreActions
59
+
60
+ function getGradientNoiseDefaults(noiseType: string): {
61
+ warpAmount: number
62
+ warpScale: number
63
+ } | null {
64
+ switch (noiseType) {
65
+ case "perlin":
66
+ return {
67
+ warpAmount: 0.64,
68
+ warpScale: 5.56,
69
+ }
70
+ case "value":
71
+ return {
72
+ warpAmount: 0.06,
73
+ warpScale: 0.35,
74
+ }
75
+ case "voronoi":
76
+ return {
77
+ warpAmount: 0.3,
78
+ warpScale: 3.0,
79
+ }
80
+ case "ridge":
81
+ return {
82
+ warpAmount: 0.2,
83
+ warpScale: 2.0,
84
+ }
85
+ case "turbulence":
86
+ return {
87
+ warpAmount: 0.04,
88
+ warpScale: 0.28,
89
+ }
90
+ case "simplex":
91
+ return {
92
+ warpAmount: 0.64,
93
+ warpScale: 5.56,
94
+ }
95
+ default:
96
+ return null
97
+ }
98
+ }
99
+
100
+ function getGradientPresetDefaults(
101
+ preset: string
102
+ ): Record<string, ParameterValue> | null {
103
+ switch (preset) {
104
+ case "aurora":
105
+ return {
106
+ activePoints: 5,
107
+ point1Color: "#ed6a5a",
108
+ point1Position: [-0.8, -0.6],
109
+ point1Weight: 1.0,
110
+ point2Color: "#f4f1bb",
111
+ point2Position: [0.2, 0.7],
112
+ point2Weight: 1.0,
113
+ point3Color: "#9bc1bc",
114
+ point3Position: [0.9, -0.3],
115
+ point3Weight: 1.0,
116
+ point4Color: "#5d576b",
117
+ point4Position: [-0.4, 0.5],
118
+ point4Weight: 1.0,
119
+ point5Color: "#e6ebe0",
120
+ point5Position: [0.6, -0.8],
121
+ point5Weight: 1.0,
122
+ noiseType: "simplex",
123
+ warpAmount: 0.8,
124
+ warpScale: 4.0,
125
+ warpIterations: 3,
126
+ warpDecay: 1.0,
127
+ warpBias: 0.65,
128
+ vortexAmount: 0.3,
129
+ falloff: 3.5,
130
+ tonemapMode: "totos",
131
+ glowStrength: 0.0,
132
+ glowThreshold: 0.0,
133
+ grainAmount: 0.08,
134
+ vignetteStrength: 0.0,
135
+ vignetteRadius: 1.5,
136
+ vignetteSoftness: 1,
137
+ }
138
+ case "sunset":
139
+ return {
140
+ activePoints: 4,
141
+ point1Color: "#1a0a2e",
142
+ point1Position: [-0.6, -0.8],
143
+ point1Weight: 0.8,
144
+ point2Color: "#c4420a",
145
+ point2Position: [0.3, 0.4],
146
+ point2Weight: 1.2,
147
+ point3Color: "#e8821a",
148
+ point3Position: [0.8, 0.7],
149
+ point3Weight: 0.9,
150
+ point4Color: "#4a1942",
151
+ point4Position: [-0.5, 0.3],
152
+ point4Weight: 1.0,
153
+ noiseType: "simplex",
154
+ warpAmount: 0.6,
155
+ warpScale: 3.5,
156
+ warpIterations: 2,
157
+ warpDecay: 1.2,
158
+ warpBias: 0.5,
159
+ vortexAmount: 0.0,
160
+ falloff: 3.5,
161
+ tonemapMode: "totos",
162
+ glowStrength: 0.0,
163
+ glowThreshold: 0.0,
164
+ grainAmount: 0.08,
165
+ vignetteStrength: 0.15,
166
+ vignetteRadius: 1.4,
167
+ vignetteSoftness: 0.8,
168
+ }
169
+ case "deep-ocean":
170
+ return {
171
+ activePoints: 4,
172
+ point1Color: "#020b1a",
173
+ point1Position: [0.0, -0.7],
174
+ point1Weight: 0.8,
175
+ point2Color: "#0a3d62",
176
+ point2Position: [-0.6, 0.4],
177
+ point2Weight: 1.2,
178
+ point3Color: "#3c8dbc",
179
+ point3Position: [0.7, 0.1],
180
+ point3Weight: 0.9,
181
+ point4Color: "#061224",
182
+ point4Position: [0.3, 0.8],
183
+ point4Weight: 1.0,
184
+ noiseType: "turbulence",
185
+ warpAmount: 0.04,
186
+ warpScale: 0.28,
187
+ warpIterations: 3,
188
+ warpDecay: 0.8,
189
+ warpBias: 0.4,
190
+ vortexAmount: 0.35,
191
+ falloff: 3.5,
192
+ tonemapMode: "totos",
193
+ glowStrength: 0.0,
194
+ glowThreshold: 0.0,
195
+ grainAmount: 0.06,
196
+ vignetteStrength: 0.2,
197
+ vignetteRadius: 1.3,
198
+ vignetteSoftness: 0.7,
199
+ }
200
+ case "neon-glow":
201
+ return {
202
+ activePoints: 5,
203
+ point1Color: "#0a0a0a",
204
+ point1Position: [0.0, 0.0],
205
+ point1Weight: 0.6,
206
+ point2Color: "#b80050",
207
+ point2Position: [-0.7, -0.5],
208
+ point2Weight: 1.3,
209
+ point3Color: "#0088aa",
210
+ point3Position: [0.8, 0.3],
211
+ point3Weight: 1.1,
212
+ point4Color: "#220033",
213
+ point4Position: [0.2, -0.8],
214
+ point4Weight: 0.9,
215
+ point5Color: "#1a0a2e",
216
+ point5Position: [-0.5, 0.7],
217
+ point5Weight: 1.0,
218
+ noiseType: "simplex",
219
+ warpAmount: 0.7,
220
+ warpScale: 4.0,
221
+ warpIterations: 3,
222
+ warpDecay: 1.0,
223
+ warpBias: 0.35,
224
+ vortexAmount: -0.25,
225
+ falloff: 3.5,
226
+ tonemapMode: "totos",
227
+ glowStrength: 0.0,
228
+ glowThreshold: 0.0,
229
+ grainAmount: 0.05,
230
+ vignetteStrength: 0.1,
231
+ vignetteRadius: 1.5,
232
+ vignetteSoftness: 1,
233
+ }
234
+ default:
235
+ return null
236
+ }
237
+ }
238
+
239
+ function getDitheringPresetDefaults(
240
+ preset: string
241
+ ): Record<string, ParameterValue> | null {
242
+ switch (preset) {
243
+ case "gameboy":
244
+ return {
245
+ algorithm: "bayer-2x2",
246
+ colorMode: "duo-tone",
247
+ highlightColor: "#9bbc0f",
248
+ levels: 4,
249
+ pixelSize: 3,
250
+ shadowColor: "#0f380f",
251
+ spread: 0.5,
252
+ }
253
+ default:
254
+ return null
255
+ }
256
+ }
257
+
258
+ function getHalftonePresetDefaults(
259
+ preset: string
260
+ ): Record<string, ParameterValue> | null {
261
+ switch (preset) {
262
+ case "process":
263
+ return {
264
+ inkCyan: "#00AEEF",
265
+ inkMagenta: "#EC008C",
266
+ inkYellow: "#FFF200",
267
+ inkKey: "#1a1a1a",
268
+ paperColor: "#F5F5F0",
269
+ }
270
+ case "risograph":
271
+ return {
272
+ inkCyan: "#0078BF",
273
+ inkMagenta: "#FF48B0",
274
+ inkYellow: "#FFE800",
275
+ inkKey: "#000000",
276
+ paperColor: "#F2F0E6",
277
+ }
278
+ case "newspaper":
279
+ return {
280
+ inkCyan: "#1A6B8A",
281
+ inkMagenta: "#8C3A5E",
282
+ inkYellow: "#C4A832",
283
+ inkKey: "#2B2B2B",
284
+ paperColor: "#F0E6D0",
285
+ }
286
+ case "vintage":
287
+ return {
288
+ inkCyan: "#3A7CA5",
289
+ inkMagenta: "#A0506A",
290
+ inkYellow: "#D4A843",
291
+ inkKey: "#3C3228",
292
+ paperColor: "#EDE4D4",
293
+ }
294
+ default:
295
+ return null
296
+ }
297
+ }
298
+
299
+ export function cloneLayerList(layers: EditorLayer[]): EditorLayer[] {
300
+ return layers.map((layer) => ({
301
+ ...layer,
302
+ params: { ...layer.params },
303
+ }))
304
+ }
305
+
306
+ function countLayersOfType(layers: EditorLayer[], type: LayerType): number {
307
+ return layers.filter((layer) => layer.type === type).length
308
+ }
309
+
310
+ function getNeighborSelection(
311
+ layers: EditorLayer[],
312
+ removedIndex: number
313
+ ): string | null {
314
+ const nextIndex = Math.min(removedIndex, layers.length - 1)
315
+ const nextLayer = layers[nextIndex]
316
+
317
+ return nextLayer?.id ?? null
318
+ }
319
+
320
+ export const useLayerStore = create<LayerStore>((set, get) => ({
321
+ hoveredLayerId: null,
322
+ layers: [],
323
+ selectedLayerId: null,
324
+
325
+ addLayer: (type, insertIndex) => {
326
+ const existingLayers = get().layers
327
+ const nextLayer = createLayer(type, countLayersOfType(existingLayers, type))
328
+
329
+ set((state) => {
330
+ const layers = [...state.layers]
331
+
332
+ if (
333
+ insertIndex === undefined ||
334
+ insertIndex < 0 ||
335
+ insertIndex > layers.length
336
+ ) {
337
+ layers.unshift(nextLayer)
338
+ } else {
339
+ layers.splice(insertIndex, 0, nextLayer)
340
+ }
341
+
342
+ return {
343
+ layers,
344
+ selectedLayerId: nextLayer.id,
345
+ }
346
+ })
347
+
348
+ useEditorStore.getState().dismissStartupPreview()
349
+
350
+ return nextLayer.id
351
+ },
352
+
353
+ removeLayer: (id) => {
354
+ set((state) => {
355
+ const removedIndex = state.layers.findIndex((layer) => layer.id === id)
356
+
357
+ if (removedIndex === -1) {
358
+ return state
359
+ }
360
+
361
+ const layers = state.layers.filter((layer) => layer.id !== id)
362
+
363
+ return {
364
+ hoveredLayerId:
365
+ state.hoveredLayerId === id ? null : state.hoveredLayerId,
366
+ layers,
367
+ selectedLayerId:
368
+ state.selectedLayerId === id
369
+ ? getNeighborSelection(layers, removedIndex)
370
+ : state.selectedLayerId,
371
+ }
372
+ })
373
+ },
374
+
375
+ duplicateLayer: (id) => {
376
+ const sourceLayer = get().layers.find((layer) => layer.id === id)
377
+
378
+ if (!sourceLayer) {
379
+ return null
380
+ }
381
+
382
+ const duplicatedLayer = cloneLayer(sourceLayer)
383
+
384
+ set((state) => {
385
+ const sourceIndex = state.layers.findIndex((layer) => layer.id === id)
386
+ const layers = [...state.layers]
387
+
388
+ layers.splice(sourceIndex + 1, 0, duplicatedLayer)
389
+
390
+ return {
391
+ layers,
392
+ selectedLayerId: duplicatedLayer.id,
393
+ }
394
+ })
395
+
396
+ return duplicatedLayer.id
397
+ },
398
+
399
+ reorderLayers: (fromIndex, toIndex) => {
400
+ set((state) => {
401
+ if (
402
+ fromIndex < 0 ||
403
+ toIndex < 0 ||
404
+ fromIndex >= state.layers.length ||
405
+ toIndex >= state.layers.length ||
406
+ fromIndex === toIndex
407
+ ) {
408
+ return state
409
+ }
410
+
411
+ const layers = [...state.layers]
412
+ const [movedLayer] = layers.splice(fromIndex, 1)
413
+
414
+ if (!movedLayer) {
415
+ return state
416
+ }
417
+
418
+ layers.splice(toIndex, 0, movedLayer)
419
+
420
+ return { layers }
421
+ })
422
+ },
423
+
424
+ selectLayer: (selectedLayerId) => {
425
+ set({ selectedLayerId })
426
+ },
427
+
428
+ setHoveredLayer: (hoveredLayerId) => {
429
+ set({ hoveredLayerId })
430
+ },
431
+
432
+ renameLayer: (id, name) => {
433
+ const nextName = name.trim()
434
+
435
+ if (!nextName) {
436
+ return
437
+ }
438
+
439
+ set((state) => ({
440
+ layers: state.layers.map((layer) =>
441
+ layer.id === id ? { ...layer, name: nextName } : layer
442
+ ),
443
+ }))
444
+ },
445
+
446
+ setLayerVisibility: (id, visible) => {
447
+ set((state) => ({
448
+ layers: state.layers.map((layer) =>
449
+ layer.id === id ? { ...layer, visible } : layer
450
+ ),
451
+ }))
452
+ },
453
+
454
+ setLayerLocked: (id, locked) => {
455
+ set((state) => ({
456
+ layers: state.layers.map((layer) =>
457
+ layer.id === id ? { ...layer, locked } : layer
458
+ ),
459
+ }))
460
+ },
461
+
462
+ setLayerExpanded: (id, expanded) => {
463
+ set((state) => ({
464
+ layers: state.layers.map((layer) =>
465
+ layer.id === id ? { ...layer, expanded } : layer
466
+ ),
467
+ }))
468
+ },
469
+
470
+ setLayerOpacity: (id, opacity) => {
471
+ set((state) => ({
472
+ layers: state.layers.map((layer) => {
473
+ if (layer.id !== id) {
474
+ return layer
475
+ }
476
+
477
+ return {
478
+ ...layer,
479
+ ...clampLayerAdjustments({
480
+ hue: layer.hue,
481
+ opacity,
482
+ saturation: layer.saturation,
483
+ }),
484
+ }
485
+ }),
486
+ }))
487
+ },
488
+
489
+ setLayerHue: (id, hue) => {
490
+ set((state) => ({
491
+ layers: state.layers.map((layer) => {
492
+ if (layer.id !== id) {
493
+ return layer
494
+ }
495
+
496
+ return {
497
+ ...layer,
498
+ ...clampLayerAdjustments({
499
+ hue,
500
+ opacity: layer.opacity,
501
+ saturation: layer.saturation,
502
+ }),
503
+ }
504
+ }),
505
+ }))
506
+ },
507
+
508
+ setLayerSaturation: (id, saturation) => {
509
+ set((state) => ({
510
+ layers: state.layers.map((layer) => {
511
+ if (layer.id !== id) {
512
+ return layer
513
+ }
514
+
515
+ return {
516
+ ...layer,
517
+ ...clampLayerAdjustments({
518
+ hue: layer.hue,
519
+ opacity: layer.opacity,
520
+ saturation,
521
+ }),
522
+ }
523
+ }),
524
+ }))
525
+ },
526
+
527
+ setLayerBlendMode: (id, blendMode) => {
528
+ set((state) => ({
529
+ layers: state.layers.map((layer) =>
530
+ layer.id === id ? { ...layer, blendMode } : layer
531
+ ),
532
+ }))
533
+ },
534
+
535
+ setLayerCompositeMode: (id, compositeMode) => {
536
+ set((state) => ({
537
+ layers: state.layers.map((layer) =>
538
+ layer.id === id ? { ...layer, compositeMode } : layer
539
+ ),
540
+ }))
541
+ },
542
+
543
+ setLayerAsset: (id, assetId) => {
544
+ set((state) => ({
545
+ layers: state.layers.map((layer) =>
546
+ layer.id === id ? { ...layer, assetId, runtimeError: null } : layer
547
+ ),
548
+ }))
549
+ },
550
+
551
+ updateLayerParam: (id, key, value) => {
552
+ set((state) => ({
553
+ layers: state.layers.map((layer) => {
554
+ if (layer.id !== id) {
555
+ return layer
556
+ }
557
+
558
+ const definition = getParameterDefinition(
559
+ getLayerDefinition(layer.type).params,
560
+ key
561
+ )
562
+
563
+ if (!definition) {
564
+ return layer
565
+ }
566
+
567
+ const nextParams = {
568
+ ...layer.params,
569
+ [key]: cloneParameterValue(value),
570
+ }
571
+
572
+ if (
573
+ layer.type === "gradient" &&
574
+ key === "noiseType" &&
575
+ typeof value === "string"
576
+ ) {
577
+ const defaults = getGradientNoiseDefaults(value)
578
+
579
+ if (defaults) {
580
+ nextParams.warpAmount = defaults.warpAmount
581
+ nextParams.warpScale = defaults.warpScale
582
+ }
583
+ }
584
+
585
+ if (
586
+ layer.type === "gradient" &&
587
+ key === "preset" &&
588
+ typeof value === "string"
589
+ ) {
590
+ const defaults = getGradientPresetDefaults(value)
591
+
592
+ if (defaults) {
593
+ Object.assign(nextParams, defaults)
594
+ }
595
+ }
596
+
597
+ if (
598
+ layer.type === "dithering" &&
599
+ key === "preset" &&
600
+ typeof value === "string"
601
+ ) {
602
+ const defaults = getDitheringPresetDefaults(value)
603
+
604
+ if (defaults) {
605
+ Object.assign(nextParams, defaults)
606
+ }
607
+ }
608
+
609
+ if (
610
+ layer.type === "halftone" &&
611
+ key === "preset" &&
612
+ typeof value === "string"
613
+ ) {
614
+ const defaults = getHalftonePresetDefaults(value)
615
+
616
+ if (defaults) {
617
+ Object.assign(nextParams, defaults)
618
+ }
619
+ }
620
+
621
+ return {
622
+ ...layer,
623
+ params: nextParams,
624
+ runtimeError: null,
625
+ }
626
+ }),
627
+ }))
628
+ },
629
+
630
+ resetLayerParams: (id) => {
631
+ set((state) => ({
632
+ layers: state.layers.map((layer) =>
633
+ layer.id === id
634
+ ? {
635
+ ...layer,
636
+ params: resetLayerParameters(layer.type),
637
+ runtimeError: null,
638
+ }
639
+ : layer
640
+ ),
641
+ }))
642
+ },
643
+
644
+ setLayerRuntimeError: (id, runtimeError) => {
645
+ set((state) => ({
646
+ layers: state.layers.map((layer) =>
647
+ layer.id === id ? { ...layer, runtimeError } : layer
648
+ ),
649
+ }))
650
+ },
651
+
652
+ replaceState: (layers, selectedLayerId = null, hoveredLayerId = null) => {
653
+ set({
654
+ hoveredLayerId,
655
+ layers: cloneLayerList(layers),
656
+ selectedLayerId,
657
+ })
658
+ },
659
+
660
+ getSelectedLayer: () => {
661
+ const state = get()
662
+
663
+ return (
664
+ state.layers.find((layer) => layer.id === state.selectedLayerId) ?? null
665
+ )
666
+ },
667
+
668
+ getLayerById: (id) => {
669
+ return get().layers.find((layer) => layer.id === id) ?? null
670
+ },
671
+
672
+ getRenderableLayers: () => {
673
+ return get().layers.filter((layer) => layer.visible)
674
+ },
675
+ }))