@mkbabb/glass-ui 3.6.0 → 3.8.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 (255) hide show
  1. package/dist/CardAction-q2qHCJFG.js +157 -0
  2. package/dist/CollapsibleContent-CP9qF1v4.js +63 -0
  3. package/dist/{CommandShortcut-BiVyqipe.js → CommandShortcut-SzWRhROC.js} +4 -4
  4. package/dist/{ContextMenuContent-De01_83g.js → ContextMenuContent-DDa0v27s.js} +1 -1
  5. package/dist/{ContextMenuSubContent-DrWkXKQP.js → ContextMenuSubContent-Do977Cbf.js} +4 -4
  6. package/dist/{DataTable-BGVXx0Ww.js → DataTable-CvlWXz65.js} +14 -7
  7. package/dist/DeckProgress-AVgbIULY.js +23 -0
  8. package/dist/{DialogContent-IQ8_BRrC.js → DialogContent-DjZWT3lK.js} +24 -24
  9. package/dist/{DialogFooter-Er0wA3K6.js → DialogFooter-Gprw8H_A.js} +2 -2
  10. package/dist/{DiscoGlyph-BaZ8OawK.js → DiscoGlyph-CG0F410s.js} +1 -1
  11. package/dist/{GlyphFace-B_7vOmYn.js → GlyphFace-DRjDkB0s.js} +1 -1
  12. package/dist/{HoverCardContent-ICfIMZX1.js → HoverCardContent-B6CcQ8He.js} +1 -1
  13. package/dist/{HoverPopover-DTSYkJtw.js → HoverPopover-D56O6Ylp.js} +1 -1
  14. package/dist/{IconTooltip-DZlCkU-0.js → IconTooltip-D2T5nYOM.js} +1 -1
  15. package/dist/{Input-CRJ2owjN.js → Input-DVG_J0ne.js} +4 -1
  16. package/dist/{Label-CZk-3nTc.js → Label-zRfbQdCn.js} +1 -1
  17. package/dist/{MetricBadge-BlrdbOGN.js → MetricBadge-lTc002u2.js} +1 -1
  18. package/dist/{Notification-DP_ApJLo.js → Notification-BxK8LZMw.js} +5 -4
  19. package/dist/{NumberFieldContent-Bhhmk95p.js → NumberFieldContent-CLNVR1iY.js} +5 -5
  20. package/dist/{PopoverContent-B8WtJECb.js → PopoverContent-BId6ggf_.js} +2 -2
  21. package/dist/{Progress-DjM86vfb.js → Progress-iEuD9pxG.js} +7 -7
  22. package/dist/{ScrollingText-P9o_DuMn.js → ScrollingText-CVyqxvfv.js} +2 -2
  23. package/dist/{SelectScrollDownButton-BGn3rjs9.js → SelectScrollDownButton-C7TRe-ep.js} +8 -7
  24. package/dist/{Separator-C2XtAXRp.js → Separator-D7sD7RKj.js} +1 -1
  25. package/dist/{Switch-x8n6husW.js → Switch-LDKLmmgx.js} +8 -2
  26. package/dist/TabsList-qyHZ2lKY.js +101 -0
  27. package/dist/{Toaster-DdhMKfus.js → Toaster-CabYEEF_.js} +54 -60
  28. package/dist/{ToggleGroupItem-gyXj998A.js → ToggleGroupItem-CkXUcBlt.js} +13 -5
  29. package/dist/{TooltipProvider-D-JrSqDu.js → TooltipProvider-CmZzCuaG.js} +1 -1
  30. package/dist/{UnderlineTabs-CuIK16JI.js → UnderlineTabs-Uxn-R3MT.js} +2 -2
  31. package/dist/animated-digit.js +2 -2
  32. package/dist/api/index.d.ts +4 -2
  33. package/dist/api.js +1 -1
  34. package/dist/aurora.js +753 -359
  35. package/dist/{badge-Cl6JZ1B7.js → badge-DHQrc_GQ.js} +13 -9
  36. package/dist/badge.js +1 -1
  37. package/dist/{button-BcMzVrqs.js → button-CAywcrhO.js} +6 -3
  38. package/dist/button.js +1 -1
  39. package/dist/canvas.d.ts +1 -0
  40. package/dist/canvas.js +3 -0
  41. package/dist/card.js +2 -2
  42. package/dist/carousel.js +110 -109
  43. package/dist/{check-CdkxGxXJ.js → check-BBKKKSTf.js} +1 -1
  44. package/dist/{chevron-down-pBY8sYfV.js → chevron-down-BSkTP2aL.js} +1 -1
  45. package/dist/{chevron-right-BjeKC22V.js → chevron-right-sDM0Yvrf.js} +1 -1
  46. package/dist/{chevron-up-DBeNHUm1.js → chevron-up-CCOD_FTz.js} +1 -1
  47. package/dist/collapsible.js +1 -1
  48. package/dist/color-DMLTunJK.js +84 -0
  49. package/dist/color.js +2 -2
  50. package/dist/command.js +1 -1
  51. package/dist/compile-BBTRjvQS.js +158 -0
  52. package/dist/components/custom/aurora/Aurora.vue.d.ts +1 -0
  53. package/dist/components/custom/aurora/composables/atoms.d.ts +125 -0
  54. package/dist/components/custom/aurora/composables/color.d.ts +7 -25
  55. package/dist/components/custom/aurora/composables/cursorModel.d.ts +35 -2
  56. package/dist/components/custom/aurora/composables/glSetup.d.ts +1 -1
  57. package/dist/components/custom/aurora/composables/gpuRuntime.d.ts +16 -0
  58. package/dist/components/custom/aurora/composables/runtime.d.ts +18 -0
  59. package/dist/components/custom/aurora/composables/uniformBridge.d.ts +46 -12
  60. package/dist/components/custom/aurora/composables/useAurora.d.ts +6 -0
  61. package/dist/components/custom/aurora/composables/useCursorInteraction.d.ts +8 -0
  62. package/dist/components/custom/aurora/constants/presets.d.ts +60 -4
  63. package/dist/components/custom/aurora/constants/renderMode.d.ts +32 -0
  64. package/dist/components/custom/aurora/constants/shaders/aurora.wgsl.d.ts +7 -0
  65. package/dist/components/custom/aurora/constants/shaders/brush.glsl.d.ts +1 -1
  66. package/dist/components/custom/aurora/constants/shaders/composition.glsl.d.ts +1 -1
  67. package/dist/components/custom/aurora/constants/shaders/flow.glsl.d.ts +1 -1
  68. package/dist/components/custom/aurora/constants/shaders/mediums.glsl.d.ts +2 -2
  69. package/dist/components/custom/aurora/constants/shaders/painterly.wgsl.d.ts +7 -0
  70. package/dist/components/custom/aurora/constants/shaders/wake.wgsl.d.ts +4 -0
  71. package/dist/components/custom/aurora/index.d.ts +4 -3
  72. package/dist/components/custom/constellation/Constellation.vue.d.ts +40 -5
  73. package/dist/components/custom/constellation/constellationField.d.ts +110 -5
  74. package/dist/components/custom/constellation/index.d.ts +2 -2
  75. package/dist/components/custom/dock/GlassDock.vue.d.ts +12 -4
  76. package/dist/components/custom/dock/composables/dockMorphContext.d.ts +57 -0
  77. package/dist/components/custom/dock/composables/index.d.ts +3 -0
  78. package/dist/components/custom/dock/composables/useDockHold.d.ts +61 -0
  79. package/dist/components/custom/dock/composables/useLayerTransition.d.ts +13 -59
  80. package/dist/components/custom/dock/index.d.ts +2 -0
  81. package/dist/components/custom/fourier-field/FourierField.vue.d.ts +38 -0
  82. package/dist/components/custom/fourier-field/index.d.ts +2 -0
  83. package/dist/components/custom/fourier-field/math.d.ts +57 -0
  84. package/dist/components/custom/goo-blob/GooBlob.vue.d.ts +15 -0
  85. package/dist/components/custom/goo-blob/composables/useBlobMood.d.ts +18 -2
  86. package/dist/components/custom/goo-blob/composables/useBlobPointer.d.ts +47 -4
  87. package/dist/components/custom/goo-blob/composables/useBlobSatellites.d.ts +2 -0
  88. package/dist/components/custom/goo-blob/composables/useMetaballRenderer.d.ts +19 -4
  89. package/dist/components/custom/goo-blob/index.d.ts +1 -1
  90. package/dist/components/custom/goo-blob/shaders/sdf-body.glsl.d.ts +1 -1
  91. package/dist/components/custom/goo-blob/shaders/watercolor-edges.glsl.d.ts +2 -2
  92. package/dist/components/custom/goo-blob/types.d.ts +48 -0
  93. package/dist/components/custom/metric-cell/MetricCell.vue.d.ts +1 -1
  94. package/dist/components/custom/metric-stack/MetricStack.vue.d.ts +1 -1
  95. package/dist/components/ui/_shared/ModalOverlay.vue.d.ts +1 -1
  96. package/dist/components/ui/alert/index.d.ts +1 -1
  97. package/dist/components/ui/card/Card.vue.d.ts +26 -3
  98. package/dist/components/ui/card/CardAction.vue.d.ts +24 -0
  99. package/dist/components/ui/card/index.d.ts +2 -1
  100. package/dist/components/ui/collapsible/CollapsibleTrigger.vue.d.ts +5 -1
  101. package/dist/components/ui/progress/ProgressDefault.vue.d.ts +14 -4
  102. package/dist/components/ui/slider/Slider.vue.d.ts +3 -1
  103. package/dist/components/ui/tabs/TabsList.vue.d.ts +13 -3
  104. package/dist/components/ui/toast/Toast.vue.d.ts +5 -5
  105. package/dist/components/ui/toast/use-toast.d.ts +1 -1
  106. package/dist/composables/color/index.d.ts +69 -0
  107. package/dist/composables/dom/index.d.ts +1 -1
  108. package/dist/composables/dom/useResolveTokenColor.d.ts +27 -0
  109. package/dist/composables/glass/canvas2d/index.d.ts +2 -1
  110. package/dist/composables/glass/canvas2d/resolveCanvasColor.d.ts +20 -0
  111. package/dist/composables/glass/canvas2d/useCanvas2D.d.ts +15 -1
  112. package/dist/composables/glass/createGPUCanvas.d.ts +40 -0
  113. package/dist/composables/glass/index.d.ts +3 -1
  114. package/dist/composables/glass/useSpecularTracking.d.ts +33 -0
  115. package/dist/composables/glass/webgl/createCanvasLifecycle.d.ts +45 -0
  116. package/dist/composables/glass/webgl/shaders/procedural-color.glsl.d.ts +7 -0
  117. package/dist/composables/glass/webgl/useWebGLCanvas.d.ts +5 -5
  118. package/dist/composables/motion/core/index.d.ts +1 -0
  119. package/dist/configurator.js +1 -1
  120. package/dist/confirm-dialog.js +3 -3
  121. package/dist/constellation.js +144 -75
  122. package/dist/context-menu.js +2 -2
  123. package/dist/controls.js +2 -2
  124. package/dist/dark.js +1 -1
  125. package/dist/data-table.js +1 -1
  126. package/dist/deck-progress.d.ts +1 -0
  127. package/dist/deck-progress.js +2 -0
  128. package/dist/dialog.js +2 -2
  129. package/dist/disco-glyph.js +1 -1
  130. package/dist/dock.js +388 -287
  131. package/dist/{dockContext-spUj_-E5.js → dockContext-BKeIMmNk.js} +1 -1
  132. package/dist/dom.js +6 -6
  133. package/dist/{dropdown-menu-EFjl5iKo.js → dropdown-menu-B_4bWyyG.js} +5 -5
  134. package/dist/dropdown-menu.js +1 -1
  135. package/dist/expandable-container.js +2 -2
  136. package/dist/fonts/README.md +26 -18
  137. package/dist/forms.js +10 -7
  138. package/dist/fourier-field.d.ts +1 -0
  139. package/dist/fourier-field.js +166 -0
  140. package/dist/glass-carousel.js +12 -7
  141. package/dist/glass-panel.js +7 -7
  142. package/dist/glass-ui.css +1 -1
  143. package/dist/glass-ui.js +209 -206
  144. package/dist/glyph-face.js +2 -2
  145. package/dist/goo-blob.js +485 -300
  146. package/dist/header-ribbon.js +1 -1
  147. package/dist/hover-card.js +1 -1
  148. package/dist/hover-popover.js +1 -1
  149. package/dist/icon-tooltip.js +1 -1
  150. package/dist/index.d.ts +1 -0
  151. package/dist/instrument-chassis.js +1 -1
  152. package/dist/instrument-rail.js +1 -1
  153. package/dist/keyboard.js +1 -1
  154. package/dist/{keys-BFoma9vm.js → keys-BsYjEFOW.js} +1 -1
  155. package/dist/label.js +1 -1
  156. package/dist/labeled-field.js +6 -6
  157. package/dist/metric-badge.js +1 -1
  158. package/dist/metric-stack.js +1 -1
  159. package/dist/{minimize-2-BP27-qBY.js → minimize-2-lcXOuc6V.js} +1 -1
  160. package/dist/motion-core.js +68 -98
  161. package/dist/motion.js +3 -3
  162. package/dist/notification.js +1 -1
  163. package/dist/number-field.js +1 -1
  164. package/dist/paper-backdrop.js +1 -1
  165. package/dist/popover.js +1 -1
  166. package/dist/progress.js +1 -1
  167. package/dist/pulse.js +1 -1
  168. package/dist/reactive.js +2 -2
  169. package/dist/resolveCanvasColor-BBiYfp8g.js +11 -0
  170. package/dist/responsive-tabs.js +2 -2
  171. package/dist/scrolling-text.js +1 -1
  172. package/dist/{search-DBG8qaRs.js → search-nbkvr2dC.js} +1 -1
  173. package/dist/search.js +12 -12
  174. package/dist/select.js +3 -3
  175. package/dist/separator.js +1 -1
  176. package/dist/{sheet-CQYYrkr9.js → sheet-VZEtNFvO.js} +4 -4
  177. package/dist/sheet.js +1 -1
  178. package/dist/slider-D9puf-Ko.js +142 -0
  179. package/dist/slider.js +1 -1
  180. package/dist/sortable-list.js +3 -3
  181. package/dist/styles/components.css +1 -1
  182. package/dist/styles/dock-controls.css +21 -18
  183. package/dist/styles/dock.css +415 -224
  184. package/dist/styles/fonts.css +0 -25
  185. package/dist/styles/glass.css +100 -41
  186. package/dist/styles/theme.css +58 -2
  187. package/dist/styles/tokens.css +139 -32
  188. package/dist/styles/typography.css +55 -72
  189. package/dist/styles/utilities.css +19 -14
  190. package/dist/styles/view-transition.css +12 -40
  191. package/dist/supportsCssTimeline-C5oe0lnD.js +12 -0
  192. package/dist/switch.js +1 -1
  193. package/dist/tabs.js +5 -5
  194. package/dist/timeline.js +3 -3
  195. package/dist/toast.js +1 -1
  196. package/dist/toggle-group.js +1 -1
  197. package/dist/tooltip.js +1 -1
  198. package/dist/typewriter.js +1 -1
  199. package/dist/{useAnimatedNumber-DfVgP4te.js → useAnimatedNumber-C_3wZLx4.js} +1 -1
  200. package/dist/{useCanvas2D-BeNJqqIS.js → useCanvas2D-ClrQuZtp.js} +2 -1
  201. package/dist/{useConfiguratorState-DUtC1jxr.js → useConfiguratorState-CMff4GzH.js} +5 -5
  202. package/dist/useResolveTokenColor-r9pnNjqK.js +24 -0
  203. package/dist/useScrollProgress-C9ysDic5.js +35 -0
  204. package/dist/useSpecularTracking-BVRZsRHA.js +26 -0
  205. package/dist/{useTextHighlight-Dmtofpk2.js → useTextHighlight-9nMW23Qb.js} +1 -1
  206. package/dist/{useTouchGate-CS5Csc2h.js → useTouchGate-WFyJYsth.js} +1 -1
  207. package/dist/vReveal-CqaCz_mC.js +44 -0
  208. package/dist/watercolor-dot.js +2 -2
  209. package/dist/{x-C4pz9nbD.js → x-Dfsegz9Y.js} +1 -1
  210. package/package.json +64 -4
  211. package/src/fonts/README.md +26 -18
  212. package/src/styles/dock-controls.css +21 -18
  213. package/src/styles/dock.css +415 -224
  214. package/src/styles/fonts.css +0 -25
  215. package/src/styles/glass.css +100 -41
  216. package/src/styles/theme.css +58 -2
  217. package/src/styles/tokens.css +139 -32
  218. package/src/styles/typography.css +55 -72
  219. package/src/styles/utilities.css +19 -14
  220. package/src/styles/view-transition.css +12 -40
  221. package/dist/CardFooter-C390imy7.js +0 -129
  222. package/dist/CollapsibleContent-cauTbZEM.js +0 -52
  223. package/dist/TabsIndicator-DA0x9gPr.js +0 -97
  224. package/dist/color-DS-iLRVM.js +0 -33
  225. package/dist/compile-C62K_FpC.js +0 -106
  226. package/dist/fonts/fraunces/fraunces-latin.woff2 +0 -0
  227. package/dist/slider-CgNzRSh6.js +0 -129
  228. package/dist/useViewTransition-DI0Rs2rV.js +0 -21
  229. package/dist/vReveal-u2wSG2El.js +0 -35
  230. package/src/fonts/fraunces/fraunces-latin.woff2 +0 -0
  231. /package/dist/{InstrumentChassis-DaHIZCqy.js → InstrumentChassis-C2f6cBaQ.js} +0 -0
  232. /package/dist/{InstrumentRail-B0qqLFN0.js → InstrumentRail-CUJ05YVG.js} +0 -0
  233. /package/dist/{ModalOverlay-B_CBtqcE.js → ModalOverlay-B1GkAjNb.js} +0 -0
  234. /package/dist/{PaperBackdrop-D_YZW47j.js → PaperBackdrop-CqwjnfLv.js} +0 -0
  235. /package/dist/{SelectGroup-CMdoCjRE.js → SelectGroup-BFiod7X2.js} +0 -0
  236. /package/dist/{SelectSeparator-CaJnPF3_.js → SelectSeparator-L8pPEn4L.js} +0 -0
  237. /package/dist/{_plugin-vue_export-helper-C1je1s0u.js → _plugin-vue_export-helper-ok8pgSl5.js} +0 -0
  238. /package/dist/composables/{dom → motion}/useTextHighlight.d.ts +0 -0
  239. /package/dist/{constants-C8NMonuo.js → constants-BPOqYJAm.js} +0 -0
  240. /package/dist/{createContext-DBMGRlx4.js → createContext-B44Sah_H.js} +0 -0
  241. /package/dist/{createLucideIcon-DuDoe_ra.js → createLucideIcon-B6uNAU9G.js} +0 -0
  242. /package/dist/{menuItemVariants-C2QlXqT3.js → menuItemVariants-DKM85SNE.js} +0 -0
  243. /package/dist/{presets-Dyv9Zl7W.js → presets-D1XPuTj-.js} +0 -0
  244. /package/dist/{prng-CjVgzjQT.js → prng-COGhh02-.js} +0 -0
  245. /package/dist/{useGlassRenderer-Dn3WpfG-.js → useGlassRenderer-5I0YkI4t.js} +0 -0
  246. /package/dist/{useGlobalDark-D7R9OypB.js → useGlobalDark-D3EBWzXK.js} +0 -0
  247. /package/dist/{useIntersectionPause-DsQYGc8V.js → useIntersectionPause-Bj4sPkMh.js} +0 -0
  248. /package/dist/{useInterval-CHVYFpXV.js → useInterval-CcLfq2WU.js} +0 -0
  249. /package/dist/{useKeyboardShortcuts-i8tHd5WO.js → useKeyboardShortcuts-BXWXOZ-9.js} +0 -0
  250. /package/dist/{useResizeObserver-DX-STszm.js → useResizeObserver-CWCbk5Px.js} +0 -0
  251. /package/dist/{useSortable-muI29agz.js → useSortable-MGAbCGVJ.js} +0 -0
  252. /package/dist/{useSpringMount-CnizvZGm.js → useSpringMount-BJSHcLl3.js} +0 -0
  253. /package/dist/{useTimer-DGgoxTXL.js → useTimer-3xqf_54b.js} +0 -0
  254. /package/dist/{useUserInvalidAria-DmvZ_6Dx.js → useUserInvalidAria-DoJKMZ3s.js} +0 -0
  255. /package/dist/{useViewportReady-Dcvf0c4p.js → useViewportReady-RIr5Wgkh.js} +0 -0
package/dist/aurora.js CHANGED
@@ -1,12 +1,13 @@
1
- import { t as e } from "./_plugin-vue_export-helper-C1je1s0u.js";
2
- import { a as t, i as n, n as r, o as i, r as a, s as o, t as s } from "./compile-C62K_FpC.js";
3
- import { n as c, r as l, t as u } from "./presets-Dyv9Zl7W.js";
4
- import { a as d, r as f, t as p } from "./color-DS-iLRVM.js";
5
- import { t as m } from "./useIntersectionPause-DsQYGc8V.js";
6
- import { computed as h, createElementBlock as g, createElementVNode as _, defineComponent as v, normalizeClass as y, normalizeStyle as b, onBeforeUnmount as x, onMounted as S, openBlock as ee, readonly as te, ref as C, unref as w, watch as T } from "vue";
7
- import { gamutMapOKLab as E, interpolateHue as D, isInSRGBGamut as O, oklabToLinearSRGB as k, rawOklabToOklch as A, rawOklchToOklab as j, srgbToOKLab as ne } from "@mkbabb/value.js";
1
+ import { t as e } from "./_plugin-vue_export-helper-ok8pgSl5.js";
2
+ import { a as t, c as n, d as r, f as i, g as a, h as o, i as s, l as c, m as l, n as u, o as d, p as f, r as p, s as m, t as h, u as g } from "./compile-BBTRjvQS.js";
3
+ import { n as _, r as v, t as y } from "./presets-D1XPuTj-.js";
4
+ import { a as ee, c as b, i as te, l as x, o as S, t as C } from "./color-DMLTunJK.js";
5
+ import { t as ne } from "./useIntersectionPause-Bj4sPkMh.js";
6
+ import { t as re } from "./useScrollProgress-C9ysDic5.js";
7
+ import { computed as w, createElementBlock as ie, createElementVNode as T, defineComponent as ae, normalizeClass as oe, normalizeStyle as E, onBeforeUnmount as D, onMounted as O, openBlock as se, readonly as ce, ref as k, unref as le, watch as A } from "vue";
8
+ import { interpolateHue as ue, rawOklabToOklch as de, srgbToOKLab as fe } from "@mkbabb/value.js";
8
9
  //#region src/components/custom/aurora/constants/shaders/aurora.vert.ts
9
- var re = "#version 300 es\nin vec2 aPos;\nout vec2 vUv;\nvoid main() {\n vUv = aPos * 0.5 + 0.5;\n gl_Position = vec4(aPos, 0.0, 1.0);\n}", ie = "// ── Palette LUT (W5 — OKLCh/OKLab interpolation) ──────────────────────────────\n// The endpoints stay CPU-baked to LINEAR sRGB (the Aras precompute pattern); only\n// the BETWEEN-endpoint interpolation moves to perceptual space. OKLab-rectangular\n// for the default ramp (holds chroma, no grey midpoint); the OKLCh hue-arc only on\n// uHuePath increasing/decreasing (deliberate rainbow travel). The prior linear\n// mix() (the muddy-midtone source) is GONE.\nvec3 samplePalette(float id) {\n id = clamp(id, 0.0, 1.0);\n float scaled = id * float(uStopCount - 1);\n int i0 = int(floor(scaled));\n int i1 = min(i0 + 1, uStopCount - 1);\n float t = fract(scaled);\n t = smoothstep(0.0, 1.0, t);\n vec3 a = uPalette[i0];\n vec3 b = uPalette[i1];\n // increasing(2)/decreasing(3) request the OKLCh hue-arc (rainbow sweep); every\n // other method takes the OKLab-rectangular straight line (no hue detour).\n if (uHuePath == 2 || uHuePath == 3) {\n return mixPaletteOklchArc(a, b, t, uHuePath);\n }\n return mixPaletteOklab(a, b, t);\n}\n\n// ── Nuclei field ──────────────────────────────────────────────────────────\nvoid nucleiField(vec2 p, float t, out float paletteId, out float valueMod) {\n float accumBias = 0.0;\n float accumValue = 0.0;\n float accumW = 0.0;\n for (int i = 0; i < MAX_NUCLEI; i++) {\n if (i >= uNucleiCount) break;\n vec2 posI = uNucleiPos[i]\n + uNucleiDriftRadius[i] * vec2(\n cos(t * uNucleiDrift * K_NUCLEI + uNucleiDriftPhase[i]),\n sin(t * uNucleiDrift * K_NUCLEI + uNucleiDriftPhase[i] * 1.13)\n );\n vec2 diff = p - posI;\n // Anisotropic Gaussian: rotate diff into the nucleus's local frame\n // (major axis along uNucleiAngle), then scale the major-axis component by\n // 1/elongation so the squared distance describes an ellipse. Defaults\n // 1.0/0.0 reduce to the isotropic dot(diff, diff).\n float ca = cos(uNucleiAngle[i]);\n float sa = sin(uNucleiAngle[i]);\n vec2 local = vec2( ca * diff.x + sa * diff.y,\n -sa * diff.x + ca * diff.y);\n float along = local.x / max(uNucleiElong[i], 0.01);\n float across = local.y;\n float d2 = along * along + across * across;\n float r = max(uNucleiRadius[i], 0.01);\n float w = exp(-uSoftmaxBeta * d2 / (r * r));\n accumBias += w * uNucleiPaletteBias[i];\n accumValue += w * uNucleiValueBias[i];\n accumW += w;\n }\n paletteId = accumBias / max(accumW, 1e-4);\n valueMod = accumValue / max(accumW, 1e-4);\n\n // Palette drift — slow global paletteId breathe between adjacent stops. The\n // rate rides K_PAL (perceptible ~30–60s hue cycle at the default coefficient);\n // the amplitude lifts the authored coefficient into a 0.03..0.06 paletteId band\n // so the palette visibly travels rather than dithering within one stop.\n float palAmp = clamp(uPaletteDrift * 6.0, 0.0, 0.06);\n paletteId += palAmp * sin(t * uPaletteDrift * K_PAL);\n paletteId = clamp(paletteId, 0.0, 1.0);\n}\n", ae = "// ── Flow field ────────────────────────────────────────────────────────────\nvec2 flowField(vec2 p, float t) {\n vec2 dir = vec2(1.0, 0.0);\n if (uFlowPattern == 1) {\n // radial\n dir = normalize(p - uFlowFocal + 1e-4);\n } else if (uFlowPattern == 2) {\n // swirl — tangent to radial\n vec2 rad = normalize(p - uFlowFocal + 1e-4);\n dir = vec2(-rad.y, rad.x);\n } else if (uFlowPattern == 3) {\n // diagonal\n float a = radians(uFlowAngle);\n dir = vec2(cos(a), sin(a));\n } else if (uFlowPattern == 4) {\n // multi — curl-noise driven\n float n = fbm(p * 2.0 + t * 0.02);\n float a = n * 6.2831;\n dir = vec2(cos(a), sin(a));\n }\n // curl — perturb by noise. Radial gets much less curl so rays stay clean.\n if (uFlowCurl > 0.0) {\n float n = fbm(p * 3.0) - 0.5;\n float curlAmt = uFlowCurl;\n if (uFlowPattern == 1) curlAmt *= 0.25; // radial: preserve ray clarity\n if (uFlowPattern == 2) curlAmt *= 0.55; // swirl: allow moderate curl\n float a = atan(dir.y, dir.x) + n * 3.14159 * curlAmt;\n dir = vec2(cos(a), sin(a));\n }\n // cursor influence — swirl around the cursor position\n if (uCursorStrength > 0.001) {\n vec2 toCur = uCursor - p;\n float d = length(toCur);\n float r = max(uCursorRadius, 0.01);\n float w = exp(-(d * d) / (r * r * 0.5));\n // swirl tangent\n vec2 tangent = vec2(-toCur.y, toCur.x) / max(d, 1e-4);\n float a0 = atan(dir.y, dir.x);\n float a1 = atan(tangent.y, tangent.x);\n // blend angle\n float da = a1 - a0;\n da = atan(sin(da), cos(da)); // wrap to [-pi, pi]\n float a = a0 + da * w * uCursorStrength;\n dir = vec2(cos(a), sin(a));\n }\n return dir;\n}\n", oe = "// ACES approximation\nvec3 aces(vec3 x) {\n float a = 2.51;\n float b = 0.03;\n float c = 2.43;\n float d = 0.59;\n float e = 0.14;\n return clamp((x * (a * x + b)) / (x * (c * x + d) + e), 0.0, 1.0);\n}\n", se = "// ── Curved swept brushstroke primitive ────────────────────────────────────\n// A stroke is a curved spine from A to B with quadratic bulge K (sideways offset\n// at midpoint). Width varies along length via a shape profile. Edge is ragged\n// (bristle-modulated), ends are rounded blobs, inside has streak modulation.\n//\n// For pixel p, we invert an approximate projection onto the curved spine:\n// 1) project p onto straight AB axis -> along0, cross0\n// 2) at parameter along0, spine offset = 4·K·along0·(1-along0) perpendicular\n// 3) cross = cross0 - spineOffset, refine along one iteration\n// Coverage blends paint atop col based on per-stroke color sampled from the\n// underlying base at spine midpoint — so overlapping strokes from different\n// palette regions meet at hard, ragged demarcations.\n\nstruct StrokeHit {\n float coverage; // 0..1\n vec3 color;\n float alongT; // 0..1 along spine, for internal modulation\n float crossN; // cross distance / current half-width, signed\n float edgeN; // distance to edge in half-widths (0 = at edge, 1 = at spine)\n};\n\nStrokeHit noHit() { return StrokeHit(0.0, vec3(0.0), 0.0, 0.0, 0.0); }\n\nvec2 rotateDir(vec2 dir, float angle) {\n float ca = cos(angle), sa = sin(angle);\n return vec2(dir.x * ca - dir.y * sa, dir.x * sa + dir.y * ca);\n}\n\nvec2 safeDir(vec2 dir) {\n float len = length(dir);\n return len > 1e-4 ? dir / len : vec2(1.0, 0.0);\n}\n\n// Shape profile along the stroke. type:\n// 0 tapered — thin-fat-thin (classic brush)\n// 1 load-drag — fat start, tapers to a point\n// 2 dab — ellipse-like, round center\n// 3 even — near-constant, slight end taper\nfloat strokeShape(float t, int type) {\n t = clamp(t, 0.0, 1.0);\n if (type == 1) {\n // loaded at t=0, tapering\n return pow(1.0 - t, 0.55) * smoothstep(0.0, 0.08, t);\n } else if (type == 2) {\n // dab / blob\n float d = t - 0.5;\n return exp(-d * d * 12.0);\n } else if (type == 3) {\n // mostly even, slight end softening\n return smoothstep(0.0, 0.08, t) * smoothstep(1.0, 0.92, t) * 0.95 + 0.05;\n }\n // 0 tapered\n return smoothstep(0.0, 0.22, t) * smoothstep(1.0, 0.78, t);\n}\n\n// A single curved stroke.\n// a, b — endpoints\n// halfW — base half-width (world units)\n// bulge — signed perpendicular midpoint offset, in world units\n// shapeType — 0..3\n// bristleFreq — spatial frequency of edge raggedness\n// bristleAmp — 0..0.5 fraction of halfW chewed away at edge extrema\n// streakSeed — uniqueness seed for internal streaks\n// colAtMid — pre-sampled color at midpoint\nStrokeHit curvedStroke(vec2 p, vec2 a, vec2 b, float halfW,\n float bulge, int shapeType,\n float bristleFreq, float bristleAmp,\n float streakSeed, vec3 colAtMid) {\n vec2 ab = b - a;\n float L = length(ab);\n if (L < 1e-5) return noHit();\n vec2 tang = ab / L;\n vec2 norm = vec2(-tang.y, tang.x);\n\n vec2 rel = p - a;\n float along0 = dot(rel, tang) / L; // straight projection 0..1 along AB\n float cross0 = dot(rel, norm); // signed cross\n\n // Spine sideways offset due to bulge (parabolic)\n float bend = 4.0 * bulge * along0 * (1.0 - along0);\n float cross1 = cross0 - bend;\n\n // One refinement step: as the spine bends, the closest-point along shifts.\n // Spine tangent differs from straight tangent; first-order correction:\n float dBend = 4.0 * bulge * (1.0 - 2.0 * along0); // d(bend)/d(along)\n float along1 = along0 + (cross1 * dBend) / (L * (1.0 + dBend * dBend / (L * L)));\n\n // Bristle-ragged edge: half-width gets chewed by 1D noise along the spine\n float edgeNoise = fbm(vec2(along1 * bristleFreq, streakSeed * 7.3)) - 0.5;\n float edgeNoise2 = fbm(vec2(along1 * bristleFreq * 2.3, streakSeed * 3.1 + 17.0)) - 0.5;\n float edgeMod = 1.0 - bristleAmp * (0.6 + 0.4 * edgeNoise) * (0.5 + edgeNoise2);\n\n // Width profile along the stroke\n float shape = strokeShape(along1, shapeType);\n float halfWNow = halfW * shape * edgeMod;\n\n // Inside-segment coverage\n float cov = 0.0;\n if (along1 >= 0.0 && along1 <= 1.0 && halfWNow > 1e-6) {\n float cn = abs(cross1) / halfWNow;\n // fwidth-derived AA: half-width from the field's screen-space gradient so the\n // edge holds ~1px regardless of zoom/DPR (mirrors metaball.frag.ts:252-255).\n float aaC = max(fwidth(cn), 1e-6);\n cov = 1.0 - smoothstep(1.0 - aaC, 1.0 + aaC, cn);\n }\n\n // End-cap blobs (rounded tips, not perpendicular cuts)\n float capA = 0.0, capB = 0.0;\n {\n vec2 capCenterA = a + norm * (bulge * 0.0); // at a\n vec2 capCenterB = a + tang * L + norm * bend * 0.0 + norm * 0.0; // at b; bend is 0 at endpoints\n capCenterB = b;\n float endShapeA = strokeShape(0.02, shapeType);\n float endShapeB = strokeShape(0.98, shapeType);\n float rA = halfW * endShapeA * (0.9 + 0.2 * fbm(vec2(streakSeed * 13.0)));\n float rB = halfW * endShapeB * (0.9 + 0.2 * fbm(vec2(streakSeed * 19.0 + 5.0)));\n float dA = length(p - capCenterA);\n float dB = length(p - capCenterB);\n // fwidth-derived AA on the clean Euclidean cap distances (mirrors metaball.frag.ts:252-255).\n float aaA = max(fwidth(dA), 1e-6);\n float aaB = max(fwidth(dB), 1e-6);\n capA = 1.0 - smoothstep(rA - aaA, rA + aaA, dA);\n capB = 1.0 - smoothstep(rB - aaB, rB + aaB, dB);\n // Only apply caps if we're BEYOND the segment; otherwise the segment wins.\n capA *= (along1 < 0.05) ? 1.0 : 0.0;\n capB *= (along1 > 0.95) ? 1.0 : 0.0;\n }\n\n float coverage = max(cov, max(capA, capB));\n if (coverage < 0.002) return noHit();\n\n // Edge distance (for impasto later): 0 at edge, 1 at spine\n float edgeDist = halfWNow > 1e-6 ? clamp(1.0 - abs(cross1) / halfWNow, 0.0, 1.0) : 0.0;\n\n return StrokeHit(\n coverage,\n colAtMid,\n clamp(along1, 0.0, 1.0),\n halfWNow > 1e-6 ? cross1 / halfWNow : 0.0,\n edgeDist\n );\n}\n\n// Composite stroke over col with internal streaking + impasto edge highlight.\n// streakAmp — 0..0.2 how much internal streaks darken/lighten\n// impastoAmp — 0..1 edge catch-light strength\n// hardness — 0..1 how crisp the compositing transition is (1 = razor, 0 = creamy)\nvoid paintOver(inout vec3 col, StrokeHit s, float streakFreq, float streakAmp,\n float impastoAmp, float hardness, float streakSeed) {\n if (s.coverage < 0.002) return;\n float strokeOpacity = clamp(uStrokeAmount, 0.0, 1.0);\n if (strokeOpacity <= 0.001) return;\n vec3 c = s.color;\n\n // Internal streaks — fbm along spine, modulated by cross position. Gives\n // the loaded-brush look: some bristles carry more pigment than others.\n float streakA = fbm(vec2(s.alongT * streakFreq, s.crossN * 2.7 + streakSeed));\n float streakB = fbm(vec2(s.alongT * streakFreq * 0.6 + streakSeed * 3.7, s.crossN * 4.1));\n float streak = 0.6 * (streakA - 0.5) + 0.4 * (streakB - 0.5);\n c *= 1.0 + streak * streakAmp * 2.0;\n\n // Subtle value variance across width (hollow-center catch-light)\n float crossShade = smoothstep(0.0, 0.4, s.edgeN) * (1.0 - smoothstep(0.65, 1.0, s.edgeN));\n c *= 1.0 + crossShade * 0.05;\n\n // Impasto edge highlight — bright rim on one side of the stroke\n float rim = smoothstep(0.85, 1.0, 1.0 - s.edgeN) * step(0.0, s.crossN);\n c += impastoAmp * rim * vec3(0.18, 0.15, 0.11);\n // Shadow on the other side (darker, cooler)\n float shadow = smoothstep(0.85, 1.0, 1.0 - s.edgeN) * step(0.0, -s.crossN);\n c -= impastoAmp * shadow * 0.25 * vec3(0.10, 0.09, 0.07);\n\n float softLimit = mix(0.35, 0.98, hardness);\n float alpha = smoothstep(0.0, 1.0 - softLimit, s.coverage) * strokeOpacity;\n col = mix(col, c, alpha);\n}\n\n// Best-of-9-neighbor placement: sample 3x3 surrounding cells and take the\n// stroke that covers this pixel most. Breaks the grid by per-cell jitter and\n// sparse density via noise thresholding.\nStrokeHit bestOil(vec2 p, float cellSize, float lenMul, float halfWMul,\n float jitterAmt, float density, int shapeType,\n float bristleAmp, vec2 flow, float t, float seed) {\n vec2 cell = floor(p / cellSize);\n\n StrokeHit best = noHit();\n // 3x3 neighborhood\n for (int dy = -1; dy <= 1; dy++) {\n for (int dx = -1; dx <= 1; dx++) {\n vec2 cc = cell + vec2(float(dx), float(dy));\n vec2 hh = hash22(cc + seed) - 0.5;\n // Density gate — noise threshold; sparse placement.\n float gate = hash21(cc * 1.7 + seed * 0.3);\n if (gate > density) continue;\n\n vec2 center = (cc + 0.5 + hh * jitterAmt) * cellSize;\n\n // Per-stroke direction: consume the layer-provided flow, then add only\n // deterministic local perturbation so alternate stroke layers stay live.\n vec2 f = safeDir(flow);\n float angJ = (hash21(cc + seed + 11.0) - 0.5) * 0.9; // +/- 0.45 rad\n float localCurl = (fbm(center * (2.6 + seed * 0.11) + seed * 1.9) - 0.5) * 0.55 * uFlowCurl;\n vec2 dir = rotateDir(f, angJ + localCurl);\n\n float lenV = cellSize * lenMul * (0.65 + 0.55 * hash21(cc + seed + 23.0));\n float halfW = cellSize * halfWMul * (0.70 + 0.55 * hash21(cc + seed + 41.0));\n float bulge = (hash21(cc + seed + 53.0) - 0.5) * lenV * 0.35;\n\n vec2 a = center - dir * (lenV * 0.5);\n vec2 b = center + dir * (lenV * 0.5);\n\n vec3 colMid = brokenColorJitter(\n sampleBase(center, t),\n hash21(cc + seed + 89.0),\n hash21(cc * 2.3 + seed + 97.0),\n 1.0\n );\n\n StrokeHit h = curvedStroke(p, a, b, halfW, bulge, shapeType,\n 7.0, bristleAmp,\n hash21(cc + seed + 67.0), colMid);\n if (h.coverage > best.coverage) best = h;\n }\n }\n return best;\n}\n", ce = "// ── Medium overlays ───────────────────────────────────────────────────────\n\n// A quick re-computation of base color for edge-mask sampling\nvec3 sampleBase(vec2 p, float t) {\n vec2 pw = domainWarp(p, t);\n float id; float vm;\n nucleiField(pw, t, id, vm);\n vec3 c = samplePalette(id);\n c *= 1.0 + uValueVariance * vm;\n return c;\n}\n\nvec3 mediumWatercolor(vec3 col, vec2 p, float t) {\n // Wet-edge cauliflowers via luma-gradient magnitude\n float eps = 0.004;\n vec3 cx1 = sampleBase(p + vec2(eps, 0.0), t);\n vec3 cx2 = sampleBase(p - vec2(eps, 0.0), t);\n vec3 cy1 = sampleBase(p + vec2(0.0, eps), t);\n vec3 cy2 = sampleBase(p - vec2(0.0, eps), t);\n float gx = dot(cx1 - cx2, W_LUMA);\n float gy = dot(cy1 - cy2, W_LUMA);\n float edge = sqrt(gx * gx + gy * gy) / (2.0 * eps);\n float mask = smoothstep(0.0, 2.5, edge);\n col *= mix(1.0, 0.78, mask * uWetEdge);\n\n // Granulation — pigment settles in paper tooth\n float paper = 0.5 * vnoise(p * 160.0) + 0.5 * vnoise(p * 360.0);\n float pigLoad = 1.0 - dot(col, W_LUMA);\n col *= 1.0 - uGranulation * pigLoad * (paper - 0.5);\n\n // Wash banding — faint horizontal wet gradient\n float band = fbm(vec2(p.x * 1.5, p.y * 0.4));\n col *= 1.0 + 0.04 * (band - 0.5);\n return col;\n}\n\nvec3 mediumPastel(vec3 col, vec2 p, float t) {\n vec2 flow = flowField(p, t);\n vec2 perp = vec2(-flow.y, flow.x);\n float along = dot(p, flow) * uStrokeScale;\n float across = dot(p, perp) * uStrokeScale;\n across += 0.03 * (vnoise(p * 260.0) - 0.5);\n\n float aniso = mix(1.0, 0.18, uStrokeAnisotropy);\n float stroke = fbm(vec2(along * aniso, across));\n col *= mix(1.0, 0.82 + 0.32 * stroke, uStrokeAmount);\n\n // Pastel tooth — tiny high-frequency grain\n float tooth = vnoise(p * 800.0);\n col *= 1.0 - 0.08 * uStrokeAmount * (tooth - 0.5);\n return col;\n}\n", M = "// ── Crayon / oil-pastel — paper tooth × wax pigment (PEER medium) ─────────\n// Crayon is not strokes. It's pigment crumbs dragged across paper tooth.\n// Model: heavy 2D tooth noise at multiple scales, anisotropically stretched\n// along flow direction, multiplied into the base color. Add a slow \"waxy\n// film\" that slightly unifies hues, and occasional darker \"pressed\" spots\n// where the crayon dug in. NO straight segments. Dispatched at main() level as\n// a peer of pastel/watercolor/oil — never a branch inside mediumOil().\nvec3 mediumCrayon(vec3 col, vec2 p, float t) {\n vec2 flow = flowField(p, t);\n float ang = atan(flow.y, flow.x);\n float ca = cos(-ang), sa = sin(-ang);\n vec2 pr = vec2(p.x * ca - p.y * sa, p.x * sa + p.y * ca);\n\n // Anisotropic tooth — squished along flow, coarse cross flow.\n float aniso = mix(0.45, 0.95, uStrokeAnisotropy);\n float scale = max(uStrokeScale * 1.6, 180.0);\n\n float t1 = vnoise(vec2(pr.x * scale * aniso, pr.y * scale));\n float t2 = vnoise(vec2(pr.x * scale * aniso * 0.4, pr.y * scale * 0.4) + 11.0);\n float t3 = vnoise(vec2(pr.x * scale * aniso * 2.1, pr.y * scale * 2.1) + 23.0);\n float tooth = 0.55 * t1 + 0.30 * t2 + 0.15 * t3;\n // Center at 0, amplify\n tooth = (tooth - 0.5) * 1.4;\n\n // Multiplicative darkening where tooth is low (pigment skipped paper valleys)\n float lay = 1.0 + tooth * 0.32 * uStrokeAmount;\n vec3 result = col * lay;\n\n // Occasional pressed-in crumbs — rare darker crumbs\n float crumbs = smoothstep(0.78, 0.95, vnoise(pr * scale * 3.0));\n result *= 1.0 - crumbs * 0.18 * uStrokeAmount;\n\n // Waxy highlight film — slight lightening on tooth peaks\n float waxy = smoothstep(0.55, 0.85, t1);\n result += waxy * 0.04 * vec3(1.0);\n\n // Paper tooth overlay (subtler than oil's canvas)\n float paperTooth = vnoise(p * 340.0) - 0.5;\n result *= 1.0 + paperTooth * 0.14 * uCanvasGrain;\n\n // Broken-color pigment: stable wax/pigment patches, not temporal flicker.\n vec2 pigmentCell = floor(pr * max(scale * 0.18, 32.0));\n float pigmentMask = smoothstep(0.28, 0.82, vnoise(pr * scale * 0.21 + 19.0));\n result = brokenColorJitter(\n result,\n hash21(pigmentCell + 17.0),\n hash21(pigmentCell * 2.1 + 31.0),\n 0.45 + 0.55 * pigmentMask\n );\n\n // Crayon is saturation-amplified\n result = saturate3(result, 1.12);\n\n return result;\n}\n\nvec3 mediumOil(vec3 col, vec2 p, float t) {\n // Mode knobs (uStrokeMode) — oil-stroke modes ONLY (crayon is a peer medium,\n // uMedium==4, dispatched at main()):\n // 0 oil — balanced modern-abstract/palette-knife hybrid\n // 1 knife — palette-knife impasto: razor edges, heavy bristle/shadow\n // 3 brushwork — thick bristle brush\n int mode = uStrokeMode;\n\n // Per-mode parameters\n int shapeType = 0; // tapered\n float bristleAmp = 0.25; // 0..0.5\n float streakFreq = 9.0;\n float streakAmp = 0.09;\n float impastoAmp = 0.9;\n float hardness = 0.80; // edge compositing\n float toothScale = 240.0;\n float toothAmp = 0.09;\n float pigmentSat = 1.03;\n float densityBig = 0.65;\n float densityMed = 0.78;\n float densitySml = 0.90;\n\n if (mode == 1) { // palette knife\n shapeType = 3; // flat, even\n bristleAmp = 0.12;\n streakFreq = 4.0; streakAmp = 0.05;\n impastoAmp = 1.6;\n hardness = 0.95;\n toothAmp = 0.04;\n densityBig = 0.80; densityMed = 0.88; densitySml = 0.70;\n } else if (mode == 3) { // thick brushwork\n shapeType = 0; // tapered\n bristleAmp = 0.32;\n streakFreq = 14.0; streakAmp = 0.14;\n impastoAmp = 1.2;\n hardness = 0.85;\n toothAmp = 0.07;\n }\n\n // Scales & multipliers from uniforms\n float baseScale = max(uStrokeScale * 0.006, 0.008);\n // Three layers: big gestural, medium body, small dabs\n float sBig = baseScale * 2.4;\n float sMed = baseScale * 1.1;\n float sSml = baseScale * 0.45;\n\n float lenMulBig = mix(2.2, 3.8, uStrokeAnisotropy);\n float widMulBig = mix(0.55, 0.32, uStrokeAnisotropy);\n float lenMulMed = mix(2.0, 3.4, uStrokeAnisotropy);\n float widMulMed = mix(0.50, 0.30, uStrokeAnisotropy);\n float lenMulSml = mix(1.6, 2.6, uStrokeAnisotropy);\n float widMulSml = mix(0.45, 0.32, uStrokeAnisotropy);\n\n float jitterAmt = 0.75; // large jitter — no grid\n vec2 flow = flowField(p, t);\n\n vec3 result = col;\n\n // Layer 1 — big gestural strokes (sparse, shaping)\n StrokeHit hBig = bestOil(p, sBig, lenMulBig, widMulBig, jitterAmt * 0.55,\n densityBig, shapeType, bristleAmp, flow, t, 1.3);\n paintOver(result, hBig, streakFreq * 0.7, streakAmp,\n uImpasto * impastoAmp * uStrokeAmount, hardness, 1.3);\n\n // Layer 2 — medium body strokes\n StrokeHit hMed = bestOil(p + vec2(11.3, 3.7), sMed, lenMulMed, widMulMed,\n jitterAmt, densityMed, shapeType, bristleAmp, flow, t, 2.7);\n paintOver(result, hMed, streakFreq, streakAmp,\n uImpasto * impastoAmp * uStrokeAmount, hardness, 2.7);\n\n // Layer 3 — small dabs (more frequent, smaller)\n int smlShape = (mode == 1) ? 2 : shapeType; // knife uses dabs for sparkle\n StrokeHit hSml = bestOil(p + vec2(-5.1, 8.4), sSml, lenMulSml, widMulSml,\n jitterAmt * 1.3, densitySml, smlShape,\n bristleAmp * 0.85, flow, t, 4.1);\n paintOver(result, hSml, streakFreq * 1.4, streakAmp * 0.8,\n uImpasto * impastoAmp * 0.65 * uStrokeAmount, hardness, 4.1);\n\n // Layer 4 — fill dabs (very dense, very small) — covers bald spots\n float sFill = baseScale * 0.22;\n float lenMulFill = mix(1.4, 2.0, uStrokeAnisotropy);\n float widMulFill = mix(0.50, 0.38, uStrokeAnisotropy);\n int fillShape = (mode == 1) ? 3 : 2; // knife=even, others=dab (round fills)\n StrokeHit hFill = bestOil(p + vec2(3.9, -6.2), sFill, lenMulFill, widMulFill,\n jitterAmt * 1.5, 0.95, fillShape,\n bristleAmp * 0.6, flow, t, 8.9);\n paintOver(result, hFill, streakFreq * 1.8, streakAmp * 0.6,\n uImpasto * impastoAmp * 0.4 * uStrokeAmount, hardness * 0.9, 8.9);\n\n // Optional crosshatch layer\n if (uStrokeLayers == 2) {\n vec2 flow2 = vec2(-flow.y, flow.x);\n StrokeHit hX = bestOil(p + vec2(7.3, -2.1), sMed, lenMulMed * 0.9, widMulMed,\n jitterAmt, densityMed * 0.7, shapeType, bristleAmp, flow2, t, 6.5);\n paintOver(result, hX, streakFreq, streakAmp * 0.85,\n uImpasto * impastoAmp * 0.55 * uStrokeAmount, hardness, 6.5);\n }\n\n // Canvas tooth — linen weave\n float tooth1 = vnoise(p * toothScale);\n float tooth2 = vnoise(p * toothScale * vec2(0.6, 2.4) + 37.0);\n float tooth = (0.6 * tooth1 + 0.4 * tooth2) - 0.5;\n result *= 1.0 + tooth * toothAmp * uCanvasGrain;\n\n // Pigment saturation boost\n result = saturate3(result, pigmentSat);\n\n return result;\n}", N = "\n", P = `#version 300 es
10
+ var pe = "#version 300 es\nin vec2 aPos;\nout vec2 vUv;\nvoid main() {\n vUv = aPos * 0.5 + 0.5;\n gl_Position = vec4(aPos, 0.0, 1.0);\n}", me = "// ── Palette LUT (W5 — OKLCh/OKLab interpolation) ──────────────────────────────\n// The endpoints stay CPU-baked to LINEAR sRGB (the Aras precompute pattern); only\n// the BETWEEN-endpoint interpolation runs in perceptual space. AX.W11 — the ramp\n// itself (the smoothstep ease + the OKLab-rectangular-vs-OKLCh-hue-arc dispatch on\n// uHuePath) is the shared samplePaletteRamp from procedural-color.glsl, the SAME\n// source the WebGPU twin splices; samplePalette here only selects the bracketing\n// stop pair + the raw inter-stop t and hands them to the shared ramp.\nvec3 samplePalette(float id) {\n id = clamp(id, 0.0, 1.0);\n float scaled = id * float(uStopCount - 1);\n int i0 = int(floor(scaled));\n int i1 = min(i0 + 1, uStopCount - 1);\n float t = fract(scaled);\n return samplePaletteRamp(uPalette[i0], uPalette[i1], t, uHuePath);\n}\n\n// ── Nuclei field ──────────────────────────────────────────────────────────\nvoid nucleiField(vec2 p, float t, out float paletteId, out float valueMod) {\n float accumBias = 0.0;\n float accumValue = 0.0;\n float accumW = 0.0;\n for (int i = 0; i < MAX_NUCLEI; i++) {\n if (i >= uNucleiCount) break;\n vec2 posI = uNucleiPos[i]\n + uNucleiDriftRadius[i] * vec2(\n cos(t * uNucleiDrift * K_NUCLEI + uNucleiDriftPhase[i]),\n sin(t * uNucleiDrift * K_NUCLEI + uNucleiDriftPhase[i] * 1.13)\n );\n vec2 diff = p - posI;\n // Anisotropic Gaussian: rotate diff into the nucleus's local frame\n // (major axis along uNucleiAngle), then scale the major-axis component by\n // 1/elongation so the squared distance describes an ellipse. Defaults\n // 1.0/0.0 reduce to the isotropic dot(diff, diff).\n float ca = cos(uNucleiAngle[i]);\n float sa = sin(uNucleiAngle[i]);\n vec2 local = vec2( ca * diff.x + sa * diff.y,\n -sa * diff.x + ca * diff.y);\n float along = local.x / max(uNucleiElong[i], 0.01);\n float across = local.y;\n float d2 = along * along + across * across;\n float r = max(uNucleiRadius[i], 0.01);\n float w = exp(-uSoftmaxBeta * d2 / (r * r));\n accumBias += w * uNucleiPaletteBias[i];\n accumValue += w * uNucleiValueBias[i];\n accumW += w;\n }\n paletteId = accumBias / max(accumW, 1e-4);\n valueMod = accumValue / max(accumW, 1e-4);\n\n // Palette drift — slow global paletteId breathe between adjacent stops. The\n // rate rides K_PAL (perceptible ~30–60s hue cycle at the default coefficient);\n // the amplitude lifts the authored coefficient into a 0.03..0.06 paletteId band\n // so the palette visibly travels rather than dithering within one stop.\n float palAmp = clamp(uPaletteDrift * 6.0, 0.0, 0.06);\n paletteId += palAmp * sin(t * uPaletteDrift * K_PAL);\n paletteId = clamp(paletteId, 0.0, 1.0);\n}\n", he = "// ── Flow field ────────────────────────────────────────────────────────────\n// Structure-tensor prototype (body defined post-sampleBase in the mediums block).\n// Returns vec3(dir.x, dir.y, coherence) — the minor-eigenvector tangent + A.\nvec3 structureTensorField(vec2 p, float t, vec2 fallbackDir);\n\nvec2 flowField(vec2 p, float t) {\n vec2 dir = vec2(1.0, 0.0);\n if (uFlowPattern == 1) {\n // radial\n dir = normalize(p - uFlowFocal + 1e-4);\n } else if (uFlowPattern == 2) {\n // swirl — tangent to radial\n vec2 rad = normalize(p - uFlowFocal + 1e-4);\n dir = vec2(-rad.y, rad.x);\n } else if (uFlowPattern == 3) {\n // diagonal\n float a = radians(uFlowAngle);\n dir = vec2(cos(a), sin(a));\n } else if (uFlowPattern == 4) {\n // multi — curl-noise driven\n float n = fbm(p * 2.0 + t * 0.02);\n float a = n * 6.2831;\n dir = vec2(cos(a), sin(a));\n } else if (uFlowPattern == 5) {\n // tensor / ETF — the color field's OWN edge-tangent flow (AW.W4.1). The\n // diagonal angle seeds the smooth fallback the coherence-weighted blend\n // relaxes toward in low-structure zones. Returns directly (no curl/cursor\n // post-mix — the tensor field already carries the structure orientation;\n // the cursor warp still drives the underlying color field in domainWarp).\n float a = radians(uFlowAngle);\n vec2 fallback = vec2(cos(a), sin(a));\n return structureTensorField(p, t, fallback).xy;\n }\n // curl — perturb by noise. Radial gets much less curl so rays stay clean.\n if (uFlowCurl > 0.0) {\n float n = fbm(p * 3.0) - 0.5;\n float curlAmt = uFlowCurl;\n if (uFlowPattern == 1) curlAmt *= 0.25; // radial: preserve ray clarity\n if (uFlowPattern == 2) curlAmt *= 0.55; // swirl: allow moderate curl\n float a = atan(dir.y, dir.x) + n * 3.14159 * curlAmt;\n dir = vec2(cos(a), sin(a));\n }\n // cursor influence — swirl around the cursor position\n if (uCursorStrength > 0.001) {\n vec2 toCur = uCursor - p;\n float d = length(toCur);\n float r = max(uCursorRadius, 0.01);\n float w = exp(-(d * d) / (r * r * 0.5));\n // swirl tangent\n vec2 tangent = vec2(-toCur.y, toCur.x) / max(d, 1e-4);\n float a0 = atan(dir.y, dir.x);\n float a1 = atan(tangent.y, tangent.x);\n // blend angle\n float da = a1 - a0;\n da = atan(sin(da), cos(da)); // wrap to [-pi, pi]\n float a = a0 + da * w * uCursorStrength;\n dir = vec2(cos(a), sin(a));\n }\n // AW.W8.1 — velocity-reactive swirl-BURST: a fast flick (uCursorBurst, decaying\n // over ~1s) injects a transient flow bias along the pointer velocity, distinct from\n // the steady attraction above (the stateless swirl is SUPERSEDED by this form). The\n // burst is gated by the master tempo scalar on the CPU side (it decays to 0 under\n // PRM/pause), so this term cannot leak motion under reduced-motion.\n if (uCursorBurst > 0.001) {\n vec2 toCur = uCursor - p;\n float d = length(toCur);\n float r = max(uCursorRadius, 0.01);\n float w = exp(-(d * d) / (r * r * 0.7));\n // Bias the flow toward the velocity direction, scaled by the decaying burst.\n vec2 velDir = uCursorVelocity / max(length(uCursorVelocity), 1e-4);\n float a0 = atan(dir.y, dir.x);\n float a1 = atan(velDir.y, velDir.x);\n float da = atan(sin(a1 - a0), cos(a1 - a0));\n float a = a0 + da * w * uCursorBurst * 0.8;\n dir = vec2(cos(a), sin(a));\n }\n return dir;\n}\n", ge = "// ACES approximation\nvec3 aces(vec3 x) {\n float a = 2.51;\n float b = 0.03;\n float c = 2.43;\n float d = 0.59;\n float e = 0.14;\n return clamp((x * (a * x + b)) / (x * (c * x + d) + e), 0.0, 1.0);\n}\n", _e = "// ── Curved swept brushstroke primitive ────────────────────────────────────\n// A stroke is a curved spine from A to B with quadratic bulge K (sideways offset\n// at midpoint). Width varies along length via a shape profile. Edge is ragged\n// (bristle-modulated), ends are rounded blobs, inside has streak modulation.\n//\n// For pixel p, we invert an approximate projection onto the curved spine:\n// 1) project p onto straight AB axis -> along0, cross0\n// 2) at parameter along0, spine offset = 4·K·along0·(1-along0) perpendicular\n// 3) cross = cross0 - spineOffset, refine along one iteration\n// Coverage blends paint atop col based on per-stroke color sampled from the\n// underlying base at spine midpoint — so overlapping strokes from different\n// palette regions meet at hard, ragged demarcations.\n\nstruct StrokeHit {\n float coverage; // 0..1\n vec3 color;\n float alongT; // 0..1 along spine, for internal modulation\n float crossN; // cross distance / current half-width, signed\n float edgeN; // distance to edge in half-widths (0 = at edge, 1 = at spine)\n};\n\nStrokeHit noHit() { return StrokeHit(0.0, vec3(0.0), 0.0, 0.0, 0.0); }\n\nvec2 rotateDir(vec2 dir, float angle) {\n float ca = cos(angle), sa = sin(angle);\n return vec2(dir.x * ca - dir.y * sa, dir.x * sa + dir.y * ca);\n}\n\nvec2 safeDir(vec2 dir) {\n float len = length(dir);\n return len > 1e-4 ? dir / len : vec2(1.0, 0.0);\n}\n\n// Shape profile along the stroke. type:\n// 0 tapered — thin-fat-thin (classic brush)\n// 1 load-drag — fat start, tapers to a point\n// 2 dab — ellipse-like, round center\n// 3 even — near-constant, slight end taper\n// 4 comma — van-Gogh atomic dab: ASYMMETRIC taper, fat near one end and\n// drawn to a fine point at the other (the divisionist crescent\n// mark). The width peak sits off-centre (~0.32) so the two ends\n// taper at DIFFERENT rates — a loaded round head + a dragged tail.\nfloat strokeShape(float t, int type) {\n t = clamp(t, 0.0, 1.0);\n if (type == 1) {\n // loaded at t=0, tapering\n return pow(1.0 - t, 0.55) * smoothstep(0.0, 0.08, t);\n } else if (type == 2) {\n // dab / blob\n float d = t - 0.5;\n return exp(-d * d * 12.0);\n } else if (type == 3) {\n // mostly even, slight end softening\n return smoothstep(0.0, 0.08, t) * smoothstep(1.0, 0.92, t) * 0.95 + 0.05;\n } else if (type == 4) {\n // comma / crescent — width peaks at ~0.32 then drags to a fine tail. The two\n // sides of the peak fall at different exponents (a fat round head, a long thin\n // tail), so the mark reads as a directional van-Gogh comma, not a symmetric oval.\n float peak = 0.32;\n float head = smoothstep(0.0, peak, t); // rounded loaded head\n float tail = pow(clamp((1.0 - t) / (1.0 - peak), 0.0, 1.0), 1.6); // dragged thin tail\n return clamp(min(head, tail) * 1.04, 0.0, 1.0);\n }\n // 0 tapered\n return smoothstep(0.0, 0.22, t) * smoothstep(1.0, 0.78, t);\n}\n\n// A single curved stroke.\n// a, b — endpoints\n// halfW — base half-width (world units)\n// bulge — signed perpendicular midpoint offset, in world units\n// shapeType — 0..3\n// bristleFreq — spatial frequency of edge raggedness\n// bristleAmp — 0..0.5 fraction of halfW chewed away at edge extrema\n// streakSeed — uniqueness seed for internal streaks\n// colAtMid — pre-sampled color at midpoint\nStrokeHit curvedStroke(vec2 p, vec2 a, vec2 b, float halfW,\n float bulge, int shapeType,\n float bristleFreq, float bristleAmp,\n float streakSeed, vec3 colAtMid) {\n vec2 ab = b - a;\n float L = length(ab);\n if (L < 1e-5) return noHit();\n vec2 tang = ab / L;\n vec2 norm = vec2(-tang.y, tang.x);\n\n vec2 rel = p - a;\n float along0 = dot(rel, tang) / L; // straight projection 0..1 along AB\n float cross0 = dot(rel, norm); // signed cross\n\n // Spine sideways offset due to bulge (parabolic)\n float bend = 4.0 * bulge * along0 * (1.0 - along0);\n float cross1 = cross0 - bend;\n\n // One refinement step: as the spine bends, the closest-point along shifts.\n // Spine tangent differs from straight tangent; first-order correction:\n float dBend = 4.0 * bulge * (1.0 - 2.0 * along0); // d(bend)/d(along)\n float along1 = along0 + (cross1 * dBend) / (L * (1.0 + dBend * dBend / (L * L)));\n\n // Bristle-ragged edge: half-width gets chewed by 1D noise along the spine\n float edgeNoise = fbm(vec2(along1 * bristleFreq, streakSeed * 7.3)) - 0.5;\n float edgeNoise2 = fbm(vec2(along1 * bristleFreq * 2.3, streakSeed * 3.1 + 17.0)) - 0.5;\n float edgeMod = 1.0 - bristleAmp * (0.6 + 0.4 * edgeNoise) * (0.5 + edgeNoise2);\n\n // Width profile along the stroke\n float shape = strokeShape(along1, shapeType);\n float halfWNow = halfW * shape * edgeMod;\n\n // Inside-segment coverage\n float cov = 0.0;\n if (along1 >= 0.0 && along1 <= 1.0 && halfWNow > 1e-6) {\n float cn = abs(cross1) / halfWNow;\n // fwidth-derived AA: half-width from the field's screen-space gradient so the\n // edge holds ~1px regardless of zoom/DPR (mirrors metaball.frag.ts:252-255).\n float aaC = max(fwidth(cn), 1e-6);\n cov = 1.0 - smoothstep(1.0 - aaC, 1.0 + aaC, cn);\n }\n\n // End-cap blobs (rounded tips, not perpendicular cuts)\n float capA = 0.0, capB = 0.0;\n {\n vec2 capCenterA = a + norm * (bulge * 0.0); // at a\n vec2 capCenterB = a + tang * L + norm * bend * 0.0 + norm * 0.0; // at b; bend is 0 at endpoints\n capCenterB = b;\n float endShapeA = strokeShape(0.02, shapeType);\n float endShapeB = strokeShape(0.98, shapeType);\n float rA = halfW * endShapeA * (0.9 + 0.2 * fbm(vec2(streakSeed * 13.0)));\n float rB = halfW * endShapeB * (0.9 + 0.2 * fbm(vec2(streakSeed * 19.0 + 5.0)));\n float dA = length(p - capCenterA);\n float dB = length(p - capCenterB);\n // fwidth-derived AA on the clean Euclidean cap distances (mirrors metaball.frag.ts:252-255).\n float aaA = max(fwidth(dA), 1e-6);\n float aaB = max(fwidth(dB), 1e-6);\n capA = 1.0 - smoothstep(rA - aaA, rA + aaA, dA);\n capB = 1.0 - smoothstep(rB - aaB, rB + aaB, dB);\n // Only apply caps if we're BEYOND the segment; otherwise the segment wins.\n capA *= (along1 < 0.05) ? 1.0 : 0.0;\n capB *= (along1 > 0.95) ? 1.0 : 0.0;\n }\n\n float coverage = max(cov, max(capA, capB));\n if (coverage < 0.002) return noHit();\n\n // Edge distance (for impasto later): 0 at edge, 1 at spine\n float edgeDist = halfWNow > 1e-6 ? clamp(1.0 - abs(cross1) / halfWNow, 0.0, 1.0) : 0.0;\n\n return StrokeHit(\n coverage,\n colAtMid,\n clamp(along1, 0.0, 1.0),\n halfWNow > 1e-6 ? cross1 / halfWNow : 0.0,\n edgeDist\n );\n}\n\n// The painterly STROKE mediums (those that deposit via paintStrokeLayers/paintOver):\n// oil (3), van-Gogh (5), oil-pastel (6). For these the stroke OVER-composite runs in\n// OKLab so overlapping complementaries mix through a chromatic path, not the linear-\n// RGB grey mud; the smooth/atmospheric pole never reaches paintOver so it pays nothing.\nbool isPainterlyStroke() {\n return uMedium == 3 || uMedium == 5 || uMedium == 6;\n}\n\n// OKLab OVER-composite: lerp L,a,b of the over-color toward the under-color (the\n// Ottosson matrices spliced from procedural-color.glsl.ts — no new color math). Two\n// complementary-hued strokes overlapping transition through OKLab, where the midpoint\n// holds chroma, instead of the linear mix() that desaturates toward grey (slice 8 F2).\nvec3 paintOverOklab(vec3 under, vec3 over, float alpha) {\n vec3 labU = linOklab(under);\n vec3 labO = linOklab(over);\n return max(oklabToLinearSrgb(mix(labU, labO, alpha)), vec3(0.0));\n}\n\n// Composite stroke over col with internal streaking + ACCUMULATE paint HEIGHT.\n// streakAmp — 0..0.2 how much internal streaks darken/lighten\n// impastoAmp — 0..1 paint-thickness contribution (the height the relight reads)\n// hardness — 0..1 how crisp the compositing transition is (1 = razor, 0 = creamy)\n//\n// AW.W4.2 — the fixed-RGB edge rim (the phantom upper-left light) is RETIRED. Each\n// stroke now deposits PAINT HEIGHT into the height accumulator (coverage x per-stroke\n// thickness, perturbed by the bristle/streak fbm for ridges/grooves); mediumOil\n// derives a normal from the accumulated height gradient (dFdx/dFdy) and relights it\n// once with the movable uLightDir source (relightImpasto), in linear before aces().\n//\n// AX.W13 — the internal streak now perturbs HUE + CHROMA in OKLCh (not value-only),\n// so a single stroke carries a small hue gradient (broken color at the ATOM level —\n// adjacent strokes shimmer like real impasto, not flat stamped swatches; slice 8 F5);\n// and the OVER-composite runs in OKLab on the painterly stroke mediums so overlapping\n// complementaries mix as pigment, not the linear-mix grey (slice 8 F2).\n// impastoFloor — the per-stroke height-crown floor: oil's 0.4+0.6*edgeN reads\n// floor=0.4 (the crown tapers to 40% at the stroke edge); the van-Gogh\n// profile passes floor=1.0 so each atomic dab catches a FULL-height\n// glint (scope item 1d). The crown magnitude is floor + (1-floor)*edgeN.\nvoid paintOver(inout vec3 col, inout float height, StrokeHit s,\n float streakFreq, float streakAmp,\n float impastoAmp, float hardness, float streakSeed,\n float impastoFloor) {\n if (s.coverage < 0.002) return;\n float strokeOpacity = clamp(uStrokeAmount, 0.0, 1.0);\n if (strokeOpacity <= 0.001) return;\n vec3 c = s.color;\n\n // Internal streaks — fbm along spine, modulated by cross position. Gives\n // the loaded-brush look: some bristles carry more pigment than others.\n float streakA = fbm(vec2(s.alongT * streakFreq, s.crossN * 2.7 + streakSeed));\n float streakB = fbm(vec2(s.alongT * streakFreq * 0.6 + streakSeed * 3.7, s.crossN * 4.1));\n float streak = 0.6 * (streakA - 0.5) + 0.4 * (streakB - 0.5);\n\n // ── WITHIN-stroke OKLCh broken color (AX.W13, slice 8 F5). The streak fBm perturbs\n // the stroke's HUE + CHROMA along alongT/crossN, seeded per-stroke, so a single\n // stroke reads a small hue gradient (the atom-level broken-color shimmer). Value is\n // carried by the same streak so the loaded-brush light/dark survives. Gated to the\n // painterly stroke mediums + uBrokenColor (the smooth pole keeps the cheap value-only\n // streak). A second decorrelated fBm drives the hue axis so hue ≠ a scaled value.\n float hueStreak = fbm(vec2(s.alongT * streakFreq * 0.45 + streakSeed * 5.3,\n s.crossN * 1.9 + streakSeed * 1.7)) - 0.5;\n if (isPainterlyStroke() && uBrokenColor > 0.001) {\n vec3 lch = oklabToOklch(linOklab(c));\n // ±~10° within-stroke hue swing + ±~9% chroma, scaled by uBrokenColor. Smaller\n // than the per-stroke cell jitter (brokenColorJitter) — this is the intra-stroke\n // gradient layered on top of the per-cell pigment patch.\n lch.z += hueStreak * (20.0 * PI / 180.0) * uBrokenColor;\n lch.y = max(lch.y * (1.0 + (streak) * 0.18 * uBrokenColor), 0.0);\n c = max(oklabToLinearSrgb(oklchToOklab(lch)), vec3(0.0));\n }\n // Value streak (the loaded-brush light/dark across the bristles) — kept for every\n // medium that reaches paintOver (the smooth pole never does).\n c *= 1.0 + streak * streakAmp * 2.0;\n\n // Subtle value variance across width (hollow-center catch-light)\n float crossShade = smoothstep(0.0, 0.4, s.edgeN) * (1.0 - smoothstep(0.65, 1.0, s.edgeN));\n c *= 1.0 + crossShade * 0.05;\n\n float softLimit = mix(0.35, 0.98, hardness);\n float alpha = smoothstep(0.0, 1.0 - softLimit, s.coverage) * strokeOpacity;\n // OKLab OVER-composite on the painterly stroke mediums (overlap mixes as pigment,\n // not grey); the smooth pole never reaches paintOver, so it keeps the cheap path.\n col = isPainterlyStroke() ? paintOverOklab(col, c, alpha) : mix(col, c, alpha);\n\n // ── Accumulate paint HEIGHT (AW.W4.2). Thickness peaks at the stroke spine and\n // falls toward the impastoFloor at the edge (edgeN: 0=edge, 1=spine), perturbed by\n // the bristle streak so ridges/grooves form. The relight reads the gradient of this\n // field. The van-Gogh profile's floor=1.0 gives every atomic dab a full-height crown.\n float ridge = 0.7 + 0.3 * (streak + 0.5);\n float crown = impastoFloor + (1.0 - impastoFloor) * s.edgeN;\n height += s.coverage * impastoAmp * crown * ridge * alpha;\n}\n\n// AW.W4.2 — relight the accumulated paint height. Derive a normal from the height\n// gradient (dFdx/dFdy — derivatives are in-pattern; fwidth is already used for AA),\n// then apply diffuse + Blinn specular from the movable uLightDir/uLightColor source.\n// LINEAR-space lighting BEFORE aces() (the tonemap/OETF stay locked); thin strokes\n// inherit a low shininess, thick impasto raises it (high-height ridges catch a sharp\n// glint). canvasBase is the tooth height so the canvas weave also catches the light.\nvec3 relightImpasto(vec3 col, float height, float canvasBase) {\n float h = height + canvasBase;\n // Height-gradient normal. heightScale trades relief depth for surface flatness.\n float heightScale = 6.0;\n vec3 N = normalize(vec3(-dFdx(h) * 40.0, -dFdy(h) * 40.0, 1.0 / heightScale));\n vec3 L = normalize(uLightDir);\n vec3 V = vec3(0.0, 0.0, 1.0); // view straight on\n vec3 H = normalize(L + V);\n float diff = max(dot(N, L), 0.0);\n // Thick impasto → high shininess (sharp glint); thin paint → broad soft sheen.\n float shininess = mix(8.0, 64.0, clamp(height * 2.0, 0.0, 1.0));\n float spec = pow(max(dot(N, H), 0.0), shininess);\n // Modulate the lit terms by how much paint sits here (no relief on bare ground).\n float relief = clamp(h * 1.5, 0.0, 1.0);\n vec3 lit = col * (1.0 + uImpasto * relief * (diff - 0.5) * 0.5 * uLightColor);\n lit += uImpasto * relief * spec * 0.6 * uLightColor;\n return lit;\n}\n\n// Best-of-9-neighbor placement: sample 3x3 surrounding cells and take the\n// stroke that covers this pixel most. Breaks the grid by per-cell jitter and\n// sparse density via noise thresholding.\n// energyGrade — the SBR energy-grade magnitude (0 = none, the oil/oil-pastel\n// baseline; 1 = the full van-Gogh Starry-Night cascade — long\n// confident strokes in the lights/coherent zones, short dabs in\n// the darks/flat zones). The van-Gogh profile carries 1.0 (the\n// energy grade is a PROFILE field, not a buried uMedium==5 branch).\nStrokeHit bestOil(vec2 p, float cellSize, float lenMul, float halfWMul,\n float jitterAmt, float density, int shapeType,\n float bristleAmp, vec2 flow, float t, float seed,\n float energyGrade) {\n vec2 cell = floor(p / cellSize);\n\n StrokeHit best = noHit();\n // 3x3 neighborhood\n for (int dy = -1; dy <= 1; dy++) {\n for (int dx = -1; dx <= 1; dx++) {\n vec2 cc = cell + vec2(float(dx), float(dy));\n vec2 hh = hash22(cc + seed) - 0.5;\n // Density gate — noise threshold; sparse placement.\n float gate = hash21(cc * 1.7 + seed * 0.3);\n if (gate > density) continue;\n\n vec2 center = (cc + 0.5 + hh * jitterAmt) * cellSize;\n\n // Per-stroke direction source — the AW.W4.1 strokeOrient switch:\n // uStrokeOrient==0 (flow) — the layer-provided hand-authored flowField.\n // uStrokeOrient==1 (tensor) — the structure-tensor minor eigenvector at the\n // cell center (the color field's edge-tangent),\n // so strokes hug the color zones (van Gogh).\n // coh carries the per-cell coherence A for the W4.3 energy grading (long\n // confident strokes where structure is coherent, stubby dabs in flat zones).\n vec2 f;\n float coh = 1.0;\n if (uStrokeOrient == 1) {\n vec3 tf = structureTensorField(center, t, safeDir(flow));\n f = safeDir(tf.xy);\n coh = tf.z;\n } else {\n f = safeDir(flow);\n }\n // Per-stroke deterministic local perturbation so alternate stroke layers stay\n // live. Tensor strokes get LESS angular jitter (the field already orients them).\n float jScale = (uStrokeOrient == 1) ? 0.4 : 1.0;\n float angJ = (hash21(cc + seed + 11.0) - 0.5) * 0.9 * jScale; // +/- 0.45/0.18 rad\n float localCurl = (fbm(center * (2.6 + seed * 0.11) + seed * 1.9) - 0.5) * 0.55 * uFlowCurl;\n vec2 dir = rotateDir(f, angJ + localCurl);\n\n // AX.W13 — the SBR energy grade is a PROFILE field (energyGrade), not a buried\n // uMedium==5 branch. The van-Gogh profile passes energyGrade=1.0 (the full\n // Starry-Night cascade); oil/oil-pastel pass 0.0 (uniform length). The grade\n // modulates stroke length by local luminance (bright → long confident strokes,\n // dark → short dabs, the Kolmogorov/Batchelor congruence) AND by coherence\n // (coherent zones → long, flat → stubby), lerped in by energyGrade so a profile\n // can dial the cascade from off (0) to full (1).\n float energy = 1.0;\n if (energyGrade > 0.001) {\n float luma = clamp(dot(sampleBase(center, t), W_LUMA), 0.0, 1.0);\n float graded = mix(0.5, 1.5, luma) * mix(0.6, 1.0, coh);\n energy = mix(1.0, graded, energyGrade);\n }\n\n float lenV = cellSize * lenMul * (0.65 + 0.55 * hash21(cc + seed + 23.0)) * energy;\n float halfW = cellSize * halfWMul * (0.70 + 0.55 * hash21(cc + seed + 41.0));\n float bulge = (hash21(cc + seed + 53.0) - 0.5) * lenV * 0.35;\n\n vec2 a = center - dir * (lenV * 0.5);\n vec2 b = center + dir * (lenV * 0.5);\n\n vec3 colMid = brokenColorJitter(\n sampleBase(center, t),\n hash21(cc + seed + 89.0),\n hash21(cc * 2.3 + seed + 97.0),\n 1.0\n );\n\n StrokeHit h = curvedStroke(p, a, b, halfW, bulge, shapeType,\n 7.0, bristleAmp,\n hash21(cc + seed + 67.0), colMid);\n if (h.coverage > best.coverage) best = h;\n }\n }\n return best;\n}\n", ve = "// ── Medium overlays ───────────────────────────────────────────────────────\n\n// A quick re-computation of base color for edge-mask sampling\nvec3 sampleBase(vec2 p, float t) {\n vec2 pw = domainWarp(p, t);\n float id; float vm;\n nucleiField(pw, t, id, vm);\n vec3 c = samplePalette(id);\n c *= 1.0 + uValueVariance * vm;\n return c;\n}\n\n// ── Structure-tensor / edge-tangent-flow (AW.W4.1) ─────────────────────────\n// The keystone of the painterly engine: derive stroke orientation from the COLOR\n// FIELD's OWN structure, not a hand-authored flow pattern. A 3x3 Sobel over\n// luma(sampleBase) yields the gradient (Gx,Gy); the 2x2 structure tensor\n// J = [[Gx², GxGy],[GxGy, Gy²]] eigen-decomposes closed-form into the MINOR\n// eigenvector (the edge-TANGENT — least color change, the stroke direction; the\n// MAJOR eigenvector is the gradient/edge-NORMAL and would make strokes cross the\n// bands) and the coherence A = (λ1-λ2)/(λ1+λ2). Low-coherence (flat) zones relax\n// toward the smooth fallbackDir by (1-A) so tensor noise never reads as jitter.\n// Single-pass small-tap WebGL2 form; the Gaussian-smoothed multi-tap + LIC smear\n// is the AW.W7 WebGPU multi-pass fold. Returns vec3(tangent.x, tangent.y, A).\nvec3 structureTensorField(vec2 p, float t, vec2 fallbackDir) {\n float e = 0.0035; // small-tap neighborhood (edge-mask scale)\n // 3x3 luma samples.\n float l00 = dot(sampleBase(p + vec2(-e, -e), t), W_LUMA);\n float l10 = dot(sampleBase(p + vec2( 0.0, -e), t), W_LUMA);\n float l20 = dot(sampleBase(p + vec2( e, -e), t), W_LUMA);\n float l01 = dot(sampleBase(p + vec2(-e, 0.0), t), W_LUMA);\n float l21 = dot(sampleBase(p + vec2( e, 0.0), t), W_LUMA);\n float l02 = dot(sampleBase(p + vec2(-e, e), t), W_LUMA);\n float l12 = dot(sampleBase(p + vec2( 0.0, e), t), W_LUMA);\n float l22 = dot(sampleBase(p + vec2( e, e), t), W_LUMA);\n // Sobel derivatives.\n float Gx = (l20 + 2.0 * l21 + l22) - (l00 + 2.0 * l01 + l02);\n float Gy = (l02 + 2.0 * l12 + l22) - (l00 + 2.0 * l10 + l20);\n // Structure tensor components.\n float Jxx = Gx * Gx;\n float Jyy = Gy * Gy;\n float Jxy = Gx * Gy;\n // Closed-form 2x2 eigenvalues: λ = 0.5(tr ± sqrt((Jxx-Jyy)² + 4Jxy²)).\n float tr = Jxx + Jyy;\n float disc = sqrt(max((Jxx - Jyy) * (Jxx - Jyy) + 4.0 * Jxy * Jxy, 0.0));\n float lambdaMaj = 0.5 * (tr + disc);\n float lambdaMin = 0.5 * (tr - disc);\n // The MAJOR eigenvector points along the gradient (the edge NORMAL); the MINOR\n // is its perpendicular — the edge TANGENT (the stroke direction). The principal\n // angle is θ = 0.5·atan2(2·Jxy, Jxx-Jyy); this form is robust at Jxy≈0 (where the\n // (λ-Jyy, Jxy) eigenvector formula collapses to a zero vector). The tangent is at\n // θ + 90°. Guard the fully-isotropic (no-gradient) degenerate case → fallback dir.\n vec2 tangent;\n if (disc < 1e-7) {\n tangent = fallbackDir;\n } else {\n float theta = 0.5 * atan(2.0 * Jxy, Jxx - Jyy); // major (gradient) angle\n tangent = vec2(-sin(theta), cos(theta)); // perpendicular = tangent\n }\n // Coherence A in [0,1] — high where one eigenvalue dominates (a strong edge),\n // ~0 in flat zones. Relax toward the smooth fallback by (1 - A).\n float A = (lambdaMaj - lambdaMin) / (lambdaMaj + lambdaMin + 1e-6);\n A = clamp(A, 0.0, 1.0);\n // Coherence-weighted blend: full tensor in coherent zones, smooth in flat ones.\n // Resolve the 180° sign ambiguity toward the fallback so the blend is continuous.\n if (dot(tangent, fallbackDir) < 0.0) tangent = -tangent;\n vec2 dir = normalize(mix(fallbackDir, tangent, A) + 1e-6);\n return vec3(dir, A);\n}\n\nvec3 mediumWatercolor(vec3 col, vec2 p, float t) {\n // Wet-edge cauliflowers via luma-gradient magnitude\n float eps = 0.004;\n vec3 cx1 = sampleBase(p + vec2(eps, 0.0), t);\n vec3 cx2 = sampleBase(p - vec2(eps, 0.0), t);\n vec3 cy1 = sampleBase(p + vec2(0.0, eps), t);\n vec3 cy2 = sampleBase(p - vec2(0.0, eps), t);\n float gx = dot(cx1 - cx2, W_LUMA);\n float gy = dot(cy1 - cy2, W_LUMA);\n float edge = sqrt(gx * gx + gy * gy) / (2.0 * eps);\n float mask = smoothstep(0.0, 2.5, edge);\n col *= mix(1.0, 0.78, mask * uWetEdge);\n\n // Granulation — pigment settles in paper tooth\n float paper = 0.5 * vnoise(p * 160.0) + 0.5 * vnoise(p * 360.0);\n float pigLoad = 1.0 - dot(col, W_LUMA);\n col *= 1.0 - uGranulation * pigLoad * (paper - 0.5);\n\n // Wash banding — faint horizontal wet gradient\n float band = fbm(vec2(p.x * 1.5, p.y * 0.4));\n col *= 1.0 + 0.04 * (band - 0.5);\n return col;\n}\n\nvec3 mediumPastel(vec3 col, vec2 p, float t) {\n vec2 flow = flowField(p, t);\n vec2 perp = vec2(-flow.y, flow.x);\n float along = dot(p, flow) * uStrokeScale;\n float across = dot(p, perp) * uStrokeScale;\n across += 0.03 * (vnoise(p * 260.0) - 0.5);\n\n float aniso = mix(1.0, 0.18, uStrokeAnisotropy);\n float stroke = fbm(vec2(along * aniso, across));\n col *= mix(1.0, 0.82 + 0.32 * stroke, uStrokeAmount);\n\n // Pastel tooth — tiny high-frequency grain\n float tooth = vnoise(p * 800.0);\n col *= 1.0 - 0.08 * uStrokeAmount * (tooth - 0.5);\n return col;\n}\n", ye = "// ── Medium id constants (mirror uniformBridge.ts MEDIUM_ID — the uMedium ladder) ──\n// The StrokeProfile selector (AX.W12 + AX.W13) dispatches on the medium id: oil,\n// van-Gogh, and oil-pastel each AUTHOR a first-class profile in profileFor.\n#define MEDIUM_OIL 3\n#define MEDIUM_CRAYON 4\n#define MEDIUM_VANGOGH 5\n#define MEDIUM_OILPASTEL 6\n\n// ── Crayon — DRY wax pigment on paper tooth (first-class medium, uMedium==4) ──\n// Crayon is not strokes. It's dry pigment crumbs dragged across paper tooth — the\n// DRY tooth-multiply model: anisotropic tooth noise multiplied into the base, a hard\n// SCUMBLE that lets the lower color show through the broken upper layer, and stable\n// OKLCh broken-color pigment patches. NO sheen, NO burnish film — that waxy gloss is\n// the OIL-PASTEL deposition's signature (mediumOilPastel), distinct from dry crayon\n// (AX.W13 slice 8 F1 — the split). Crayon shares the SUBSTRATE (the structure-tensor\n// orientation, the tooth noise, OKLCh brokenColorJitter), not the dispatch body.\nvec3 mediumCrayon(vec3 col, vec2 p, float t) {\n // Orient the deposition along the structure-tensor edge-tangent (the bridge forces\n // tensor for crayon, so read the tensor field directly).\n vec2 flow = structureTensorField(p, t, flowField(p, t)).xy;\n float ang = atan(flow.y, flow.x);\n float ca = cos(-ang), sa = sin(-ang);\n vec2 pr = vec2(p.x * ca - p.y * sa, p.x * sa + p.y * ca);\n\n float aniso = mix(0.45, 0.95, uStrokeAnisotropy);\n float scale = max(uStrokeScale * 1.6, 180.0);\n\n // ── Paper TOOTH height field — the relief the dry pigment rides. Anisotropic\n // (squished along flow, coarse cross-flow). Peaks = ridges, valleys = paper pits.\n float t1 = vnoise(vec2(pr.x * scale * aniso, pr.y * scale));\n float t2 = vnoise(vec2(pr.x * scale * aniso * 0.4, pr.y * scale * 0.4) + 11.0);\n float t3 = vnoise(vec2(pr.x * scale * aniso * 2.1, pr.y * scale * 2.1) + 23.0);\n float tooth = 0.55 * t1 + 0.30 * t2 + 0.15 * t3;\n\n // ── DRY TOOTH-MULTIPLY — the crayon multiplies the tooth relief into the base\n // color (dark in the pits, bright on the ridges). The pressure (uStrokeAmount) sets\n // how hard the multiply bites. This is the DRY model — no pigment build-up, no sheen.\n float pressure = clamp(uStrokeAmount, 0.0, 1.0);\n float bite = mix(0.18, 0.46, pressure); // multiply strength\n vec3 result = col * (1.0 - bite * (0.5 - tooth));\n\n // ── HARD SCUMBLE — a broken upper layer that lets the lower color show through the\n // gaps. A coarse mask gates a HARD (high-contrast) dragged pass so the dry crayon\n // reads as broken pigment crumbs, not a smooth wash. Drag follows the tensor flow.\n float scumbleMask = smoothstep(0.42, 0.78, vnoise(vec2(pr.x * scale * 0.55, pr.y * scale * 0.22) + 7.0));\n float scumbleCoverage = scumbleMask * pressure * 0.7; // hard broken upper layer\n vec3 paper = col * mix(0.80, 0.94, pressure); // the paper showing through\n result = mix(paper, result, scumbleCoverage);\n\n // ── Broken-color pigment: stable wax/pigment patches in OKLCh (W5), not flicker.\n vec2 pigmentCell = floor(pr * max(scale * 0.18, 32.0));\n float pigmentMask = smoothstep(0.28, 0.82, vnoise(pr * scale * 0.21 + 19.0));\n result = brokenColorJitter(\n result,\n hash21(pigmentCell + 17.0),\n hash21(pigmentCell * 2.1 + 31.0),\n 0.45 + 0.55 * pigmentMask\n );\n\n // Crayon is saturation-amplified (the wax chroma punch — but DRY, no sheen).\n result = saturate3(result, 1.08);\n\n return result;\n}\n\n// ── StrokeProfile — the oil-stroke parameter vector (AX.W12, slice 8 F6) ──────\n// The SBR \"stroke = parameter vector\" canon (facet 11): the per-mode if-ladder knobs\n// become struct FIELDS — logic-as-DATA, not an imperative branch. profileFor(medium,\n// mode) populates the profile for a (medium, mode) pair; paintStrokeLayers(profile) runs\n// the four-layer bestOil/paintOver cascade off it. A new medium (the W13 van-Gogh /\n// oil-pastel profiles) AUTHORS a profile entry — it never edits a monolith.\nstruct StrokeProfile {\n int shapeType; // 0 tapered, 1 load-drag, 2 dab, 3 even, 4 comma/crescent\n float bristleAmp; // edge raggedness 0..0.5\n float streakFreq; // internal-streak spatial frequency\n float streakAmp; // internal-streak amplitude\n float impastoAmp; // paint-thickness contribution (the relight reads the height)\n float hardness; // edge-compositing crispness 0..1\n float toothScale; // canvas-tooth (linen weave) spatial frequency\n float toothAmp; // canvas-tooth amplitude\n float pigmentSat; // pigment saturation boost (OKLCh chroma scale)\n float densityBig; // layer-1 (big gestural) placement density gate\n float densityMed; // layer-2 (medium body) placement density gate\n float densitySml; // layer-3 (small dabs) placement density gate\n float energyGrade; // AX.W13 — SBR energy-grade magnitude (0 off; 1 the van-Gogh\n // Starry-Night length cascade). The energy grade is a PROFILE\n // field, NOT a buried uMedium==5 bestOil branch (slice 8 F0).\n float impastoFloor;// AX.W13 — the per-stroke height-crown floor (0.4 oil falloff;\n // 1.0 van-Gogh FULL-height crown so each dab catches its glint).\n float densityFill; // AX.W13 — layer-4 fill-dab density. Oil/oil-pastel fill the\n // bald spots (0.95, full coverage); van-Gogh sets it LOW so the\n // sparse atomic dabs keep their visible inter-stroke ground gaps\n // (the atomicity read — a 0.95 fill would close every gap to a smear).\n float groundFloor; // AX.W13 — the bare-ground darken floor. The gaps between strokes\n // (low accumulated height) multiply toward this floor, so van-Gogh's\n // sparse atomic dabs read over a visibly darker underpainting (the\n // Starry-Night visible ground). Oil/oil-pastel keep 1.0 (no darken).\n};\n\n// The (medium, mode) -> StrokeProfile selector. The if-ladder's knobs are the profile's\n// fields; a new medium adds a CASE here (never an edit to a dispatch body). mode is the\n// uStrokeMode oil-stroke sub-mode (0 oil, 1 palette-knife, 3 brushwork). crayon\n// (uMedium==4) is a PEER tooth-multiply medium that never reaches the stroke cascade.\n// The painterly STROKE mediums are oil (MEDIUM_OIL), van-Gogh (MEDIUM_VANGOGH) and\n// oil-pastel (MEDIUM_OILPASTEL) — each AUTHORS its own profile entry below.\nStrokeProfile profileFor(int medium, int mode) {\n // The oil baseline (mode 0) — balanced modern-abstract/palette-knife hybrid.\n StrokeProfile prof = StrokeProfile(\n 0, // shapeType — tapered\n 0.25, // bristleAmp\n 9.0, // streakFreq\n 0.09, // streakAmp\n 0.9, // impastoAmp\n 0.80, // hardness\n 240.0, // toothScale\n 0.09, // toothAmp\n 1.03, // pigmentSat\n 0.65, // densityBig\n 0.78, // densityMed\n 0.90, // densitySml\n 0.0, // energyGrade — oil is uniform-length (no Starry-Night cascade)\n 0.4, // impastoFloor — oil's 0.4+0.6·edgeN crown falloff\n 0.95, // densityFill — oil fills the bald spots (full coverage, no bare canvas)\n 1.0 // groundFloor — oil keeps the full base (no ground darken; fills everywhere)\n );\n\n // ── Van-Gogh — first-class atomic comma/crescent dabs (slice 8 F0). NOT a length\n // modulation of oil: sparse high-contrast SEPARABLE marks with their own asymmetric\n // shape, the full Starry-Night energy cascade (energyGrade=1), and full-height\n // impasto crowns so each dab catches its own glint. The mode sub-modes don't apply\n // (van-Gogh has its own grammar), so this returns early.\n if (medium == MEDIUM_VANGOGH) {\n prof.shapeType = 4; // comma / crescent — the divisionist atomic dab\n prof.bristleAmp = 0.22; // a touch of raggedness on the loaded head\n prof.streakFreq = 16.0; // tight within-stroke streaks (the loaded comma)\n prof.streakAmp = 0.16;\n prof.impastoAmp = 1.5; // heavy paint per atomic dab\n prof.hardness = 0.92; // crisp separable edges (visible inter-stroke gaps)\n prof.toothScale = 220.0;\n prof.toothAmp = 0.06;\n prof.pigmentSat = 1.12; // the van-Gogh chroma punch\n // SPARSE placement — visible inter-stroke canvas gaps (the atomicity read). Lower\n // density gates than oil so each mark stays separable, not a coverage smear.\n prof.densityBig = 0.42;\n prof.densityMed = 0.52;\n prof.densitySml = 0.60;\n prof.energyGrade = 1.0; // the full Starry-Night length cascade (the profile field)\n prof.impastoFloor = 1.0; // FULL-height crowns — every atomic dab catches its glint\n prof.densityFill = 0.09; // SPARSE fill — the bald-spot layer stays mostly open so\n // the atomic dabs read as separable marks over a bare\n // ground, not a fill-closed coverage smear (the gap read).\n prof.groundFloor = 0.38; // darken the bare-ground gaps to the visible Starry-Night\n // underpainting so the bright impasto dabs read separable.\n return prof;\n }\n\n // ── Oil-pastel — broad smeared directional strokes deposited via the brush engine\n // (slice 8 F1). Creamy soft edges (low hardness), heavy build-up, and a chroma punch.\n // Distinct from the dry-crayon tooth-multiply (mediumCrayon): oil-pastel LAYS strokes.\n if (medium == MEDIUM_OILPASTEL) {\n prof.shapeType = 2; // dab / blob — broad short smeared marks\n prof.bristleAmp = 0.10; // smooth creamy edges (waxy pastel, not bristle)\n prof.streakFreq = 6.0; // soft low-frequency internal smear\n prof.streakAmp = 0.07;\n prof.impastoAmp = 0.7; // build-up, but flatter than oil impasto\n prof.hardness = 0.42; // CREAMY — soft compositing, strokes blend on overlap\n prof.toothScale = 280.0;\n prof.toothAmp = 0.12; // the pastel tooth reads stronger\n prof.pigmentSat = 1.16; // the waxy chroma punch\n prof.densityBig = 0.80; // dense — broad coverage, pigment-on-pigment build-up\n prof.densityMed = 0.90;\n prof.densitySml = 0.95;\n prof.energyGrade = 0.0; // uniform (no Starry-Night grade)\n prof.impastoFloor = 0.55; // a softer crown than van-Gogh's full-height glint\n return prof;\n }\n\n // ── Oil sub-modes (mode dispatch on the oil medium).\n if (mode == 1) { // palette knife — razor edges, heavy impasto\n prof.shapeType = 3; // flat, even\n prof.bristleAmp = 0.12;\n prof.streakFreq = 4.0; prof.streakAmp = 0.05;\n prof.impastoAmp = 1.6;\n prof.hardness = 0.95;\n prof.toothAmp = 0.04;\n prof.densityBig = 0.80; prof.densityMed = 0.88; prof.densitySml = 0.70;\n } else if (mode == 3) { // thick brushwork — heavy bristle brush\n prof.shapeType = 0; // tapered\n prof.bristleAmp = 0.32;\n prof.streakFreq = 14.0; prof.streakAmp = 0.14;\n prof.impastoAmp = 1.2;\n prof.hardness = 0.85;\n prof.toothAmp = 0.07;\n }\n return prof;\n}\n\n// The single parameterized four-layer stroke cascade (AX.W12). The four hand-unrolled\n// bestOil/paintOver invocations collapse into ONE body driven by the profile + the\n// uniform-derived per-layer scale/anisotropy multipliers (which are mode-INVARIANT — the\n// per-layer offsets, seeds, and len/wid muls are fixed across modes, so they stay here as\n// the cascade's structure; only the profile's knobs differentiate the medium). The\n// mode passes through for the two mode-special-cased shapes (knife layer-3 dabs +\n// layer-4 fill shape), which are placement details of the cascade, not profile knobs.\nvoid paintStrokeLayers(inout vec3 col, inout float height, StrokeProfile prof,\n int mode, vec2 p, float t) {\n // Scales & multipliers from uniforms (mode-invariant cascade structure).\n float baseScale = max(uStrokeScale * 0.006, 0.008);\n // Three primary layers: big gestural, medium body, small dabs.\n float sBig = baseScale * 2.4;\n float sMed = baseScale * 1.1;\n float sSml = baseScale * 0.45;\n\n float lenMulBig = mix(2.2, 3.8, uStrokeAnisotropy);\n float widMulBig = mix(0.55, 0.32, uStrokeAnisotropy);\n float lenMulMed = mix(2.0, 3.4, uStrokeAnisotropy);\n float widMulMed = mix(0.50, 0.30, uStrokeAnisotropy);\n float lenMulSml = mix(1.6, 2.6, uStrokeAnisotropy);\n float widMulSml = mix(0.45, 0.32, uStrokeAnisotropy);\n\n float jitterAmt = 0.75; // large jitter — no grid\n vec2 flow = flowField(p, t);\n\n // Layer 1 — big gestural strokes (sparse, shaping)\n StrokeHit hBig = bestOil(p, sBig, lenMulBig, widMulBig, jitterAmt * 0.55,\n prof.densityBig, prof.shapeType, prof.bristleAmp, flow, t, 1.3,\n prof.energyGrade);\n paintOver(col, height, hBig, prof.streakFreq * 0.7, prof.streakAmp,\n uImpasto * prof.impastoAmp * uStrokeAmount, prof.hardness, 1.3, prof.impastoFloor);\n\n // Layer 2 — medium body strokes\n StrokeHit hMed = bestOil(p + vec2(11.3, 3.7), sMed, lenMulMed, widMulMed,\n jitterAmt, prof.densityMed, prof.shapeType, prof.bristleAmp, flow, t, 2.7,\n prof.energyGrade);\n paintOver(col, height, hMed, prof.streakFreq, prof.streakAmp,\n uImpasto * prof.impastoAmp * uStrokeAmount, prof.hardness, 2.7, prof.impastoFloor);\n\n // Layer 3 — small dabs (more frequent, smaller)\n int smlShape = (mode == 1) ? 2 : prof.shapeType; // knife uses dabs for sparkle\n StrokeHit hSml = bestOil(p + vec2(-5.1, 8.4), sSml, lenMulSml, widMulSml,\n jitterAmt * 1.3, prof.densitySml, smlShape,\n prof.bristleAmp * 0.85, flow, t, 4.1, prof.energyGrade);\n paintOver(col, height, hSml, prof.streakFreq * 1.4, prof.streakAmp * 0.8,\n uImpasto * prof.impastoAmp * 0.65 * uStrokeAmount, prof.hardness, 4.1, prof.impastoFloor);\n\n // Layer 4 — fill dabs (very dense, very small) — covers bald spots. The density is\n // PROFILE-driven (prof.densityFill): oil/oil-pastel fill to full coverage; van-Gogh\n // keeps it sparse so the bare-ground gaps between its atomic dabs survive.\n float sFill = baseScale * 0.22;\n float lenMulFill = mix(1.4, 2.0, uStrokeAnisotropy);\n float widMulFill = mix(0.50, 0.38, uStrokeAnisotropy);\n int fillShape = (mode == 1) ? 3 : 2; // knife=even, others=dab (round fills)\n StrokeHit hFill = bestOil(p + vec2(3.9, -6.2), sFill, lenMulFill, widMulFill,\n jitterAmt * 1.5, prof.densityFill, fillShape,\n prof.bristleAmp * 0.6, flow, t, 8.9, prof.energyGrade);\n paintOver(col, height, hFill, prof.streakFreq * 1.8, prof.streakAmp * 0.6,\n uImpasto * prof.impastoAmp * 0.4 * uStrokeAmount, prof.hardness * 0.9, 8.9, prof.impastoFloor);\n\n // Optional crosshatch layer\n if (uStrokeLayers == 2) {\n vec2 flow2 = vec2(-flow.y, flow.x);\n StrokeHit hX = bestOil(p + vec2(7.3, -2.1), sMed, lenMulMed * 0.9, widMulMed,\n jitterAmt, prof.densityMed * 0.7, prof.shapeType, prof.bristleAmp, flow2, t, 6.5,\n prof.energyGrade);\n paintOver(col, height, hX, prof.streakFreq, prof.streakAmp * 0.85,\n uImpasto * prof.impastoAmp * 0.55 * uStrokeAmount, prof.hardness, 6.5, prof.impastoFloor);\n }\n}\n\n// The shared stroke-medium SUBSTRATE finish (AX.W12 + AX.W13): paint the four-layer\n// cascade off the profile, then canvas-tooth + relight + saturation. The three painterly\n// STROKE mediums (oil / van-Gogh / oil-pastel) each AUTHOR their own profile and call\n// this — the substrate is shared, the PROFILE (not a dispatch-body fork) differentiates\n// the medium (slice 8 F6). mode passes through for the cascade's mode-special-cased shapes.\nvec3 paintStrokeMedium(vec3 col, vec2 p, float t, StrokeProfile prof, int mode) {\n vec3 result = col;\n // AW.W4.2 — accumulated paint HEIGHT across the stroke layers. The relight reads\n // its gradient for the normal; the canvas tooth seeds the base relief.\n float height = 0.0;\n paintStrokeLayers(result, height, prof, mode, p, t);\n\n // Canvas tooth — linen weave\n float tooth1 = vnoise(p * prof.toothScale);\n float tooth2 = vnoise(p * prof.toothScale * vec2(0.6, 2.4) + 37.0);\n float tooth = (0.6 * tooth1 + 0.4 * tooth2) - 0.5;\n result *= 1.0 + tooth * prof.toothAmp * uCanvasGrain;\n\n // AW.W4.2 — relight the accumulated paint height with the movable uLightDir\n // source (diffuse + Blinn specular, in LINEAR before aces()). The canvas tooth\n // is the base relief term so the weave also catches the raking light.\n float canvasBase = tooth * prof.toothAmp * 0.5;\n result = relightImpasto(result, height, canvasBase);\n\n // AX.W13 — the visible ground. The bare-ground gaps (low accumulated stroke height)\n // multiply toward prof.groundFloor, so van-Gogh's sparse atomic dabs read as separable\n // marks over a darker underpainting (oil/oil-pastel keep groundFloor=1.0 → no-op). The\n // smoothstep keeps the impasto dabs (high height) at full value; only the open ground\n // between them darkens.\n result *= mix(prof.groundFloor, 1.0, smoothstep(0.0, 0.38, height));\n\n // Pigment saturation boost\n result = saturate3(result, prof.pigmentSat);\n\n return result;\n}\n\nvec3 mediumOil(vec3 col, vec2 p, float t) {\n // The oil medium is a thin body: fetch the oil profile (mode-dispatched), paint via\n // the shared stroke substrate. uStrokeMode: 0 oil (gestural), 1 knife, 3 brushwork.\n int mode = uStrokeMode;\n StrokeProfile prof = profileFor(MEDIUM_OIL, mode);\n return paintStrokeMedium(col, p, t, prof, mode);\n}\n\n// AX.W13 — the van-Gogh atomic-stroke medium is now a FIRST-CLASS body, NOT a\n// mediumOil passthrough (slice 8 F0). It composes its OWN vangogh StrokeProfile (the\n// comma/crescent shapeType, sparse high-contrast atomic placement with visible\n// inter-stroke gaps, the full Starry-Night energy-grade cascade as a PROFILE field,\n// and full-height impasto crowns) through the shared stroke substrate. The bridge forces\n// the ETF (tensor) orientation so the swirl-rows queue along the color field's tangent;\n// the within-stroke OKLCh broken color (paintOver) + the OKLab stroke OVER-composite\n// give pigment-true atom-level shimmer. No subject matter — the \"source image\" is the\n// generated nuclei field, so dabs trace its iso-bands (the Starry-Night swirl-rows).\nvec3 mediumVangogh(vec3 col, vec2 p, float t) {\n StrokeProfile prof = profileFor(MEDIUM_VANGOGH, 0);\n return paintStrokeMedium(col, p, t, prof, 0);\n}\n\n// AX.W13 — the oil-pastel medium (uMedium==6) is a DISTINCT stroke-deposition body,\n// split out of the dry-crayon tooth-multiply (mediumCrayon) (slice 8 F1). It DEPOSITS\n// broad smeared directional strokes via the same brush engine with a creamy soft\n// hardness, heavy pigment build-up where strokes overlap, and a chroma punch — the\n// stroke-deposition model that gives oil-pastel its depth and less-uniform read.\n// Shares the SUBSTRATE (the stroke cascade + tooth + relight) with oil/van-Gogh, NOT\n// the dispatch body of dry crayon. The bridge forces tensor orientation.\nvec3 mediumOilPastel(vec3 col, vec2 p, float t) {\n StrokeProfile prof = profileFor(MEDIUM_OILPASTEL, 0);\n return paintStrokeMedium(col, p, t, prof, 0);\n}", j = "\n", be = `#version 300 es
10
11
  precision highp float;
11
12
 
12
13
  in vec2 vUv;
@@ -53,24 +54,40 @@ uniform int uMedium;
53
54
  // consulted when the hue-arc path is requested; OKLab-rectangular ramps (the
54
55
  // default for adjacent-hue stops) ignore it.
55
56
  uniform int uHuePath;
56
- uniform int uFlowPattern; // 0 none, 1 radial, 2 swirl, 3 diagonal, 4 multi
57
+ uniform int uFlowPattern; // 0 none, 1 radial, 2 swirl, 3 diagonal, 4 multi, 5 tensor/ETF (W4.1)
57
58
  uniform vec2 uFlowFocal;
58
59
  uniform float uFlowAngle;
59
60
  uniform float uFlowCurl;
60
61
  uniform vec2 uCursor; // in 0..1 screen space (matches pN)
61
62
  uniform float uCursorStrength; // 0..1 attraction amount
62
63
  uniform float uCursorRadius; // radius of influence (0.05..0.5)
64
+ // AW.W8.1 — the velocity-reactive flow: a fast flick injects a transient swirl-burst
65
+ // (uCursorBurst, decaying over ~1s) along the pointer velocity (uCursorVelocity),
66
+ // distinct from the steady uCursorStrength attraction. Both are PRM-gated (the cursor
67
+ // write-path early-outs on reduced-motion, and the master tempo scalar zeroes them).
68
+ uniform vec2 uCursorVelocity; // smoothed pointer velocity (normalized-screen/move)
69
+ uniform float uCursorBurst; // 0..1 transient flick burst
63
70
  uniform float uStrokeAmount;
64
71
  uniform float uStrokeScale;
65
72
  uniform float uStrokeAnisotropy;
66
73
  uniform int uStrokeLayers; // 1 or 2 (crosshatch)
67
74
  uniform int uStrokeMode; // 0 oil (modern gestural), 1 palette-knife, 3 modern-chunky (crayon is uMedium==4, a peer medium)
75
+ // AW.W4.1 — the stroke-orientation source: 0 flow (hand-authored flowField), 1
76
+ // tensor (the structure-tensor minor eigenvector — the color field's edge-tangent).
77
+ uniform int uStrokeOrient;
68
78
  uniform float uWetEdge;
69
79
  uniform float uGranulation;
70
80
  uniform float uImpasto;
71
81
  uniform float uBrokenColor;
72
82
  uniform float uCanvasGrain;
73
83
 
84
+ // AW.W4.2 — the impasto relight axis. A movable directional source lighting the
85
+ // accumulated paint-height field (diffuse + Blinn specular, in LINEAR before aces()).
86
+ // AW.W8 drives uLightDir from the cursor (cursor-as-light). Default = upper-left so
87
+ // the still default reads identically to the prior fixed rim.
88
+ uniform vec3 uLightDir; // unit direction (x,y in screen space, z toward viewer)
89
+ uniform vec3 uLightColor; // warm-white tint (linear)
90
+
74
91
  // Motion
75
92
  uniform float uNucleiDrift;
76
93
  uniform float uPaletteDrift;
@@ -125,7 +142,7 @@ float vnoise(vec2 p) {
125
142
  // The rotated-octave FBM rotation constant — spliced from the shared chunk (AV.W2
126
143
  // — the one FBM_ROT). The loop below stays aurora-local (2.02 lacunarity +
127
144
  // uniform-driven octaves, per §3a — only the rotation constant converges).
128
- ${t}
145
+ ${m}
129
146
 
130
147
  float fbm(vec2 p) {
131
148
  float v = 0.0;
@@ -139,69 +156,41 @@ float fbm(vec2 p) {
139
156
  return v;
140
157
  }
141
158
 
159
+ // AX.W12 — the painterly-medium organic noise basis (Jarzynski PCG2D integer-bit hash
160
+ // + 2D simplex gradient noise), SPLICED from the shared procedural-color chunk — the
161
+ // SAME single source the WebGPU twin (aurora.wgsl.ts) splices, so the hash can NEVER
162
+ // drift between backends (proof:aurora-noise-hash-equivalence locks the twins to 1e-6).
163
+ // The painterly mediums (mediumOil's tooth/granulation) opt into pcgHash2/gnoise for
164
+ // organic paper/pigment grain; the smooth/atmospheric pole KEEPS its cheap value-noise
165
+ // fbm above (the cost-tiering is preserved per AX.W12 §3a — this is an ADDITIVE basis,
166
+ // not a blanket upgrade). The aurora-local hash21/vnoise/fbm loop stays (legitimately
167
+ // divergent per the chunk's §3a).
168
+ ${o}
169
+
142
170
  // The sRGB OETF (linearToSrgb) — spliced from the shared procedural-color chunk
143
171
  // (AV.W2 — the single OETF source; the AV.W1 local copy is deleted here). Aurora's
144
172
  // linear pipeline closes the seam with this transfer before fragColor (the
145
173
  // proof:aurora-space-gamma seam).
146
- ${i}
174
+ ${c}
147
175
 
148
176
  // W5 — the four Ottosson OKLab/OKLCh matrices + their space fns, spliced from the
149
177
  // SAME shared chunk the goo-blob uses (zero new payload, 1e-6-verified). PI is
150
178
  // defined first because the chunk's oklabToOklch folds the hue into [0, 2pi).
151
179
  const float PI = 3.141592653589793;
152
- ${o}
180
+ ${r}
153
181
 
154
- // ── OKLCh palette interpolation (W5) ─────────────────────────────────────────
182
+ // ── OKLCh palette ramp (W5 interp · AX.W11 single-sourced twin) ───────────────
155
183
  // The muddy-midtone kill: distant-hue stops interpolated by a plain linear mix()
156
184
  // desaturate toward grey at the midpoint. The OKLab-rectangular lerp (L,a,b) holds
157
- // chroma across the ramp; the OKLCh hue-arc form (used only for deliberate rainbow
158
- // travel via uHuePath increasing/decreasing) sweeps the hue wheel without flipping.
159
-
160
- // value.js's interpolateHue, transcribed in the NORMALIZED-TURNS domain (h_rad/TAU
161
- // → turns, the exact .5/+1.0 branch per method, fract() wrap, *TAU back). A
162
- // radians-native port (PI thresholds / +TAU wrap) diverges 180° at the antipode.
163
- // methods: 0 shorter, 1 longer, 2 increasing, 3 decreasing.
164
- float interpolateHueTurns(float h0, float h1, float t, int method) {
165
- // h0/h1 are radians in [0, 2pi); convert to turns [0,1).
166
- float TAU = 2.0 * PI;
167
- float a = h0 / TAU;
168
- float b = h1 / TAU;
169
- float i = b - a;
170
- if (method == 0) { // shorter
171
- if (i > 0.5) a += 1.0; else if (i < -0.5) b += 1.0;
172
- } else if (method == 1) { // longer
173
- if (i > 0.0 && i < 0.5) a += 1.0; else if (i > -0.5 && i <= 0.0) b += 1.0;
174
- } else if (method == 2) { // increasing
175
- if (i < 0.0) b += 1.0;
176
- } else { // decreasing
177
- if (i > 0.0) a += 1.0;
178
- }
179
- float r = a + t * (b - a);
180
- r = fract(r); // (r % 1 + 1) % 1 — fract is non-negative in GLSL
181
- return r * TAU; // back to radians
182
- }
183
-
184
- // Linear-sRGB endpoint pair → OKLab-rectangular interpolation → linear sRGB. Holds
185
- // chroma across the ramp (no grey midpoint). Used for the default/shorter ramp path.
186
- vec3 mixPaletteOklab(vec3 linA, vec3 linB, float t) {
187
- vec3 labA = LMS_TO_OKLAB * (sign(LINEAR_SRGB_TO_LMS * linA) * pow(abs(LINEAR_SRGB_TO_LMS * linA), vec3(1.0 / 3.0)));
188
- vec3 labB = LMS_TO_OKLAB * (sign(LINEAR_SRGB_TO_LMS * linB) * pow(abs(LINEAR_SRGB_TO_LMS * linB), vec3(1.0 / 3.0)));
189
- vec3 lab = mix(labA, labB, t);
190
- return oklabToLinearSrgb(lab);
191
- }
192
-
193
- // Linear-sRGB endpoint pair → OKLCh interpolation along the uHuePath arc → linear
194
- // sRGB. L and C lerp; H walks the chosen arc. The deliberate-rainbow path.
195
- vec3 mixPaletteOklchArc(vec3 linA, vec3 linB, float t, int method) {
196
- vec3 labA = LMS_TO_OKLAB * (sign(LINEAR_SRGB_TO_LMS * linA) * pow(abs(LINEAR_SRGB_TO_LMS * linA), vec3(1.0 / 3.0)));
197
- vec3 labB = LMS_TO_OKLAB * (sign(LINEAR_SRGB_TO_LMS * linB) * pow(abs(LINEAR_SRGB_TO_LMS * linB), vec3(1.0 / 3.0)));
198
- vec3 lchA = oklabToOklch(labA);
199
- vec3 lchB = oklabToOklch(labB);
200
- float L = mix(lchA.x, lchB.x, t);
201
- float C = mix(lchA.y, lchB.y, t);
202
- float H = interpolateHueTurns(lchA.z, lchB.z, t, method);
203
- return oklabToLinearSrgb(oklchToOklab(vec3(L, C, H)));
204
- }
185
+ // chroma across the ramp; the OKLCh hue-arc form (deliberate rainbow travel via
186
+ // uHuePath increasing/decreasing) sweeps the hue wheel without flipping.
187
+ //
188
+ // AX.W11 — the ramp (interpolateHueTurns + mixPaletteOklab + mixPaletteOklchArc +
189
+ // the smoothstep-eased samplePaletteRamp dispatcher) is SPLICED from the shared
190
+ // procedural-color chunk, the SAME single source the WebGPU twin (aurora.wgsl.ts)
191
+ // splices — so the ramp can NEVER drift between backends (the AV.W1 two-copy class,
192
+ // applied to the ramp). composition.glsl.ts's samplePalette calls samplePaletteRamp.
193
+ ${f}
205
194
 
206
195
  // Interleaved Gradient Noise (Jimenez) — a 1-LSB triangular dither applied in
207
196
  // DISPLAY space AFTER the OETF, so 8-bit mid-tone banding on the soft gradient is
@@ -270,14 +259,63 @@ vec2 domainWarp(vec2 p, float t) {
270
259
  return warped;
271
260
  }
272
261
 
273
- ` + ie + N + ae + N + "// ── Color utils (OKLCh — W5; the sRGB YIQ hueShift matrix is DELETED) ──────────\nconst vec3 W_LUMA = vec3(0.2126, 0.7152, 0.0722);\n\n// Linear sRGB → OKLab (the cbrt-LMS path; the goo-blob's srgbToOklab takes gamma,\n// here the color field is already linear so we skip the OETF inverse).\nvec3 linOklab(vec3 lin) {\n vec3 lms = LINEAR_SRGB_TO_LMS * lin;\n return LMS_TO_OKLAB * (sign(lms) * pow(abs(lms), vec3(1.0 / 3.0)));\n}\n\n// Broken color (W5) — hue + chroma jitter at fixed PERCEPTUAL lightness, in OKLCh.\n// Broken color is hue variation at constant VALUE, which only OKLCh makes true (the\n// prior YIQ-style sRGB rotation muddied value). This is the seam AW.W4's van-Gogh /\n// oil-pastel per-stroke pigment jitter consumes (the pigment-variation read).\nvec3 brokenColorJitter(vec3 c, float hueSeed, float valueSeed, float strength) {\n float amt = clamp(uBrokenColor * strength, 0.0, 1.0);\n if (amt <= 0.001) return c;\n vec3 lch = oklabToOklch(linOklab(c));\n // ±~16° hue swing + ±~12% chroma swing; L held so value reads constant.\n lch.z += (hueSeed - 0.5) * (32.0 * PI / 180.0) * amt;\n lch.y = max(lch.y * (1.0 + (valueSeed - 0.5) * 0.28 * amt), 0.0);\n return max(oklabToLinearSrgb(oklchToOklab(lch)), vec3(0.0));\n}\n\n// Saturation (W5) — chroma scale at fixed L/H in OKLCh (the perceptual saturation,\n// replacing the luma-mix sRGB form). amt=1 is identity; amt<1 desaturates toward\n// the achromatic axis with NO lightness shift (the muddy-luma-mix grey is gone).\nvec3 saturate3(vec3 c, float amt) {\n vec3 lch = oklabToOklch(linOklab(c));\n lch.y = max(lch.y * amt, 0.0);\n return max(oklabToLinearSrgb(oklchToOklab(lch)), vec3(0.0));\n}\n\n" + oe + N + ce + N + se + N + M + N + "\n// ── Main ──────────────────────────────────────────────────────────────────\nvoid main() {\n // Normalized 0..1 coordinates for nuclei, domain warp, and medium sampling.\n vec2 uv = vUv;\n vec2 pN = uv;\n\n float t = uTime;\n\n // Warp in pN space\n vec2 p_warp = domainWarp(pN, t);\n\n // Composition\n float paletteId; float valueMod;\n nucleiField(p_warp, t, paletteId, valueMod);\n vec3 col = samplePalette(paletteId);\n col *= 1.0 + uValueVariance * valueMod;\n\n // Breath — slow global luminance wobble\n float breath = sin(t * 6.2831 / max(uBreathPeriod, 1.0));\n col *= 1.0 + uBreathDepth * breath * 0.5;\n\n // Medium — crayon (4) is a peer dispatched here, not a mediumOil sub-mode.\n if (uMedium == 1) col = mediumPastel(col, pN, t);\n else if (uMedium == 2) col = mediumWatercolor(col, pN, t);\n else if (uMedium == 3) col = mediumOil(col, pN, t);\n else if (uMedium == 4) col = mediumCrayon(col, pN, t);\n\n // Saturation trim\n col = saturate3(col, uSaturation);\n\n // Tonemap + film grain\n col = aces(col);\n float grain = hash21(gl_FragCoord.xy + t * 17.0);\n col += (grain - 0.5) * uPaperGrain;\n\n col = clamp(col * 0.985 + 0.008, 0.0, 1.0);\n\n // MANDATORY OETF — closes the seam (mirrors metaball.frag.ts:278). The whole\n // pipeline above (linear palette LUT, ACES tonemap, grain) runs in LINEAR; without\n // this transfer the aurora ships ~2.2× too dark (linear 0.5 → ~0.215, not ~0.735).\n col = linearToSrgb(col);\n\n // 1-LSB triangular IGN dither in DISPLAY space, AFTER the OETF — breaks 8-bit\n // mid-tone banding on the soft gradient. Texture-free (the canonical Jimenez IGN).\n col += (1.0 / 255.0) * (ign(gl_FragCoord.xy) - 0.5);\n\n // Premultiply operates on DISPLAY-space color (matches the blob: OETF, then premul).\n fragColor = vec4(col * uAlpha, uAlpha);\n}\n", F = "[Aurora]", I = /* @__PURE__ */ "uTime.uPalette.uStopCount.uNucleiCount.uNucleiPos.uNucleiRadius.uNucleiPaletteBias.uNucleiValueBias.uNucleiDriftRadius.uNucleiDriftPhase.uNucleiElong.uNucleiAngle.uSoftmaxBeta.uValueVariance.uWarpAmount.uWarpScale.uWarpDrift.uWarpMode.uNoiseOctaves.uMedium.uHuePath.uFlowPattern.uFlowFocal.uFlowAngle.uFlowCurl.uCursor.uCursorStrength.uCursorRadius.uStrokeAmount.uStrokeScale.uStrokeAnisotropy.uStrokeLayers.uStrokeMode.uWetEdge.uGranulation.uImpasto.uBrokenColor.uCanvasGrain.uNucleiDrift.uPaletteDrift.uBreathDepth.uBreathPeriod.uSaturation.uPaperGrain.uAlpha".split(".");
274
- function L(e, t, n) {
275
- let i = s(e, e.VERTEX_SHADER, t, F), a = s(e, e.FRAGMENT_SHADER, n, F), o = r(e, i, a, F);
276
- e.useProgram(o);
277
- let c = e.createVertexArray();
278
- e.bindVertexArray(c);
279
- let l = e.createBuffer();
280
- e.bindBuffer(e.ARRAY_BUFFER, l), e.bufferData(e.ARRAY_BUFFER, new Float32Array([
262
+ ` + me + j + he + j + "// ── Color utils (OKLCh — W5; the sRGB YIQ hueShift matrix is DELETED) ──────────\nconst vec3 W_LUMA = vec3(0.2126, 0.7152, 0.0722);\n\n// Linear sRGB → OKLab (the cbrt-LMS path; the goo-blob's srgbToOklab takes gamma,\n// here the color field is already linear so we skip the OETF inverse).\nvec3 linOklab(vec3 lin) {\n vec3 lms = LINEAR_SRGB_TO_LMS * lin;\n return LMS_TO_OKLAB * (sign(lms) * pow(abs(lms), vec3(1.0 / 3.0)));\n}\n\n// Broken color (W5) — hue + chroma jitter at fixed PERCEPTUAL lightness, in OKLCh.\n// Broken color is hue variation at constant VALUE, which only OKLCh makes true (the\n// prior YIQ-style sRGB rotation muddied value). This is the seam AW.W4's van-Gogh /\n// oil-pastel per-stroke pigment jitter consumes (the pigment-variation read).\nvec3 brokenColorJitter(vec3 c, float hueSeed, float valueSeed, float strength) {\n float amt = clamp(uBrokenColor * strength, 0.0, 1.0);\n if (amt <= 0.001) return c;\n vec3 lch = oklabToOklch(linOklab(c));\n // ±~16° hue swing + ±~12% chroma swing; L held so value reads constant.\n lch.z += (hueSeed - 0.5) * (32.0 * PI / 180.0) * amt;\n lch.y = max(lch.y * (1.0 + (valueSeed - 0.5) * 0.28 * amt), 0.0);\n return max(oklabToLinearSrgb(oklchToOklab(lch)), vec3(0.0));\n}\n\n// Saturation (W5) — chroma scale at fixed L/H in OKLCh (the perceptual saturation,\n// replacing the luma-mix sRGB form). amt=1 is identity; amt<1 desaturates toward\n// the achromatic axis with NO lightness shift (the muddy-luma-mix grey is gone).\nvec3 saturate3(vec3 c, float amt) {\n vec3 lch = oklabToOklch(linOklab(c));\n lch.y = max(lch.y * amt, 0.0);\n return max(oklabToLinearSrgb(oklchToOklab(lch)), vec3(0.0));\n}\n\n" + ge + j + ve + j + _e + j + ye + j + "\n// ── Main ──────────────────────────────────────────────────────────────────\nvoid main() {\n // Normalized 0..1 coordinates for nuclei, domain warp, and medium sampling.\n vec2 uv = vUv;\n vec2 pN = uv;\n\n float t = uTime;\n\n // Warp in pN space\n vec2 p_warp = domainWarp(pN, t);\n\n // Composition\n float paletteId; float valueMod;\n nucleiField(p_warp, t, paletteId, valueMod);\n vec3 col = samplePalette(paletteId);\n col *= 1.0 + uValueVariance * valueMod;\n\n // Breath — slow global luminance wobble\n float breath = sin(t * 6.2831 / max(uBreathPeriod, 1.0));\n col *= 1.0 + uBreathDepth * breath * 0.5;\n\n // Medium dispatch (AX.W13 — each is a FIRST-CLASS body, no shared dispatch):\n // 1 pastel · 2 watercolor · 3 oil · 4 crayon (DRY tooth-multiply) ·\n // 5 van-Gogh (atomic comma/crescent dabs) · 6 oil-pastel (stroke deposition).\n // van-Gogh is no longer a mediumOil passthrough; oil-pastel and crayon no longer\n // share a body — they share the SUBSTRATE (the tooth/placement helpers), not the\n // dispatch body (slice 8 F0/F1).\n if (uMedium == 1) col = mediumPastel(col, pN, t);\n else if (uMedium == 2) col = mediumWatercolor(col, pN, t);\n else if (uMedium == 3) col = mediumOil(col, pN, t);\n else if (uMedium == 4) col = mediumCrayon(col, pN, t);\n else if (uMedium == 5) col = mediumVangogh(col, pN, t);\n else if (uMedium == 6) col = mediumOilPastel(col, pN, t);\n\n // Saturation trim\n col = saturate3(col, uSaturation);\n\n // Tonemap + film grain\n col = aces(col);\n float grain = hash21(gl_FragCoord.xy + t * 17.0);\n col += (grain - 0.5) * uPaperGrain;\n\n col = clamp(col * 0.985 + 0.008, 0.0, 1.0);\n\n // MANDATORY OETF — closes the seam (mirrors metaball.frag.ts:278). The whole\n // pipeline above (linear palette LUT, ACES tonemap, grain) runs in LINEAR; without\n // this transfer the aurora ships ~2.2× too dark (linear 0.5 → ~0.215, not ~0.735).\n col = linearToSrgb(col);\n\n // 1-LSB triangular IGN dither in DISPLAY space, AFTER the OETF — breaks 8-bit\n // mid-tone banding on the soft gradient. Texture-free (the canonical Jimenez IGN).\n col += (1.0 / 255.0) * (ign(gl_FragCoord.xy) - 0.5);\n\n // Premultiply operates on DISPLAY-space color (matches the blob: OETF, then premul).\n fragColor = vec4(col * uAlpha, uAlpha);\n}\n";
263
+ //#endregion
264
+ //#region src/composables/glass/createGPUCanvas.ts
265
+ function xe(e, t) {
266
+ let { device: n, setup: r } = t, i = typeof navigator < "u" && navigator.gpu ? navigator.gpu.getPreferredCanvasFormat() : "bgra8unorm", a = null, o = null, c = n;
267
+ function l() {
268
+ let s = e.getContext("webgpu");
269
+ if (!s) throw Error("[createGPUCanvas] WebGPU canvas context unavailable");
270
+ return a = s, a.configure({
271
+ device: n,
272
+ format: i,
273
+ alphaMode: t.alphaMode ?? "premultiplied"
274
+ }), o = r({
275
+ device: n,
276
+ context: a,
277
+ format: i
278
+ }), {
279
+ frame: o.frame,
280
+ shouldContinue: o.shouldContinue,
281
+ time: o.time,
282
+ teardown: () => {
283
+ o?.teardown?.(), a?.unconfigure(), a = null, o = null, c = null;
284
+ }
285
+ };
286
+ }
287
+ let u = s({
288
+ canvas: e,
289
+ mode: t.mode,
290
+ respectReducedMotion: t.respectReducedMotion,
291
+ buildContext: l,
292
+ resize: () => o?.resize()
293
+ });
294
+ return {
295
+ arm: u.arm,
296
+ suspend: u.suspend,
297
+ resume: u.resume,
298
+ wake: u.wake,
299
+ renderAt: u.renderAt,
300
+ dispose: u.dispose,
301
+ get device() {
302
+ return c;
303
+ },
304
+ get reducedMotion() {
305
+ return u.reducedMotion;
306
+ }
307
+ };
308
+ }
309
+ //#endregion
310
+ //#region src/components/custom/aurora/composables/glSetup.ts
311
+ var M = "[Aurora]", Se = /* @__PURE__ */ "uTime.uPalette.uStopCount.uNucleiCount.uNucleiPos.uNucleiRadius.uNucleiPaletteBias.uNucleiValueBias.uNucleiDriftRadius.uNucleiDriftPhase.uNucleiElong.uNucleiAngle.uSoftmaxBeta.uValueVariance.uWarpAmount.uWarpScale.uWarpDrift.uWarpMode.uNoiseOctaves.uMedium.uHuePath.uFlowPattern.uFlowFocal.uFlowAngle.uFlowCurl.uCursor.uCursorStrength.uCursorRadius.uCursorVelocity.uCursorBurst.uStrokeAmount.uStrokeScale.uStrokeAnisotropy.uStrokeLayers.uStrokeMode.uStrokeOrient.uLightDir.uLightColor.uWetEdge.uGranulation.uImpasto.uBrokenColor.uCanvasGrain.uNucleiDrift.uPaletteDrift.uBreathDepth.uBreathPeriod.uSaturation.uPaperGrain.uAlpha".split(".");
312
+ function Ce(e, t, n) {
313
+ let r = h(e, e.VERTEX_SHADER, t, M), i = h(e, e.FRAGMENT_SHADER, n, M), a = u(e, r, i, M);
314
+ e.useProgram(a);
315
+ let o = e.createVertexArray();
316
+ e.bindVertexArray(o);
317
+ let s = e.createBuffer();
318
+ e.bindBuffer(e.ARRAY_BUFFER, s), e.bufferData(e.ARRAY_BUFFER, new Float32Array([
281
319
  -1,
282
320
  -1,
283
321
  3,
@@ -285,231 +323,150 @@ function L(e, t, n) {
285
323
  -1,
286
324
  3
287
325
  ]), e.STATIC_DRAW);
288
- let u = e.getAttribLocation(o, "aPos");
289
- e.enableVertexAttribArray(u), e.vertexAttribPointer(u, 2, e.FLOAT, !1, 0, 0);
290
- let d = {};
291
- for (let t of I) d[t] = e.getUniformLocation(o, t);
326
+ let c = e.getAttribLocation(a, "aPos");
327
+ e.enableVertexAttribArray(c), e.vertexAttribPointer(c, 2, e.FLOAT, !1, 0, 0);
328
+ let l = {};
329
+ for (let t of Se) l[t] = e.getUniformLocation(a, t);
292
330
  return {
293
- program: o,
294
- vs: i,
295
- fs: a,
296
- uniforms: d,
331
+ program: a,
332
+ vs: r,
333
+ fs: i,
334
+ uniforms: l,
297
335
  geometry: {
298
- vao: c,
299
- buf: l
336
+ vao: o,
337
+ buf: s
300
338
  }
301
339
  };
302
340
  }
303
341
  //#endregion
304
342
  //#region src/components/custom/aurora/composables/color.ts
305
- function R(e, t = 8, n) {
343
+ function N(e, t = 8, n) {
306
344
  let r = n ?? new Float32Array(t * 3), i = Math.min(e.length, t);
307
345
  for (let t = 0; t < i; t++) {
308
- let [n, i, a] = d(e[t]);
346
+ let [n, i, a] = b(e[t]);
309
347
  r[t * 3 + 0] = n, r[t * 3 + 1] = i, r[t * 3 + 2] = a;
310
348
  }
311
349
  for (let e = i; e < t; e++) r[e * 3 + 0] = 0, r[e * 3 + 1] = 0, r[e * 3 + 2] = 0;
312
350
  return r;
313
351
  }
314
- function z(e) {
352
+ function P(e) {
315
353
  if (e.length === 0) return "transparent";
316
- if (e.length === 1) return f(e[0]);
354
+ if (e.length === 1) return S(e[0]);
317
355
  let t = e.length;
318
356
  return `linear-gradient(135deg, ${e.map((e, n) => {
319
357
  let r = Math.round(n / (t - 1) * 100);
320
- return `${f(e)} ${r}%`;
358
+ return `${S(e)} ${r}%`;
321
359
  }).join(", ")})`;
322
360
  }
323
- function B(e) {
324
- let t = e.replace("#", ""), n = parseInt(t.slice(0, 2), 16), r = parseInt(t.slice(2, 4), 16), i = parseInt(t.slice(4, 6), 16), [a, o, s] = ne(n / 255, r / 255, i / 255), [c, l, u] = A(a, o, s);
361
+ function we(e) {
362
+ let t = e.replace("#", ""), n = parseInt(t.slice(0, 2), 16), r = parseInt(t.slice(2, 4), 16), i = parseInt(t.slice(4, 6), 16), [a, o, s] = fe(n / 255, r / 255, i / 255), [c, l, u] = de(a, o, s);
325
363
  return {
326
364
  L: c,
327
365
  C: l,
328
366
  h: u
329
367
  };
330
368
  }
331
- var V = {
332
- complementary: "longer",
333
- "split-complementary": "longer",
334
- analogous: "shorter",
335
- monochrome: "shorter",
336
- triad: "increasing",
337
- tetradic: "increasing"
338
- }, H = [.35, .95];
339
- function U(e, t = {}) {
340
- let n = typeof e == "string" ? p(e) : e, { stopCount: r = 4, harmony: i = "analogous", lightnessSpread: a = .32, chromaFalloff: o = .85, hueSpread: s = 28, lightnessEasing: c = "linear", chromaEasing: l = "bell", temperatureShift: u = 0 } = t, d = Math.max(2, Math.min(8, Math.round(r))), [f, m] = H, h = n.L - a / 2, g = n.L + a / 2;
341
- h < f && (g += f - h, h = f), g > m && (h -= g - m, g = m), h = Math.max(f, h), g = Math.min(m, g);
342
- let _ = [];
369
+ var Te = [.35, .95];
370
+ function F(e, t = {}) {
371
+ let n = typeof e == "string" ? C(e) : e, { stopCount: r = 4, harmony: i = "analogous", lightnessSpread: a = .32, chromaFalloff: o = .85, hueSpread: s = 28, lightnessEasing: c = "linear", chromaEasing: l = "bell", temperatureShift: u = 0 } = t, d = Math.max(2, Math.min(8, Math.round(r))), [f, p] = Te, m = n.L - a / 2, h = n.L + a / 2;
372
+ m < f && (h += f - m, m = f), h > p && (m -= h - p, h = p), m = Math.max(f, m), h = Math.min(p, h);
373
+ let g = [];
343
374
  for (let e = 0; e < d; e++) {
344
- let t = d === 1 ? 0 : e / (d - 1), r = h + (g - h) * W(t, c), a = n.C * o, f;
345
- if (l === "bell") f = a + (n.C - a) * G(t);
375
+ let t = d === 1 ? 0 : e / (d - 1), r = m + (h - m) * I(t, c), a = n.C * o, f;
376
+ if (l === "bell") f = a + (n.C - a) * Ee(t);
346
377
  else {
347
- let e = l === "sine" ? W(t, "sine") : t;
378
+ let e = l === "sine" ? I(t, "sine") : t;
348
379
  f = n.C - (n.C - a) * e;
349
380
  }
350
- let p = ue(n.h, i, s, t);
351
- u > 0 && (p = le(p, t, u)), _.push(de({
381
+ let p = te(n.h, i, s, t);
382
+ u > 0 && (p = Ae(p, t, u)), g.push(ee({
352
383
  L: r,
353
384
  C: f,
354
385
  h: p
355
386
  }));
356
387
  }
357
- return _;
388
+ return g;
358
389
  }
359
- function W(e, t) {
390
+ function I(e, t) {
360
391
  return t === "sine" ? .5 - .5 * Math.cos(Math.PI * e) : e;
361
392
  }
362
- function G(e) {
393
+ function Ee(e) {
363
394
  return Math.sin(Math.PI * e);
364
395
  }
365
- function le(e, t, n) {
366
- return ((e) => (e % 360 + 360) % 360)(e + (t - .5) * 2 * n * 22);
367
- }
368
- function ue(e, t, n, r) {
369
- let i = (e) => (e % 360 + 360) % 360, a = V[t], o = (e, t) => i(D(i(e) / 360, i(t) / 360, r, a) * 360);
370
- switch (t) {
371
- case "monochrome": return i(e);
372
- case "complementary": return o(e, e + 180);
373
- case "split-complementary": return o(e + 150, e + 210);
374
- case "triad": return o(e, e + 240);
375
- case "tetradic": return o(e, e + 270);
376
- default: return o(e - n, e + n);
377
- }
378
- }
379
- function de(e) {
380
- let [t, n, r] = j(e.L, e.C, e.h), [i, a, o] = E(t, n, r), [s, c, l] = A(i, a, o), u = c;
381
- for (let e = 0; e < 6; e++) {
382
- let [e, t, n] = j(s, u, l), [r, i, a] = k(e, t, n);
383
- if (O(r, i, a)) break;
384
- u *= .999;
385
- }
386
- return {
387
- L: s,
388
- C: u,
389
- h: l
390
- };
391
- }
392
- function fe(e) {
393
- let t = [.33, .67], n = [
394
- [t[0], t[0]],
395
- [t[1], t[1]],
396
- [t[1], t[0]],
397
- [t[0], t[1]],
398
- [.5, .5],
399
- [.5, t[0]]
400
- ], r = Math.max(1, Math.min(n.length, e));
401
- return Array.from({ length: r }, (e, t) => {
402
- let [i, a] = n[t];
403
- return {
404
- x: i,
405
- y: a,
406
- radius: .5,
407
- paletteBias: r === 1 ? 0 : t / (r - 1),
408
- valueBias: (t % 2 == 0 ? 1 : -1) * .05,
409
- driftRadius: .045,
410
- driftPhase: t * 2.4 % (Math.PI * 2)
411
- };
412
- });
413
- }
414
- var pe = {
415
- atmospheric: {
416
- harmony: "analogous",
417
- medium: "smooth",
418
- temperatureShift: .3,
419
- chromaEasing: "bell",
420
- saturation: .95,
421
- warpAmount: .55,
422
- valueVariance: .08,
423
- breathDepth: .06,
424
- zones: 2
425
- },
426
- painterly: {
427
- harmony: "split-complementary",
428
- medium: "oil",
429
- temperatureShift: .6,
430
- chromaEasing: "bell",
431
- saturation: 1.05,
432
- warpAmount: .45,
433
- valueVariance: .12,
434
- breathDepth: .04,
435
- zones: 3
436
- },
437
- vivid: {
438
- harmony: "triad",
439
- medium: "watercolor",
440
- temperatureShift: .2,
441
- chromaEasing: "linear",
442
- saturation: 1.2,
443
- warpAmount: .5,
444
- valueVariance: .14,
445
- breathDepth: .05,
446
- zones: 3
447
- },
448
- muted: {
449
- harmony: "monochrome",
450
- medium: "pastel",
451
- temperatureShift: .15,
452
- chromaEasing: "bell",
453
- saturation: .78,
454
- warpAmount: .4,
455
- valueVariance: .06,
456
- breathDepth: .03,
457
- zones: 2
458
- }
459
- };
460
- function me(e, t = "atmospheric") {
461
- let n = pe[t], r = U(e, {
462
- harmony: n.harmony,
463
- temperatureShift: n.temperatureShift,
464
- chromaEasing: n.chromaEasing,
465
- stopCount: 4
466
- });
467
- return {
468
- ...u,
469
- palette: r,
470
- nuclei: fe(n.zones),
471
- medium: n.medium,
472
- saturation: n.saturation,
473
- warpAmount: n.warpAmount,
474
- valueVariance: n.valueVariance,
475
- breathDepth: n.breathDepth
476
- };
396
+ var De = 70, Oe = 250, ke = 22;
397
+ function Ae(e, t, n) {
398
+ let r = (t - .5) * 2;
399
+ if (r === 0 || n === 0) return e;
400
+ let i = (ue(e, r >= 0 ? De : Oe, 1, "shorter") - e + 540) % 360 - 180;
401
+ return ((e + Math.sign(i) * Math.min(Math.abs(r) * n * ke, Math.abs(i))) % 360 + 360) % 360;
477
402
  }
478
403
  //#endregion
479
404
  //#region src/components/custom/aurora/composables/uniformBridge.ts
480
- var K = {
405
+ var je = {
481
406
  smooth: 0,
482
407
  pastel: 1,
483
408
  watercolor: 2,
484
409
  oil: 3,
485
- crayon: 4
486
- }, he = {
410
+ crayon: 4,
411
+ vangogh: 5,
412
+ "oil-pastel": 6
413
+ }, L = {
487
414
  shorter: 0,
488
415
  longer: 1,
489
416
  increasing: 2,
490
417
  decreasing: 3
491
- }, ge = {
418
+ }, Me = {
492
419
  none: 0,
493
420
  radial: 1,
494
421
  swirl: 2,
495
422
  diagonal: 3,
496
- multi: 4
497
- }, _e = {
423
+ multi: 4,
424
+ tensor: 5
425
+ }, R = {
426
+ flow: 0,
427
+ tensor: 1
428
+ }, z = {
498
429
  fbm: 0,
499
430
  cellular: 1,
500
431
  hybrid: 2
501
- }, q = {
432
+ }, Ne = {
502
433
  oil: 0,
503
434
  knife: 1,
504
435
  chunky: 3
436
+ }, B = {
437
+ L: .985,
438
+ C: .0125,
439
+ h: 77.5
505
440
  };
506
- function ve(e) {
507
- return e.medium === "oil" && e.strokeMode === "crayon" ? K.crayon : K[e.medium];
441
+ function Pe(e) {
442
+ return e === void 0 ? x(B.L, B.C, B.h) : Array.isArray(e) ? e : x(e.L, e.C, e.h);
508
443
  }
509
- function ye(e) {
510
- return e.strokeMode === "crayon" ? q.oil : q[e.strokeMode];
444
+ function V(e) {
445
+ return je[e.medium];
511
446
  }
512
- function be(e, t, n, r) {
447
+ function Fe(e) {
448
+ return Ne[e.strokeMode];
449
+ }
450
+ function Ie(e) {
451
+ return e.medium === "vangogh" || e.medium === "oil-pastel" || e.medium === "crayon" ? R.tensor : R[e.strokeOrient ?? "flow"];
452
+ }
453
+ var Le = 80;
454
+ function Re(e, t, n, r) {
455
+ n.fill(0), r.fill(0), n[0] = t, n[1] = Math.min(e.palette.length, 8), n[2] = Math.min(e.nuclei.length, 6), n[3] = e.softmaxBeta, n[4] = e.valueVariance, n[5] = e.warpAmount, n[6] = e.warpScale, n[7] = e.warpDrift, n[8] = z[e.warpMode], n[9] = e.noiseOctaves, n[10] = V(e), n[11] = e.breathDepth, n[12] = e.breathPeriod, n[13] = e.saturation, n[14] = e.paperGrain, n[15] = e.alpha, n[16] = L[e.huePath ?? "shorter"];
456
+ let i = N(e.palette, 8);
457
+ for (let e = 0; e < 8; e++) r[e * 4 + 0] = i[e * 3 + 0], r[e * 4 + 1] = i[e * 3 + 1], r[e * 4 + 2] = i[e * 3 + 2];
458
+ let a = 32, o = Math.min(e.nuclei.length, 6);
459
+ for (let t = 0; t < o; t++) {
460
+ let n = e.nuclei[t];
461
+ r[a + t * 4 + 0] = n.x, r[a + t * 4 + 1] = 1 - n.y, r[a + t * 4 + 2] = n.radius, r[a + t * 4 + 3] = n.paletteBias;
462
+ }
463
+ a = 56;
464
+ for (let t = 0; t < o; t++) {
465
+ let n = e.nuclei[t];
466
+ r[a + t * 4 + 0] = n.valueBias, r[a + t * 4 + 1] = n.elongation ?? 1, r[a + t * 4 + 2] = -(n.angle ?? 0) * Math.PI / 180, r[a + t * 4 + 3] = n.driftRadius;
467
+ }
468
+ }
469
+ function ze(e, t, n, r) {
513
470
  let i = (e) => 1 - e, a = {
514
471
  palette: new Float32Array(24),
515
472
  pos: new Float32Array(12),
@@ -522,7 +479,7 @@ function be(e, t, n, r) {
522
479
  angle: new Float32Array(6)
523
480
  };
524
481
  return function(o) {
525
- e.useProgram(t), R(o.palette, 8, a.palette), e.uniform3fv(n.uPalette, a.palette), e.uniform1i(n.uStopCount, Math.min(o.palette.length, 8));
482
+ e.useProgram(t), N(o.palette, 8, a.palette), e.uniform3fv(n.uPalette, a.palette), e.uniform1i(n.uStopCount, Math.min(o.palette.length, 8));
526
483
  let s = Math.min(o.nuclei.length, 6);
527
484
  e.uniform1i(n.uNucleiCount, s);
528
485
  for (let e = 0; e < s; e++) {
@@ -530,13 +487,22 @@ function be(e, t, n, r) {
530
487
  a.pos[e * 2 + 0] = t.x, a.pos[e * 2 + 1] = i(t.y), a.rad[e] = t.radius, a.pb[e] = t.paletteBias, a.vb[e] = t.valueBias, a.dr[e] = t.driftRadius, a.dp[e] = t.driftPhase, a.elong[e] = t.elongation ?? 1, a.angle[e] = -(t.angle ?? 0) * Math.PI / 180;
531
488
  }
532
489
  for (let e = s; e < 6; e++) a.pos[e * 2 + 0] = 0, a.pos[e * 2 + 1] = 0, a.rad[e] = 0, a.pb[e] = 0, a.vb[e] = 0, a.dr[e] = 0, a.dp[e] = 0, a.elong[e] = 1, a.angle[e] = 0;
533
- e.uniform2fv(n.uNucleiPos, a.pos), e.uniform1fv(n.uNucleiRadius, a.rad), e.uniform1fv(n.uNucleiPaletteBias, a.pb), e.uniform1fv(n.uNucleiValueBias, a.vb), e.uniform1fv(n.uNucleiDriftRadius, a.dr), e.uniform1fv(n.uNucleiDriftPhase, a.dp), e.uniform1fv(n.uNucleiElong, a.elong), e.uniform1fv(n.uNucleiAngle, a.angle), e.uniform1f(n.uSoftmaxBeta, o.softmaxBeta), e.uniform1f(n.uValueVariance, o.valueVariance), e.uniform1f(n.uWarpAmount, o.warpAmount), e.uniform1f(n.uWarpScale, o.warpScale), e.uniform1f(n.uWarpDrift, o.warpDrift), e.uniform1i(n.uWarpMode, _e[o.warpMode]), e.uniform1i(n.uNoiseOctaves, o.noiseOctaves), e.uniform1i(n.uMedium, ve(o)), e.uniform1i(n.uHuePath, he[o.huePath ?? "shorter"]), e.uniform1i(n.uFlowPattern, ge[o.flow.pattern]), e.uniform2f(n.uFlowFocal, o.flow.focalX, i(o.flow.focalY)), e.uniform1f(n.uFlowAngle, o.flow.angle), e.uniform1f(n.uFlowCurl, o.flow.curl), e.uniform2f(n.uCursor, r.x, i(r.y)), e.uniform1f(n.uCursorStrength, r.strength), e.uniform1f(n.uCursorRadius, r.radius), e.uniform1f(n.uStrokeAmount, o.strokeAmount), e.uniform1f(n.uStrokeScale, o.strokeScale), e.uniform1f(n.uStrokeAnisotropy, o.strokeAnisotropy), e.uniform1i(n.uStrokeLayers, o.strokeLayers), e.uniform1i(n.uStrokeMode, ye(o)), e.uniform1f(n.uWetEdge, o.wetEdge), e.uniform1f(n.uGranulation, o.granulation), e.uniform1f(n.uImpasto, o.impasto), e.uniform1f(n.uBrokenColor, o.brokenColor), e.uniform1f(n.uCanvasGrain, o.canvasGrain), e.uniform1f(n.uNucleiDrift, o.nucleiDrift), e.uniform1f(n.uPaletteDrift, o.paletteDrift), e.uniform1f(n.uBreathDepth, o.breathDepth), e.uniform1f(n.uBreathPeriod, o.breathPeriod), e.uniform1f(n.uSaturation, o.saturation), e.uniform1f(n.uPaperGrain, o.paperGrain), e.uniform1f(n.uAlpha, o.alpha);
490
+ e.uniform2fv(n.uNucleiPos, a.pos), e.uniform1fv(n.uNucleiRadius, a.rad), e.uniform1fv(n.uNucleiPaletteBias, a.pb), e.uniform1fv(n.uNucleiValueBias, a.vb), e.uniform1fv(n.uNucleiDriftRadius, a.dr), e.uniform1fv(n.uNucleiDriftPhase, a.dp), e.uniform1fv(n.uNucleiElong, a.elong), e.uniform1fv(n.uNucleiAngle, a.angle), e.uniform1f(n.uSoftmaxBeta, o.softmaxBeta), e.uniform1f(n.uValueVariance, o.valueVariance), e.uniform1f(n.uWarpAmount, o.warpAmount), e.uniform1f(n.uWarpScale, o.warpScale), e.uniform1f(n.uWarpDrift, o.warpDrift), e.uniform1i(n.uWarpMode, z[o.warpMode]), e.uniform1i(n.uNoiseOctaves, o.noiseOctaves), e.uniform1i(n.uMedium, V(o)), e.uniform1i(n.uHuePath, L[o.huePath ?? "shorter"]), e.uniform1i(n.uFlowPattern, Me[o.flow.pattern]), e.uniform2f(n.uFlowFocal, o.flow.focalX, i(o.flow.focalY)), e.uniform1f(n.uFlowAngle, o.flow.angle), e.uniform1f(n.uFlowCurl, o.flow.curl), e.uniform2f(n.uCursor, r.x, i(r.y)), e.uniform1f(n.uCursorStrength, r.strength), e.uniform1f(n.uCursorRadius, r.radius), e.uniform2f(n.uCursorVelocity, r.velX, -r.velY), e.uniform1f(n.uCursorBurst, r.burst), e.uniform1f(n.uStrokeAmount, o.strokeAmount), e.uniform1f(n.uStrokeScale, o.strokeScale), e.uniform1f(n.uStrokeAnisotropy, o.strokeAnisotropy), e.uniform1i(n.uStrokeLayers, o.strokeLayers), e.uniform1i(n.uStrokeMode, Fe(o)), e.uniform1i(n.uStrokeOrient, Ie(o));
491
+ {
492
+ let t = o.lightDir ?? [
493
+ -.5,
494
+ .6,
495
+ .62
496
+ ], r = Pe(o.lightColor);
497
+ e.uniform3f(n.uLightDir, t[0], t[1], t[2]), e.uniform3f(n.uLightColor, r[0], r[1], r[2]);
498
+ }
499
+ e.uniform1f(n.uWetEdge, o.wetEdge), e.uniform1f(n.uGranulation, o.granulation), e.uniform1f(n.uImpasto, o.impasto), e.uniform1f(n.uBrokenColor, o.brokenColor), e.uniform1f(n.uCanvasGrain, o.canvasGrain), e.uniform1f(n.uNucleiDrift, o.nucleiDrift), e.uniform1f(n.uPaletteDrift, o.paletteDrift), e.uniform1f(n.uBreathDepth, o.breathDepth), e.uniform1f(n.uBreathPeriod, o.breathPeriod), e.uniform1f(n.uSaturation, o.saturation), e.uniform1f(n.uPaperGrain, o.paperGrain), e.uniform1f(n.uAlpha, o.alpha);
534
500
  };
535
501
  }
536
502
  //#endregion
537
503
  //#region src/components/custom/aurora/composables/cursorModel.ts
538
- var J = .22, xe = .18, Y = .992;
539
- function Se() {
504
+ var H = .22, Be = .18, Ve = .992, He = .96, U = .3;
505
+ function Ue() {
540
506
  return {
541
507
  x: .5,
542
508
  y: .5,
@@ -544,115 +510,415 @@ function Se() {
544
510
  targetY: .5,
545
511
  strength: 0,
546
512
  targetStrength: 0,
547
- radius: .25
513
+ radius: .25,
514
+ velX: 0,
515
+ velY: 0,
516
+ burst: 0
548
517
  };
549
518
  }
550
- function Ce(e) {
551
- e.x += (e.targetX - e.x) * J, e.y += (e.targetY - e.y) * J, e.strength += (e.targetStrength - e.strength) * xe, e.targetStrength *= Y;
519
+ function W(e, t = 1) {
520
+ let n = Math.max(0, Math.min(1, t));
521
+ e.x += (e.targetX - e.x) * H, e.y += (e.targetY - e.y) * H, e.strength += (e.targetStrength - e.strength) * Be, e.targetStrength *= Ve, e.velX *= (1 - U) * n, e.velY *= (1 - U) * n, e.burst *= He * n;
552
522
  }
553
- function we(e) {
523
+ function We(e, t, n) {
524
+ let r = .12, i = Math.max(-r, Math.min(r, t)), a = Math.max(-r, Math.min(r, n));
525
+ e.velX += (i - e.velX) * U, e.velY += (a - e.velY) * U;
526
+ let o = Math.hypot(i, a);
527
+ e.burst = Math.min(1, e.burst + o * 4);
528
+ }
529
+ function G(e) {
554
530
  return e.targetStrength > .001 || e.strength > .001 || Math.abs(e.x - e.targetX) > .001 || Math.abs(e.y - e.targetY) > .001;
555
531
  }
556
532
  //#endregion
557
533
  //#region src/components/custom/aurora/composables/frameLoop.ts
558
- function Te(e) {
534
+ function Ge(e) {
559
535
  let { gl: t, prog: n, uniforms: r, cursor: i, getConfig: a, getReducedMotion: o } = e, s = (e) => 1 - e;
560
- function c(e) {
561
- t.useProgram(n), t.uniform2f(r.uCursor, i.x, s(i.y)), t.uniform1f(r.uCursorStrength, i.strength), t.uniform1f(r.uCursorRadius, i.radius), t.uniform1f(r.uTime, e), t.clear(t.COLOR_BUFFER_BIT), t.drawArrays(t.TRIANGLES, 0, 3);
536
+ function c() {
537
+ return +!o();
562
538
  }
563
- function l() {
539
+ function l(e) {
540
+ if (t.useProgram(n), t.uniform2f(r.uCursor, i.x, s(i.y)), t.uniform1f(r.uCursorStrength, i.strength), t.uniform1f(r.uCursorRadius, i.radius), t.uniform2f(r.uCursorVelocity, i.velX, -i.velY), t.uniform1f(r.uCursorBurst, i.burst), t.uniform1f(r.uTime, e), a().interactivity?.light) {
541
+ let n = c(), a = e * .25, o = Math.cos(a) * .5, s = Math.sin(a) * .5, l = (i.x - .5) * 2, u = (.5 - i.y) * 2, d = i.strength * n;
542
+ o = o * (1 - d) + l * d, s = s * (1 - d) + u * d, t.uniform3f(r.uLightDir, o * n + (1 - n) * -.5, s * n + (1 - n) * .6, .62);
543
+ }
544
+ t.clear(t.COLOR_BUFFER_BIT), t.drawArrays(t.TRIANGLES, 0, 3);
545
+ }
546
+ function u() {
564
547
  if (o()) return !1;
565
548
  let e = a();
566
- return e.nucleiDrift !== 0 || e.paletteDrift !== 0 || e.breathDepth !== 0 || e.warpDrift !== 0 ? !0 : we(i);
549
+ return e.nucleiDrift !== 0 || e.paletteDrift !== 0 || e.breathDepth !== 0 || e.warpDrift !== 0 || e.interactivity?.light && i.burst > .001 ? !0 : G(i);
567
550
  }
568
551
  return {
569
552
  frame: (e) => {
570
- Ce(i), c(e);
553
+ W(i, c()), l(e);
571
554
  },
572
- needsAnimation: l
555
+ needsAnimation: u
556
+ };
557
+ }
558
+ var Ke = "\nstruct VSOut {\n @builtin(position) pos: vec4f,\n @location(0) uv: vec2f,\n};\n\n@vertex\nfn vs_main(@builtin(vertex_index) vi: u32) -> VSOut {\n // Full-screen triangle (matches the WebGL2 [-1,-1, 3,-1, -1,3] quad).\n var p = array<vec2f, 3>(vec2f(-1.0, -1.0), vec2f(3.0, -1.0), vec2f(-1.0, 3.0));\n var out: VSOut;\n let xy = p[vi];\n out.pos = vec4f(xy, 0.0, 1.0);\n out.uv = 0.5 * (xy + vec2f(1.0));\n return out;\n}", qe = `
559
+ const PI: f32 = 3.141592653589793;
560
+ const W_LUMA: vec3f = vec3f(0.2126, 0.7152, 0.0722);
561
+ const MAX_NUCLEI: u32 = 6u;
562
+ const MAX_STOPS: u32 = 8u;
563
+
564
+ // The uniform struct — ONLY constant-indexed scalars (safe in the uniform address
565
+ // space). The five count/enum fields are f32 (AX.W07 1a): the WebGPU write path packs
566
+ // them as floats and the shader i32-casts at each use, so WGSL never reads a float
567
+ // bit-pattern as a raw i32. The dynamically-indexed arrays moved OUT to struct Field
568
+ // below (AX.W07 1b).
569
+ struct Uniforms {
570
+ time: f32,
571
+ stopCount: f32,
572
+ nucleiCount: f32,
573
+ softmaxBeta: f32,
574
+ valueVariance: f32,
575
+ warpAmount: f32,
576
+ warpScale: f32,
577
+ warpDrift: f32,
578
+ warpMode: f32,
579
+ noiseOctaves: f32,
580
+ medium: f32,
581
+ breathDepth: f32,
582
+ breathPeriod: f32,
583
+ saturation: f32,
584
+ paperGrain: f32,
585
+ alpha: f32,
586
+ // AX.W11 — the huePath enum (f32-packed, i32()-cast in samplePalette) carries the
587
+ // shared ramp's OKLab-rectangular-vs-OKLCh-hue-arc dispatch into the WebGPU twin so
588
+ // a huePath:'increasing' rainbow config arcs identically to the WebGL2 oracle. The
589
+ // CPU pad keeps the buffer vec4-aligned (WGPU_UNIFORM_FLOATS rounds to 20).
590
+ huePath: f32,
591
+ };
592
+
593
+ // The dynamically-indexed arrays — bound var<storage, read> (AX.W07 1b). A runtime
594
+ // palette/nuclei index into a var<uniform> array<vec4f> returns [0,0,0,0] on Metal
595
+ // (MSL forbids dynamic indexing of constant); storage is always-legal. The vec4 stride
596
+ // is std140-identical to the uniform layout, so the move is byte-exact.
597
+ struct Field {
598
+ palette: array<vec4f, 8>, // .xyz = linear-sRGB stop
599
+ nucleiPos: array<vec4f, 6>, // .xy pos, .z radius, .w paletteBias
600
+ nucleiMod: array<vec4f, 6>, // .x valueBias, .y elong, .z angle, .w driftRadius
601
+ };
602
+
603
+ @group(0) @binding(0) var<uniform> U: Uniforms;
604
+ @group(0) @binding(1) var<storage, read> F: Field;
605
+
606
+ ${g}
607
+ ${n}
608
+ ${i}
609
+ ${l}
610
+
611
+ // AX.W12 — the painterly-medium organic noise basis (PCG2D integer-bit hash + 2D
612
+ // simplex gradient noise), SPLICED from the shared procedural-color chunk — the EXACT
613
+ // twin of the GLSL leaf aurora.frag.ts splices, so the hash can NEVER drift between
614
+ // backends (proof:aurora-noise-hash-equivalence locks the twins to 1e-6). The WebGPU
615
+ // single-pass fs_main below stays the smooth pole (it runs no medium body — the
616
+ // painterly mediums are the W7c/W14 multi-pass), so the leaf is single-sourced HERE for
617
+ // the twin-equivalence contract; the W14 painterly passes consume it.
618
+ ${a}
619
+
620
+ fn hash21(p0: vec2f) -> f32 {
621
+ var p = fract(p0 * vec2f(123.34, 456.21));
622
+ p = p + dot(p, p + 45.32);
623
+ return fract(p.x * p.y);
624
+ }
625
+ fn vnoise(p: vec2f) -> f32 {
626
+ let i = floor(p);
627
+ let f = fract(p);
628
+ let u = f * f * (3.0 - 2.0 * f);
629
+ let a = hash21(i);
630
+ let b = hash21(i + vec2f(1.0, 0.0));
631
+ let c = hash21(i + vec2f(0.0, 1.0));
632
+ let d = hash21(i + vec2f(1.0, 1.0));
633
+ return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
634
+ }
635
+ fn fbm(p0: vec2f) -> f32 {
636
+ var v = 0.0;
637
+ var a = 0.5;
638
+ var p = p0;
639
+ let octaves = i32(U.noiseOctaves); // AX.W07 1a — i32()-cast the f32-packed count.
640
+ for (var i = 0; i < 5; i = i + 1) {
641
+ if (i >= octaves) { break; }
642
+ v = v + a * vnoise(p);
643
+ p = FBM_ROT * p * 2.02;
644
+ a = a * 0.5;
645
+ }
646
+ return v;
647
+ }
648
+
649
+ fn samplePalette(id: f32) -> vec3f {
650
+ // AX.W11 — splice the SHARED samplePaletteRamp (the smoothstep ease + the OKLab-
651
+ // rectangular-vs-OKLCh-hue-arc huePath dispatch), the EXACT twin the GLSL oracle
652
+ // splices. The body here only selects the bracketing stop pair + the raw inter-stop
653
+ // t (the W07 storage-Field dynamic index), then hands them to the shared ramp.
654
+ let n = i32(U.stopCount); // AX.W07 1a — i32()-cast the f32-packed count.
655
+ if (n <= 1) { return F.palette[0].xyz; }
656
+ let t = clamp(id, 0.0, 1.0) * f32(n - 1);
657
+ let i0 = i32(floor(t));
658
+ let i1 = min(i0 + 1, n - 1);
659
+ let f = t - f32(i0);
660
+ // AX.W07 1b — read the palette from the storage Field (dynamic i0/i1 index);
661
+ // AX.W11 — i32()-cast the f32-packed huePath enum (parity with the count casts).
662
+ return samplePaletteRamp(F.palette[i0].xyz, F.palette[i1].xyz, f, i32(U.huePath));
663
+ }
664
+
665
+ fn domainWarp(p: vec2f, t: f32) -> vec2f {
666
+ let drift = t * U.warpDrift * 5.0;
667
+ let q = vec2f(
668
+ fbm(p * U.warpScale + vec2f(drift, 0.0)),
669
+ fbm(p * U.warpScale + vec2f(5.2, 1.3 + drift))
670
+ );
671
+ let r = vec2f(
672
+ fbm(p * U.warpScale + 4.0 * q + vec2f(1.7, 9.2)),
673
+ fbm(p * U.warpScale + 4.0 * q + vec2f(8.3, 2.8))
674
+ );
675
+ return p + U.warpAmount * r;
676
+ }
677
+
678
+ fn nucleiField(p: vec2f, t: f32) -> vec2f {
679
+ // Returns (paletteId, valueMod) — softmax over the nuclei (isotropic; the WebGPU
680
+ // twin keeps the parity-floor isotropic Gaussian).
681
+ var accumId = 0.0;
682
+ var accumVal = 0.0;
683
+ var w = 0.0;
684
+ let n = i32(U.nucleiCount); // AX.W07 1a — i32()-cast the f32-packed count.
685
+ for (var i = 0; i < 6; i = i + 1) {
686
+ if (i >= n) { break; }
687
+ // AX.W07 1b — read the nuclei from the storage Field (dynamic i index).
688
+ let pos = F.nucleiPos[i].xy;
689
+ let rad = max(F.nucleiPos[i].z, 0.01);
690
+ let d2 = dot(p - pos, p - pos);
691
+ let wi = exp((-U.softmaxBeta * d2) / (rad * rad));
692
+ accumId = accumId + wi * F.nucleiPos[i].w;
693
+ accumVal = accumVal + wi * F.nucleiMod[i].x;
694
+ w = w + wi;
695
+ }
696
+ if (w < 1e-4) { return vec2f(0.5, 0.0); }
697
+ return vec2f(accumId / w, accumVal / w);
698
+ }
699
+
700
+ // ACES (Narkowicz) — matches the GLSL tonemap.
701
+ fn aces(x: vec3f) -> vec3f {
702
+ let a = 2.51; let b = 0.03; let c = 2.43; let d = 0.59; let e = 0.14;
703
+ return clamp((x * (a * x + b)) / (x * (c * x + d) + e), vec3f(0.0), vec3f(1.0));
704
+ }
705
+
706
+ @fragment
707
+ fn fs_main(@location(0) uv: vec2f) -> @location(0) vec4f {
708
+ let t = U.time;
709
+ let pw = domainWarp(uv, t);
710
+ let nf = nucleiField(pw, t);
711
+ var col = samplePalette(nf.x);
712
+ col = col * (1.0 + U.valueVariance * nf.y);
713
+
714
+ // Breath.
715
+ let breath = sin(t * 6.2831 / max(U.breathPeriod, 1.0));
716
+ col = col * (1.0 + U.breathDepth * breath * 0.5);
717
+
718
+ // (The WebGPU smooth pole; the painterly mediums run the W7c multi-pass passes.)
719
+ col = aces(col);
720
+ col = linearToSrgb(col); // the mandatory OETF (linear → display).
721
+ return vec4f(col * U.alpha, U.alpha); // premultiply AFTER the OETF (parity).
722
+ }`, Je = Ke + "\n" + qe;
723
+ //#endregion
724
+ //#region src/components/custom/aurora/composables/gpuRuntime.ts
725
+ function Ye(e) {
726
+ let { canvas: t, getConfig: n, cursor: r, getReducedMotion: i, frozenOffset: a } = e;
727
+ return function({ device: e, context: o, format: s }) {
728
+ let c = e.createShaderModule({ code: Je }), l = e.createRenderPipeline({
729
+ layout: "auto",
730
+ vertex: {
731
+ module: c,
732
+ entryPoint: "vs_main"
733
+ },
734
+ fragment: {
735
+ module: c,
736
+ entryPoint: "fs_main",
737
+ targets: [{
738
+ format: s,
739
+ blend: {
740
+ color: {
741
+ srcFactor: "one",
742
+ dstFactor: "one-minus-src-alpha"
743
+ },
744
+ alpha: {
745
+ srcFactor: "one",
746
+ dstFactor: "one-minus-src-alpha"
747
+ }
748
+ }
749
+ }]
750
+ },
751
+ primitive: { topology: "triangle-list" }
752
+ }), u = new Float32Array(20), f = e.createBuffer({
753
+ size: u.byteLength,
754
+ usage: 72
755
+ }), p = new Float32Array(Le), m = e.createBuffer({
756
+ size: p.byteLength,
757
+ usage: 136
758
+ }), h = e.createBindGroup({
759
+ layout: l.getBindGroupLayout(0),
760
+ entries: [{
761
+ binding: 0,
762
+ resource: { buffer: f }
763
+ }, {
764
+ binding: 1,
765
+ resource: { buffer: m }
766
+ }]
767
+ });
768
+ function g() {
769
+ return +!i();
770
+ }
771
+ function _() {
772
+ let e = d(), n = t.clientWidth || t.parentElement?.clientWidth || 1, r = t.clientHeight || t.parentElement?.clientHeight || 1;
773
+ t.width = Math.max(1, Math.floor(n * e)), t.height = Math.max(1, Math.floor(r * e));
774
+ }
775
+ function v(t) {
776
+ W(r, g()), Re(n(), t, u, p), e.queue.writeBuffer(f, 0, u), e.queue.writeBuffer(m, 0, p);
777
+ let i = e.createCommandEncoder(), a = o.getCurrentTexture().createView(), s = i.beginRenderPass({ colorAttachments: [{
778
+ view: a,
779
+ clearValue: {
780
+ r: 0,
781
+ g: 0,
782
+ b: 0,
783
+ a: 0
784
+ },
785
+ loadOp: "clear",
786
+ storeOp: "store"
787
+ }] });
788
+ s.setPipeline(l), s.setBindGroup(0, h), s.draw(3), s.end(), e.queue.submit([i.finish()]);
789
+ }
790
+ function y() {
791
+ if (i()) return !1;
792
+ let e = n();
793
+ return e.nucleiDrift !== 0 || e.paletteDrift !== 0 || e.breathDepth !== 0 || e.warpDrift !== 0 || e.interactivity?.light && r.burst > .001 ? !0 : G(r);
794
+ }
795
+ return {
796
+ frame: v,
797
+ shouldContinue: y,
798
+ resize: _,
799
+ time: (e) => i() ? a : e,
800
+ teardown: () => {
801
+ f.destroy(), m.destroy();
802
+ }
803
+ };
573
804
  };
574
805
  }
575
806
  //#endregion
576
807
  //#region src/components/custom/aurora/composables/runtime.ts
577
- function Ee(e) {
808
+ function Xe(e) {
578
809
  return e.mode === "capture" ? !0 : e.initStrategy === "eager";
579
810
  }
580
- function De(e) {
811
+ function Ze(e) {
581
812
  return typeof e.preserveDrawingBuffer == "boolean" ? e.preserveDrawingBuffer : e.mode === "capture";
582
813
  }
583
- function X(e, t, r = {}) {
584
- let i = De(r), o = t, s = Se(), c = null, l = a(e, {
585
- mode: r.mode === "capture" ? "capture" : "live",
814
+ function K(e, t, n = {}) {
815
+ let r = Ze(n), i = t, a = 3.7, o = Ue(), s = null, c = n.gpuDevice ? xe(e, {
816
+ device: n.gpuDevice,
817
+ mode: n.mode === "capture" ? "capture" : "live",
818
+ alphaMode: "premultiplied",
819
+ setup: Ye({
820
+ canvas: e,
821
+ getConfig: () => i,
822
+ cursor: o,
823
+ getReducedMotion: () => c.reducedMotion,
824
+ frozenOffset: a
825
+ })
826
+ }) : p(e, {
827
+ mode: n.mode === "capture" ? "capture" : "live",
586
828
  contextAttrs: {
587
829
  antialias: !1,
588
830
  alpha: !0,
589
831
  premultipliedAlpha: !0,
590
- preserveDrawingBuffer: i
832
+ preserveDrawingBuffer: r
591
833
  },
592
834
  setup: (t) => {
593
- let { program: r, vs: i, fs: a, uniforms: u, geometry: d } = L(t, re, P);
594
- function f() {
595
- let i = n(), a = e.clientWidth || e.parentElement?.clientWidth || 1, o = e.clientHeight || e.parentElement?.clientHeight || 1, s = Math.max(1, Math.floor(a * i)), c = Math.max(1, Math.floor(o * i));
596
- e.width = s, e.height = c, t.viewport(0, 0, s, c), t.useProgram(r);
835
+ let { program: n, vs: r, fs: l, uniforms: u, geometry: f } = Ce(t, pe, be);
836
+ function p() {
837
+ let r = d(), i = e.clientWidth || e.parentElement?.clientWidth || 1, a = e.clientHeight || e.parentElement?.clientHeight || 1, o = Math.max(1, Math.floor(i * r)), s = Math.max(1, Math.floor(a * r));
838
+ e.width = o, e.height = s, t.viewport(0, 0, o, s), t.useProgram(n);
597
839
  }
598
- let p = be(t, r, u, s), m = Te({
840
+ let m = ze(t, n, u, o), h = Ge({
599
841
  gl: t,
600
- prog: r,
842
+ prog: n,
601
843
  uniforms: u,
602
- cursor: s,
603
- getConfig: () => o,
604
- getReducedMotion: () => l.reducedMotion
844
+ cursor: o,
845
+ getConfig: () => i,
846
+ getReducedMotion: () => c.reducedMotion
605
847
  });
606
- return t.clearColor(0, 0, 0, 0), t.disable(t.DEPTH_TEST), t.enable(t.BLEND), t.blendFunc(t.ONE, t.ONE_MINUS_SRC_ALPHA), c = (e) => {
607
- o = e, p(e);
608
- }, c(o), requestAnimationFrame(() => {
609
- f(), requestAnimationFrame(f);
848
+ return t.clearColor(0, 0, 0, 0), t.disable(t.DEPTH_TEST), t.enable(t.BLEND), t.blendFunc(t.ONE, t.ONE_MINUS_SRC_ALPHA), s = (e) => {
849
+ i = e, m(e);
850
+ }, s(i), requestAnimationFrame(() => {
851
+ p(), requestAnimationFrame(p);
610
852
  }), {
611
- frame: m.frame,
612
- shouldContinue: m.needsAnimation,
613
- resize: f,
614
- time: (e) => l.reducedMotion ? 3.7 : e,
853
+ frame: h.frame,
854
+ shouldContinue: h.needsAnimation,
855
+ resize: p,
856
+ time: (e) => c.reducedMotion ? a : e,
615
857
  teardown: () => {
616
- t.deleteProgram(r), t.deleteShader(i), t.deleteShader(a), t.deleteBuffer(d.buf), t.deleteVertexArray(d.vao);
858
+ t.deleteProgram(n), t.deleteShader(r), t.deleteShader(l), t.deleteBuffer(f.buf), t.deleteVertexArray(f.vao);
617
859
  }
618
860
  };
619
861
  }
620
862
  });
621
- function u(e, t, n = .8) {
622
- s.targetX = e, s.targetY = t, s.targetStrength = n, l.wake();
863
+ function l(e, t, n = .8) {
864
+ o.targetX = e, o.targetY = t, o.targetStrength = n, c.wake();
865
+ }
866
+ function u(e, t) {
867
+ c.reducedMotion || (We(o, e, t), c.wake());
623
868
  }
624
- function d() {
625
- s.targetStrength = 0, l.wake();
869
+ function f() {
870
+ o.targetStrength = 0, c.wake();
626
871
  }
627
- function f(e) {
628
- s.radius = e, l.wake();
872
+ function m(e) {
873
+ o.radius = e, c.wake();
629
874
  }
630
- function p(e) {
631
- l.wake();
875
+ function h(e) {
876
+ c.wake();
632
877
  }
633
- return r.mode !== "capture" && Ee(r) && l.arm(), {
634
- arm: () => l.arm(),
878
+ return n.mode !== "capture" && Xe(n) && c.arm(), {
879
+ arm: () => c.arm(),
635
880
  update: (e) => {
636
- o = e, c?.(e), l.wake();
881
+ i = e, s?.(e), c.wake();
637
882
  },
638
- setCursor: u,
639
- clearCursor: d,
640
- setCursorRadius: f,
641
- setReducedMotion: p,
642
- pause: (e = "manual") => l.suspend(e),
643
- resume: (e = "manual") => l.resume(e),
644
- renderAt: (e) => l.renderAt(e),
645
- dispose: () => l.dispose()
883
+ setCursor: l,
884
+ clearCursor: f,
885
+ setCursorRadius: m,
886
+ setReducedMotion: h,
887
+ injectCursorVelocity: u,
888
+ get reducedMotion() {
889
+ return c.reducedMotion;
890
+ },
891
+ pause: (e = "manual") => c.suspend(e),
892
+ resume: (e = "manual") => c.resume(e),
893
+ renderAt: (e) => c.renderAt(e),
894
+ dispose: () => c.dispose()
646
895
  };
647
896
  }
648
897
  //#endregion
649
898
  //#region src/components/custom/aurora/composables/configSource.ts
650
- function Z(e) {
899
+ function q(e) {
651
900
  return typeof e == "function" ? e : e && typeof e == "object" && "value" in e ? () => e.value : () => e;
652
901
  }
653
902
  //#endregion
903
+ //#region src/components/custom/aurora/constants/renderMode.ts
904
+ function J(e) {
905
+ if (e !== "auto") return e;
906
+ if (typeof navigator > "u" || typeof window > "u") return "webgl";
907
+ let t = typeof navigator.hardwareConcurrency == "number" && navigator.hardwareConcurrency <= 4, n = typeof window.matchMedia == "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches, r = navigator.connection?.saveData === !0;
908
+ return t || n || r ? "css" : "webgl";
909
+ }
910
+ async function Qe(e) {
911
+ return J(e) === "css" ? {
912
+ substrate: "css",
913
+ device: null
914
+ } : {
915
+ substrate: "webgl",
916
+ device: null
917
+ };
918
+ }
919
+ //#endregion
654
920
  //#region src/components/custom/aurora/composables/useAurora.ts
655
- function Oe(e) {
921
+ function $e(e) {
656
922
  if (typeof window > "u") return () => {};
657
923
  if (typeof window.requestIdleCallback == "function") {
658
924
  let t = window.requestIdleCallback(() => e(), { timeout: 2e3 });
@@ -669,10 +935,10 @@ function Oe(e) {
669
935
  r = !0, cancelAnimationFrame(t), n !== void 0 && clearTimeout(n);
670
936
  };
671
937
  }
672
- function Q(e, t, n = {}, r = {}) {
673
- let i = Z(t), a = r.renderMode === "css", o = null, s = null, c = null, l = null, u = null, d = null, f = !1, p = C(!1), h = n.mode === "capture" || n.initStrategy === "eager";
938
+ function Y(e, t, n = {}, r = {}) {
939
+ let i = q(t), a = r.renderMode === "css", o = null, s = null, c = null, l = null, u = null, d = null, f = null, p = !1, m = k(!1), h = n.mode === "capture" || n.initStrategy === "eager";
674
940
  function g() {
675
- d && o && o.setReducedMotion(d.matches);
941
+ f && o && o.setReducedMotion(f.matches);
676
942
  }
677
943
  function _(e) {
678
944
  let t = e instanceof Error ? e : Error(String(e));
@@ -683,22 +949,27 @@ function Q(e, t, n = {}, r = {}) {
683
949
  Promise.reject(t);
684
950
  }
685
951
  function v() {
686
- if (!(f || !o)) {
687
- f = !0;
952
+ if (!(p || !o)) {
953
+ p = !0;
688
954
  try {
689
955
  o.arm();
690
956
  } catch (e) {
691
957
  _(e);
692
958
  return;
693
959
  }
694
- p.value = !0, d = window.matchMedia("(prefers-reduced-motion: reduce)"), d.addEventListener("change", g), s = T(i, (e) => o?.update(e), { deep: !0 });
960
+ m.value = !0, f = window.matchMedia("(prefers-reduced-motion: reduce)"), f.addEventListener("change", g), s = A(i, (e) => o?.update(e), { deep: !0 }), i().interactivity?.scroll && e.value && (l = A(re({
961
+ target: e,
962
+ trackExit: !0
963
+ }), (e) => {
964
+ !o || o.reducedMotion || o.setCursorRadius(.2 + e * .25);
965
+ }));
695
966
  }
696
967
  }
697
- return S(() => {
968
+ return O(() => {
698
969
  let t = e.value;
699
970
  if (!t || a) return;
700
971
  try {
701
- o = X(t, i(), {
972
+ o = K(t, i(), {
702
973
  initStrategy: "deferred",
703
974
  ...n
704
975
  });
@@ -710,150 +981,152 @@ function Q(e, t, n = {}, r = {}) {
710
981
  v();
711
982
  return;
712
983
  }
713
- u = m(e, {
984
+ d = ne(e, {
714
985
  pause: () => o?.pause("off-screen"),
715
986
  resume: () => o?.resume("off-screen")
716
987
  }, { pauseWhenHidden: !1 });
717
- let r = u.isIntersecting;
718
- c = T(r, (e) => {
719
- !e || f || l || (l = Oe(() => {
720
- l = null, r.value && v();
988
+ let r = d.isIntersecting;
989
+ c = A(r, (t) => {
990
+ !t || p || u || (u = $e(() => {
991
+ u = null, r.value && Qe("webgl").then(({ substrate: t, device: a }) => {
992
+ p || (t === "webgpu" && a && o && e.value && (o.dispose(), o = K(e.value, i(), {
993
+ initStrategy: "deferred",
994
+ ...n,
995
+ gpuDevice: a
996
+ })), r.value && v());
997
+ }).catch(() => {
998
+ r.value && v();
999
+ });
721
1000
  }));
722
1001
  }, { immediate: !0 });
723
- }), x(() => {
724
- l?.(), c?.(), s?.(), u?.dispose(), d?.removeEventListener("change", g), o?.dispose(), o = null;
1002
+ }), D(() => {
1003
+ u?.(), c?.(), s?.(), l?.(), d?.dispose(), f?.removeEventListener("change", g), o?.dispose(), o = null;
725
1004
  }), {
726
1005
  setCursor: (e, t, n) => o?.setCursor(e, t, n),
727
1006
  clearCursor: () => o?.clearCursor(),
728
1007
  setCursorRadius: (e) => o?.setCursorRadius(e),
1008
+ injectCursorVelocity: (e, t) => o?.injectCursorVelocity(e, t),
729
1009
  renderAt: (e) => o?.renderAt(e),
730
1010
  pause: () => o?.pause(),
731
1011
  resume: () => o?.resume(),
732
- isArmed: te(p)
1012
+ isArmed: ce(m)
733
1013
  };
734
1014
  }
735
1015
  //#endregion
736
- //#region src/components/custom/aurora/constants/renderMode.ts
737
- function $(e) {
738
- if (e !== "auto") return e;
739
- if (typeof navigator > "u" || typeof window > "u") return "webgl";
740
- let t = typeof navigator.hardwareConcurrency == "number" && navigator.hardwareConcurrency <= 4, n = typeof window.matchMedia == "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches, r = navigator.connection?.saveData === !0;
741
- return t || n || r ? "css" : "webgl";
742
- }
743
- //#endregion
744
1016
  //#region src/components/custom/aurora/Aurora.vue
745
- var ke = /* @__PURE__ */ e(/* @__PURE__ */ v({
1017
+ var et = /* @__PURE__ */ e(/* @__PURE__ */ ae({
746
1018
  __name: "Aurora",
747
1019
  props: {
748
- config: { default: () => u },
1020
+ config: { default: () => y },
749
1021
  runtimeOptions: {},
750
1022
  onInitError: {},
751
1023
  renderMode: { default: "auto" },
752
1024
  opacityCeiling: { default: 1 }
753
1025
  },
754
1026
  setup(e, { expose: t }) {
755
- let n = e, r = $(n.renderMode), i = h(() => Math.max(0, Math.min(1, n.opacityCeiling))), a = C(null), o = Q(a, () => n.config, h(() => ({
1027
+ let n = e, r = J(n.renderMode), i = w(() => Math.max(0, Math.min(1, n.opacityCeiling))), a = k(null), o = Y(a, () => n.config, w(() => ({
756
1028
  ...n.runtimeOptions ?? {},
757
1029
  ...n.onInitError ? { onInitError: n.onInitError } : {}
758
- })).value, { renderMode: r }), s = h(() => z(n.config.palette));
1030
+ })).value, { renderMode: r }), s = w(() => P(n.config.palette));
759
1031
  return t({
760
1032
  config: n.config,
761
1033
  canvasRef: a,
762
1034
  setCursor: o.setCursor,
763
1035
  clearCursor: o.clearCursor,
764
1036
  setCursorRadius: o.setCursorRadius,
1037
+ injectCursorVelocity: o.injectCursorVelocity,
765
1038
  renderAt: o.renderAt,
766
1039
  pause: o.pause,
767
1040
  resume: o.resume,
768
1041
  isArmed: o.isArmed
769
- }), (e, t) => (ee(), g("div", {
1042
+ }), (e, t) => (se(), ie("div", {
770
1043
  class: "aurora-root block h-full w-full overflow-hidden",
771
- style: b({ "--aurora-opacity-ceiling": i.value })
772
- }, [_("div", {
1044
+ style: E({ "--aurora-opacity-ceiling": i.value })
1045
+ }, [T("div", {
773
1046
  class: "aurora-placeholder h-full w-full",
774
1047
  "aria-hidden": "true",
775
- style: b({ backgroundImage: s.value })
776
- }, null, 4), _("canvas", {
1048
+ style: E({ backgroundImage: s.value })
1049
+ }, null, 4), T("canvas", {
777
1050
  ref_key: "canvasRef",
778
1051
  ref: a,
779
1052
  "aria-hidden": "true",
780
- class: y(["aurora-canvas block h-full w-full", { "aurora-canvas--armed": w(o).isArmed.value }])
1053
+ class: oe(["aurora-canvas block h-full w-full", { "aurora-canvas--armed": le(o).isArmed.value }])
781
1054
  }, null, 2)], 4));
782
1055
  }
783
- }), [["__scopeId", "data-v-c5277232"]]);
1056
+ }), [["__scopeId", "data-v-59e84382"]]);
784
1057
  //#endregion
785
1058
  //#region src/components/custom/aurora/composables/useCursorInteraction.ts
786
- function Ae(e, t, n) {
787
- let r = Z(t), i = n.hitFraction ?? .35, a = null, o = null;
788
- function s(e, t) {
1059
+ function tt(e, t, n) {
1060
+ let r = q(t), i = n.hitFraction ?? .35, a = null, o = null, s = null, c = null;
1061
+ function l(e, t) {
789
1062
  let n = t.getBoundingClientRect();
790
1063
  return {
791
1064
  x: (e.clientX - n.left) / Math.max(n.width, 1),
792
1065
  y: (e.clientY - n.top) / Math.max(n.height, 1)
793
1066
  };
794
1067
  }
795
- function c(e, t, n) {
1068
+ function u(e, t, n) {
796
1069
  let r = t - e.x, i = n - e.y, a = (e.angle ?? 0) * Math.PI / 180, o = Math.cos(a), s = Math.sin(a), c = o * r + s * i, l = -s * r + o * i, u = c / Math.max(e.elongation ?? 1, .01), d = l;
797
1070
  return u * u + d * d;
798
1071
  }
799
- function l(e, t) {
1072
+ function d(e, t) {
800
1073
  let n = null, a = Infinity;
801
1074
  for (let o = 0; o < r().nuclei.length; o++) {
802
- let s = r().nuclei[o], l = c(s, e, t), u = s.radius * i;
803
- l < u * u && l < a && (a = l, n = o);
1075
+ let s = r().nuclei[o], c = u(s, e, t), l = s.radius * i;
1076
+ c < l * l && c < a && (a = c, n = o);
804
1077
  }
805
1078
  return n;
806
1079
  }
807
- function u(e, t) {
1080
+ function f(e, t) {
808
1081
  let n = 0, i = 0;
809
1082
  for (let a of r().nuclei) {
810
- let o = c(a, e, t), s = Math.max(a.radius, .01), l = Math.exp(-r().softmaxBeta * o / (s * s));
811
- n += l * a.paletteBias, i += l;
1083
+ let o = u(a, e, t), s = Math.max(a.radius, .01), c = Math.exp(-r().softmaxBeta * o / (s * s));
1084
+ n += c * a.paletteBias, i += c;
812
1085
  }
813
1086
  return i > 1e-4 ? n / i : .5;
814
1087
  }
815
- function d() {
1088
+ function p() {
816
1089
  if (r().nuclei.length === 0) return .5;
817
1090
  let e = r().nuclei.map((e) => e.radius).sort((e, t) => e - t);
818
1091
  return e[Math.floor(e.length / 2)];
819
1092
  }
820
- function f(e, t) {
1093
+ function m(e, t) {
821
1094
  return {
822
1095
  x: e,
823
1096
  y: t,
824
- radius: d(),
825
- paletteBias: u(e, t),
1097
+ radius: p(),
1098
+ paletteBias: f(e, t),
826
1099
  valueBias: 0,
827
1100
  driftRadius: .012,
828
1101
  driftPhase: Math.random() * Math.PI * 2
829
1102
  };
830
1103
  }
831
- function p(t) {
1104
+ function h(t) {
832
1105
  let i = e.value;
833
1106
  if (!i) return;
834
- let { x: o, y: c } = s(t, i);
835
- a !== null && (r().nuclei[a].x = Math.max(0, Math.min(1, o)), r().nuclei[a].y = Math.max(0, Math.min(1, c))), n.setCursor(o, c, 1);
1107
+ let { x: o, y: u } = l(t, i);
1108
+ a !== null && (r().nuclei[a].x = Math.max(0, Math.min(1, o)), r().nuclei[a].y = Math.max(0, Math.min(1, u))), n.setCursor(o, u, 1), n.injectVelocity && s !== null && c !== null && n.injectVelocity(o - s, u - c), s = o, c = u;
836
1109
  }
837
- function m() {
838
- a === null && n.clearCursor();
1110
+ function g() {
1111
+ s = null, c = null, a === null && n.clearCursor();
839
1112
  }
840
- function h(t) {
1113
+ function _(t) {
841
1114
  let n = e.value;
842
1115
  if (!n) return;
843
- let { x: i, y: c } = s(t, n), u = l(i, c);
844
- if (t.altKey && u === null) {
1116
+ let { x: i, y: s } = l(t, n), c = d(i, s);
1117
+ if (t.altKey && c === null) {
845
1118
  if (r().nuclei.length >= 6) return;
846
- r().nuclei.push(f(i, c)), t.preventDefault();
1119
+ r().nuclei.push(m(i, s)), t.preventDefault();
847
1120
  return;
848
1121
  }
849
- if ((t.shiftKey || t.button === 2) && u !== null) {
1122
+ if ((t.shiftKey || t.button === 2) && c !== null) {
850
1123
  if (r().nuclei.length <= 1) return;
851
- r().nuclei.splice(u, 1), t.preventDefault();
1124
+ r().nuclei.splice(c, 1), t.preventDefault();
852
1125
  return;
853
1126
  }
854
- u !== null && t.button === 0 && (a = u, o = t.pointerId, n.setPointerCapture(t.pointerId), t.preventDefault());
1127
+ c !== null && t.button === 0 && (a = c, o = t.pointerId, n.setPointerCapture(t.pointerId), t.preventDefault());
855
1128
  }
856
- function g(t) {
1129
+ function v(t) {
857
1130
  let n = e.value;
858
1131
  if (n && o !== null && t.pointerId === o) {
859
1132
  try {
@@ -862,22 +1135,143 @@ function Ae(e, t, n) {
862
1135
  o = null, a = null;
863
1136
  }
864
1137
  }
865
- function _(t) {
1138
+ function y(t) {
866
1139
  let n = e.value;
867
1140
  if (!n) return;
868
1141
  let i = {
869
1142
  clientX: t.clientX,
870
1143
  clientY: t.clientY
871
- }, a = n.getBoundingClientRect(), o = l((i.clientX - a.left) / Math.max(a.width, 1), (i.clientY - a.top) / Math.max(a.height, 1));
1144
+ }, a = n.getBoundingClientRect(), o = d((i.clientX - a.left) / Math.max(a.width, 1), (i.clientY - a.top) / Math.max(a.height, 1));
872
1145
  o !== null && r().nuclei.length > 1 && (r().nuclei.splice(o, 1), t.preventDefault());
873
1146
  }
874
- return S(() => {
1147
+ return O(() => {
875
1148
  let t = e.value;
876
- t && (t.addEventListener("pointermove", p), t.addEventListener("pointerleave", m), t.addEventListener("pointerdown", h), t.addEventListener("pointerup", g), t.addEventListener("pointercancel", g), t.addEventListener("contextmenu", _));
877
- }), x(() => {
1149
+ t && (t.addEventListener("pointermove", h), t.addEventListener("pointerleave", g), t.addEventListener("pointerdown", _), t.addEventListener("pointerup", v), t.addEventListener("pointercancel", v), t.addEventListener("contextmenu", y));
1150
+ }), D(() => {
878
1151
  let t = e.value;
879
- t && (t.removeEventListener("pointermove", p), t.removeEventListener("pointerleave", m), t.removeEventListener("pointerdown", h), t.removeEventListener("pointerup", g), t.removeEventListener("pointercancel", g), t.removeEventListener("contextmenu", _));
1152
+ t && (t.removeEventListener("pointermove", h), t.removeEventListener("pointerleave", g), t.removeEventListener("pointerdown", _), t.removeEventListener("pointerup", v), t.removeEventListener("pointercancel", v), t.removeEventListener("contextmenu", y));
880
1153
  }), { isDragging: () => a };
881
1154
  }
882
1155
  //#endregion
883
- export { ke as Aurora, u as DEFAULT_AURORA_CONFIG, c as MAX_NUCLEI, l as MAX_STOPS, X as createAurora, p as cssToOklch, U as deriveAurora, me as deriveScene, R as flattenPalette, B as hexToOklchStop, f as oklchStopToHex, d as oklchToLinear, z as paletteToCssGradient, $ as resolveRenderMode, Q as useAurora, Ae as useCursorInteraction };
1156
+ //#region src/components/custom/aurora/composables/atoms.ts
1157
+ var X = {};
1158
+ function Z(e, t, n) {
1159
+ return e + (t - e) * n;
1160
+ }
1161
+ var Q = {
1162
+ saturation: {
1163
+ calm: .85,
1164
+ vivid: 1.2
1165
+ },
1166
+ valueVariance: {
1167
+ calm: .04,
1168
+ vivid: .14
1169
+ },
1170
+ breathDepth: {
1171
+ calm: .03,
1172
+ vivid: .08
1173
+ },
1174
+ temperatureShift: {
1175
+ calm: .15,
1176
+ vivid: .5
1177
+ }
1178
+ }, nt = {
1179
+ still: {
1180
+ nucleiDrift: 0,
1181
+ paletteDrift: 0,
1182
+ warpDrift: 0,
1183
+ breathDepth: 0
1184
+ },
1185
+ breathing: {
1186
+ nucleiDrift: 0,
1187
+ paletteDrift: 0,
1188
+ warpDrift: 0,
1189
+ breathDepth: .05
1190
+ },
1191
+ drifting: {
1192
+ nucleiDrift: .015,
1193
+ paletteDrift: .015,
1194
+ warpDrift: .008,
1195
+ breathDepth: .05
1196
+ }
1197
+ }, rt = .61803398875;
1198
+ function $(e, n = "composed") {
1199
+ let r = Math.max(1, Math.min(6, Math.round(e))), i = [
1200
+ [.33, .33],
1201
+ [.67, .67],
1202
+ [.67, .33],
1203
+ [.33, .67],
1204
+ [.5, .5],
1205
+ [.5, .33]
1206
+ ], a = (e) => {
1207
+ switch (n) {
1208
+ case "scattered": {
1209
+ let n = e * rt * Math.PI * 2, i = .12 + .3 * Math.sqrt((e + .5) / r);
1210
+ return [t(.5 + i * Math.cos(n), .1, .9), t(.5 + i * Math.sin(n), .1, .9)];
1211
+ }
1212
+ case "centred": {
1213
+ if (r === 1) return [.5, .5];
1214
+ let t = e / r * Math.PI * 2, n = .12;
1215
+ return [.5 + n * Math.cos(t), .5 + n * Math.sin(t)];
1216
+ }
1217
+ default: return i[e % i.length];
1218
+ }
1219
+ };
1220
+ return Array.from({ length: r }, (e, t) => {
1221
+ let [n, i] = a(t);
1222
+ return {
1223
+ x: n,
1224
+ y: i,
1225
+ radius: .5,
1226
+ paletteBias: r === 1 ? 0 : t / (r - 1),
1227
+ valueBias: (t % 2 == 0 ? 1 : -1) * .05,
1228
+ driftRadius: .045,
1229
+ driftPhase: t * 2.4 % (Math.PI * 2)
1230
+ };
1231
+ });
1232
+ }
1233
+ function it(e) {
1234
+ return e < .4 ? "fbm" : e < .75 ? "hybrid" : "cellular";
1235
+ }
1236
+ function at(e, n) {
1237
+ let r = t(n, 0, 1);
1238
+ e.warpAmount = t(Z(.2, .6, r), 0, .6), e.warpScale = t(Z(1, 2.6, r), .5, 3), e.warpMode = it(r), e.noiseOctaves = r < .5 ? 3 : r < .85 ? 4 : 5;
1239
+ }
1240
+ function ot(e, n, r) {
1241
+ let i = t(r, 0, 1);
1242
+ switch (n) {
1243
+ case "watercolor":
1244
+ e.wetEdge = i, e.granulation = i * .8;
1245
+ break;
1246
+ case "pastel":
1247
+ e.strokeAmount = i, e.canvasGrain = i * .05;
1248
+ break;
1249
+ case "oil":
1250
+ case "vangogh":
1251
+ e.strokeAmount = i, e.impasto = i, e.canvasGrain = i * .06;
1252
+ break;
1253
+ case "oil-pastel":
1254
+ case "crayon":
1255
+ e.strokeAmount = i, e.canvasGrain = i * .05;
1256
+ break;
1257
+ }
1258
+ }
1259
+ function st(e = X) {
1260
+ let n = {
1261
+ ...y,
1262
+ palette: y.palette.map((e) => ({ ...e })),
1263
+ nuclei: y.nuclei.map((e) => ({ ...e })),
1264
+ flow: { ...y.flow }
1265
+ }, r = e.colorEnergy === void 0 ? void 0 : t(e.colorEnergy, 0, 1);
1266
+ if (e.seed !== void 0 && (n.palette = F(e.seed, {
1267
+ harmony: e.harmony ?? "analogous",
1268
+ stopCount: 4,
1269
+ temperatureShift: r === void 0 ? 0 : Z(Q.temperatureShift.calm, Q.temperatureShift.vivid, r)
1270
+ })), r !== void 0 && (n.saturation = t(Z(Q.saturation.calm, Q.saturation.vivid, r), .6, 1.3), n.valueVariance = t(Z(Q.valueVariance.calm, Q.valueVariance.vivid, r), 0, .3), n.breathDepth = t(Z(Q.breathDepth.calm, Q.breathDepth.vivid, r), 0, .15)), e.zones !== void 0 && (n.nuclei = $(e.zones.count, e.zones.arrangement ?? "composed")), e.noise !== void 0 && at(n, e.noise), e.medium !== void 0 && (n.medium = e.medium.kind, (e.medium.kind === "vangogh" || e.medium.kind === "oil-pastel") && (n.strokeOrient = "tensor"), e.medium.kind !== "smooth" && e.medium.amount !== void 0 && ot(n, e.medium.kind, e.medium.amount)), e.motion !== void 0) {
1271
+ let r = nt[e.motion];
1272
+ n.nucleiDrift = t(r.nucleiDrift, 0, .05), n.paletteDrift = t(r.paletteDrift, 0, .04), n.warpDrift = t(r.warpDrift, 0, .015), n.breathDepth = t(e.motion === "still" ? 0 : Math.max(n.breathDepth, r.breathDepth), 0, .15);
1273
+ }
1274
+ return e.interactivity !== void 0 && (n.interactivity = { ...e.interactivity }), n;
1275
+ }
1276
+ //#endregion
1277
+ export { et as Aurora, X as DEFAULT_ATOMS, y as DEFAULT_AURORA_CONFIG, _ as MAX_NUCLEI, v as MAX_STOPS, K as createAurora, C as cssToOklch, F as deriveAurora, N as flattenPalette, we as hexToOklchStop, $ as nucleiPrior, S as oklchStopToHex, b as oklchToLinear, P as paletteToCssGradient, st as resolveAtoms, J as resolveRenderMode, Y as useAurora, tt as useCursorInteraction };