@mkbabb/glass-ui 4.0.1 → 4.2.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 (606) hide show
  1. package/dist/{CardAction-CW7a0m8M.js → CardAction-DlVP2evz.js} +72 -53
  2. package/dist/{CommandShortcut-DvZNQ_HF.js → CommandShortcut-By-VQcLO.js} +25 -14
  3. package/dist/{ConfiguratorRow-BR4l-ixA.js → ConfiguratorRow-YMXdINHx.js} +14 -14
  4. package/dist/ContextMenuContent-CMWn6huB.js +90 -0
  5. package/dist/{ContextMenuSubContent-xol4sD2q.js → ContextMenuSubContent-DvwWCUc0.js} +16 -16
  6. package/dist/{DataTable-CuKwF7wr.js → DataTable-C4tG3eIJ.js} +4 -4
  7. package/dist/DialogContent-D8U9ZfWS.js +121 -0
  8. package/dist/DialogFooter-DoLy0ewI.js +80 -0
  9. package/dist/DialogScrollContent-BMekN7gq.js +93 -0
  10. package/dist/{FadingScroll-DI6hQhT9.js → FadingScroll-P8B30rAD.js} +1 -1
  11. package/dist/HoverCardContent-DAmGYglo.js +88 -0
  12. package/dist/{HoverPopover-CbqbNwgU.js → HoverPopover-DYpBOzzF.js} +1 -1
  13. package/dist/{IconChip-BTD-nay5.js → IconChip-CQa7XZYc.js} +7 -5
  14. package/dist/{IconTooltip-Bl8gFZ8d.js → IconTooltip-_df8M3xm.js} +1 -1
  15. package/dist/Input-CyU4_hIC.js +57 -0
  16. package/dist/{MetricBadge-RHdBP72w.js → MetricBadge-DQt_dGah.js} +1 -1
  17. package/dist/{Notification-VUwexFUf.js → Notification-BlSPJhtZ.js} +4 -4
  18. package/dist/NumberFieldContent-Z3VxGu_6.js +140 -0
  19. package/dist/PagerDots-CYlqVFd4.js +196 -0
  20. package/dist/PaperBackdrop-DYWgrnh8.js +42 -0
  21. package/dist/{PopoverContent-DL7E6vJP.js → PopoverContent-BuKyHZSz.js} +2 -2
  22. package/dist/{Progress-DctG61iZ.js → Progress-C9SsdwIZ.js} +1 -1
  23. package/dist/{ScrollingText-yOpBzFYq.js → ScrollingText-DY3W1Jfh.js} +2 -2
  24. package/dist/SelectScrollDownButton-3mFHX_R-.js +260 -0
  25. package/dist/{SelectSeparator-DXswVdRz.js → SelectSeparator-CNRGG7Ew.js} +1 -1
  26. package/dist/Separator-Cf9-JFgi.js +44 -0
  27. package/dist/Switch-uf-UKPA2.js +49 -0
  28. package/dist/{Toaster-Bw4xR8xV.js → Toaster-ozrsR4mP.js} +2 -2
  29. package/dist/{ToggleGroupItem-pEM9vIhs.js → ToggleGroupItem-BpzaVzNm.js} +6 -6
  30. package/dist/TooltipProvider-QQVM_80r.js +109 -0
  31. package/dist/animated-digit.js +2 -2
  32. package/dist/api/index.d.ts +16 -32
  33. package/dist/api/types-extra.d.ts +33 -0
  34. package/dist/api.js +2 -2
  35. package/dist/aurora.js +629 -269
  36. package/dist/badge-Bt14yURi.js +72 -0
  37. package/dist/badge.js +1 -1
  38. package/dist/border-progress.js +49 -57
  39. package/dist/button-D1W9PrzI.js +130 -0
  40. package/dist/button.js +1 -1
  41. package/dist/canvas.js +2 -3
  42. package/dist/card.js +1 -1
  43. package/dist/carousel.js +235 -106
  44. package/dist/chipVariants-BJQ0I2kB.js +18 -0
  45. package/dist/collapsible.js +1 -1
  46. package/dist/color-B9jZdhJX.js +116 -0
  47. package/dist/color.js +2 -2
  48. package/dist/command.js +1 -1
  49. package/dist/completion-seal.d.ts +1 -0
  50. package/dist/completion-seal.js +89 -0
  51. package/dist/components/custom/aurora/Aurora.vue.d.ts +10 -5
  52. package/dist/components/custom/aurora/composables/color.d.ts +13 -0
  53. package/dist/components/custom/aurora/composables/frameLoop.d.ts +9 -0
  54. package/dist/components/custom/aurora/composables/glSetup.d.ts +1 -1
  55. package/dist/components/custom/aurora/composables/runtime.d.ts +12 -0
  56. package/dist/components/custom/aurora/composables/uniformBridgeWGPU.d.ts +12 -3
  57. package/dist/components/custom/aurora/composables/wgpuSetup.d.ts +8 -0
  58. package/dist/components/custom/aurora/constants/presets.d.ts +47 -0
  59. package/dist/components/custom/aurora/constants/renderMode.d.ts +16 -8
  60. package/dist/components/custom/aurora/constants/shaders/aurora-mediums.wgsl.d.ts +1 -0
  61. package/dist/components/custom/aurora/constants/shaders/aurora.wgsl.d.ts +1 -1
  62. package/dist/components/custom/aurora/index.d.ts +1 -1
  63. package/dist/components/custom/border-progress/composables/spectrum-walk.d.ts +25 -0
  64. package/dist/components/custom/border-progress/composables/useBorderSpectrum.d.ts +14 -18
  65. package/dist/components/custom/border-progress/index.d.ts +1 -1
  66. package/dist/components/custom/completion-seal/CompletionSeal.vue.d.ts +21 -0
  67. package/dist/components/custom/completion-seal/composables/useCompletionSeal.d.ts +37 -0
  68. package/dist/components/custom/completion-seal/constants.d.ts +46 -0
  69. package/dist/components/custom/completion-seal/index.d.ts +4 -0
  70. package/dist/components/custom/concentric/Concentric.vue.d.ts +15 -12
  71. package/dist/components/custom/concentric/composables/concentricGLSetup.d.ts +12 -0
  72. package/dist/components/custom/concentric/composables/concentricWGPUSetup.d.ts +13 -0
  73. package/dist/components/custom/concentric/composables/levelField.d.ts +50 -0
  74. package/dist/components/custom/concentric/composables/uniformBridgeWGPU.d.ts +7 -3
  75. package/dist/components/custom/concentric/composables/useConcentric.d.ts +3 -3
  76. package/dist/components/custom/concentric/constants.d.ts +69 -24
  77. package/dist/components/custom/concentric/index.d.ts +2 -2
  78. package/dist/components/custom/concentric/shaders/concentric.glsl.d.ts +2 -2
  79. package/dist/components/custom/concentric/shaders/concentric.wgsl.d.ts +1 -1
  80. package/dist/components/custom/configurator/Configurator.vue.d.ts +23 -2
  81. package/dist/components/custom/configurator/index.d.ts +1 -1
  82. package/dist/components/custom/confirm-dialog/ConfirmDialog.vue.d.ts +3 -3
  83. package/dist/components/custom/constellation/Constellation.vue.d.ts +5 -4
  84. package/dist/components/custom/constellation/composables/constellationGLSetup.d.ts +10 -0
  85. package/dist/components/custom/constellation/composables/constellationWGPUSetup.d.ts +19 -0
  86. package/dist/components/custom/constellation/composables/uniformBridgeWGPU.d.ts +66 -0
  87. package/dist/components/custom/constellation/composables/useConstellation.d.ts +3 -10
  88. package/dist/components/custom/constellation/constants.d.ts +33 -0
  89. package/dist/components/custom/constellation/constellationField.d.ts +31 -1
  90. package/dist/components/custom/constellation/constellationInteraction.d.ts +23 -14
  91. package/dist/components/custom/constellation/constellationRender.d.ts +25 -0
  92. package/dist/components/custom/constellation/constellationTypes.d.ts +38 -0
  93. package/dist/components/custom/constellation/constellationWell.d.ts +15 -0
  94. package/dist/components/custom/constellation/index.d.ts +5 -5
  95. package/dist/components/custom/constellation/shaders/constellation-lines.glsl.d.ts +2 -0
  96. package/dist/components/custom/constellation/shaders/constellation-lines.wgsl.d.ts +1 -0
  97. package/dist/components/custom/constellation/shaders/constellation-points.glsl.d.ts +2 -0
  98. package/dist/components/custom/constellation/shaders/constellation-points.wgsl.d.ts +1 -0
  99. package/dist/components/custom/deck/DeckPager.vue.d.ts +29 -0
  100. package/dist/components/custom/deck/composables/useDeck.d.ts +27 -0
  101. package/dist/components/custom/deck/composables/useDeckKeyboard.d.ts +25 -0
  102. package/dist/components/custom/deck/composables/useDeckSpring.d.ts +22 -0
  103. package/dist/components/custom/deck/constants.d.ts +14 -0
  104. package/dist/components/custom/deck/index.d.ts +8 -0
  105. package/dist/components/custom/dock/DockDropdownTrigger.vue.d.ts +4 -3
  106. package/dist/components/custom/dock/DockIconButton.vue.d.ts +22 -0
  107. package/dist/components/custom/dock/DockPopoverTrigger.vue.d.ts +19 -0
  108. package/dist/components/custom/dock/DockStack.vue.d.ts +78 -0
  109. package/dist/components/custom/dock/GlassDock.vue.d.ts +21 -3
  110. package/dist/components/custom/dock/composables/dockMorphContext.d.ts +18 -32
  111. package/dist/components/custom/dock/composables/dockMorphMeasure.d.ts +78 -67
  112. package/dist/components/custom/dock/composables/index.d.ts +3 -0
  113. package/dist/components/custom/dock/composables/railProjection.d.ts +45 -0
  114. package/dist/components/custom/dock/composables/useDockContextSilhouette.d.ts +192 -0
  115. package/dist/components/custom/dock/composables/useDockFission.d.ts +158 -0
  116. package/dist/components/custom/dock/composables/useDockItemDrag.d.ts +18 -0
  117. package/dist/components/custom/dock/composables/useDockSearch.d.ts +85 -0
  118. package/dist/components/custom/dock/composables/useDockShellProps.d.ts +49 -0
  119. package/dist/components/custom/dock/constants.d.ts +32 -17
  120. package/dist/components/custom/dock/index.d.ts +8 -2
  121. package/dist/components/custom/dot-flow-field/DotFlowField.vue.d.ts +15 -11
  122. package/dist/components/custom/dot-flow-field/composables/flowField.d.ts +69 -6
  123. package/dist/components/custom/dot-flow-field/composables/flowGLProgram.d.ts +8 -0
  124. package/dist/components/custom/dot-flow-field/composables/flowSetupGL.d.ts +9 -0
  125. package/dist/components/custom/dot-flow-field/composables/flowSetupGLFlow.d.ts +12 -0
  126. package/dist/components/custom/dot-flow-field/composables/flowSetupWGPU.d.ts +7 -0
  127. package/dist/components/custom/dot-flow-field/composables/uniformBridgeWGPU.d.ts +50 -9
  128. package/dist/components/custom/dot-flow-field/composables/useDotFlowField.d.ts +6 -5
  129. package/dist/components/custom/dot-flow-field/composables/useFlowParticles.d.ts +23 -9
  130. package/dist/components/custom/dot-flow-field/constants.d.ts +92 -20
  131. package/dist/components/custom/dot-flow-field/index.d.ts +2 -2
  132. package/dist/components/custom/dot-flow-field/shaders/flow-field.compute.wgsl.d.ts +1 -1
  133. package/dist/components/custom/dot-flow-field/shaders/flow-field.glsl.d.ts +31 -18
  134. package/dist/components/custom/dot-flow-field/shaders/flow-field.render.wgsl.d.ts +2 -1
  135. package/dist/components/custom/dot-matrix/DotMatrix.vue.d.ts +43 -0
  136. package/dist/components/custom/dot-matrix/composables/dotMatrixField.d.ts +89 -0
  137. package/dist/components/custom/dot-matrix/composables/uniformBridgeWGPU.d.ts +61 -0
  138. package/dist/components/custom/dot-matrix/composables/useDotMatrix.d.ts +34 -0
  139. package/dist/components/custom/dot-matrix/composables/useDotSphere.d.ts +34 -0
  140. package/dist/components/custom/dot-matrix/constants.d.ts +72 -0
  141. package/dist/components/custom/dot-matrix/index.d.ts +5 -0
  142. package/dist/components/custom/dot-matrix/shaders/dot-matrix.glsl.d.ts +4 -0
  143. package/dist/components/custom/dot-matrix/shaders/dot-matrix.wgsl.d.ts +1 -0
  144. package/dist/components/custom/easing/EasingPicker.vue.d.ts +2 -2
  145. package/dist/components/custom/easing/composables/useEasingPicker.d.ts +5 -0
  146. package/dist/components/custom/expandable-container/ExpandableContainer.vue.d.ts +11 -0
  147. package/dist/components/custom/fourier-field/FourierField.vue.d.ts +44 -47
  148. package/dist/components/custom/fourier-field/composables/fourierFieldGLSetup.d.ts +24 -0
  149. package/dist/components/custom/fourier-field/composables/fourierFieldWGPUSetup.d.ts +31 -0
  150. package/dist/components/custom/fourier-field/composables/uniformBridgeWGPU.d.ts +56 -0
  151. package/dist/components/custom/fourier-field/composables/useFourierField.d.ts +61 -37
  152. package/dist/components/custom/fourier-field/constants.d.ts +114 -4
  153. package/dist/components/custom/fourier-field/index.d.ts +23 -18
  154. package/dist/components/custom/fourier-field/shaders/fourier-field.compute.wgsl.d.ts +1 -0
  155. package/dist/components/custom/fourier-field/shaders/fourier-field.glsl.d.ts +4 -0
  156. package/dist/components/custom/fourier-field/shaders/fourier-field.render.wgsl.d.ts +1 -0
  157. package/dist/components/custom/goo-blob/GooBlob.vue.d.ts +9 -1
  158. package/dist/components/custom/goo-blob/composables/easing.d.ts +1 -0
  159. package/dist/components/custom/goo-blob/composables/useBlobSatellites.d.ts +1 -0
  160. package/dist/components/custom/goo-blob/constants.d.ts +26 -1
  161. package/dist/components/custom/goo-blob/index.d.ts +1 -1
  162. package/dist/components/custom/goo-blob/shaders/metaball-noise.wgsl.d.ts +1 -0
  163. package/dist/components/custom/goo-blob/shaders/metaball-palette.wgsl.d.ts +1 -0
  164. package/dist/components/custom/goo-blob/shaders/metaball-uniforms.glsl.d.ts +1 -1
  165. package/dist/components/custom/goo-blob/shaders/metaball.wgsl.d.ts +1 -1
  166. package/dist/components/custom/goo-blob/types.d.ts +86 -1
  167. package/dist/components/custom/goo-dot-matrix/GooDotMatrix.vue.d.ts +49 -0
  168. package/dist/components/custom/goo-dot-matrix/composables/gooDotLattice.d.ts +24 -0
  169. package/dist/components/custom/goo-dot-matrix/composables/gooDotSetup.d.ts +79 -0
  170. package/dist/components/custom/goo-dot-matrix/composables/uniformBridgeWGPU.d.ts +45 -0
  171. package/dist/components/custom/goo-dot-matrix/composables/useGooDotMatrix.d.ts +31 -0
  172. package/dist/components/custom/goo-dot-matrix/constants.d.ts +115 -0
  173. package/dist/components/custom/goo-dot-matrix/index.d.ts +5 -0
  174. package/dist/components/custom/goo-dot-matrix/shaders/goo-dot.frag.d.ts +13 -0
  175. package/dist/components/custom/goo-dot-matrix/shaders/goo-dot.wgsl.d.ts +4 -0
  176. package/dist/components/custom/goo-filter/GooFilter.vue.d.ts +22 -0
  177. package/dist/components/custom/goo-filter/index.d.ts +1 -0
  178. package/dist/components/custom/handmark/brush.d.ts +17 -17
  179. package/dist/components/custom/handmark/constants.d.ts +28 -8
  180. package/dist/components/custom/handmark/geometry.d.ts +34 -1
  181. package/dist/components/custom/handmark/index.d.ts +1 -1
  182. package/dist/components/custom/icon-chip/types.d.ts +10 -0
  183. package/dist/components/custom/labeled-field/LabeledField.vue.d.ts +21 -2
  184. package/dist/components/custom/metric-stack/MetricRow.vue.d.ts +15 -0
  185. package/dist/components/custom/pager-dots/PagerDots.vue.d.ts +9 -0
  186. package/dist/components/custom/pager-dots/index.d.ts +2 -0
  187. package/dist/components/custom/pager-dots/pagerWindow.d.ts +9 -0
  188. package/dist/components/custom/paper-backdrop/PaperBackdrop.vue.d.ts +18 -0
  189. package/dist/components/custom/paper-grid/PaperGrid.vue.d.ts +42 -0
  190. package/dist/components/custom/paper-grid/composables/paperGrid.d.ts +105 -0
  191. package/dist/components/custom/paper-grid/composables/paperGridGLSetup.d.ts +18 -0
  192. package/dist/components/custom/paper-grid/composables/paperGridWGPUSetup.d.ts +21 -0
  193. package/dist/components/custom/paper-grid/composables/uniformBridgeWGPU.d.ts +16 -0
  194. package/dist/components/custom/paper-grid/composables/usePaperGrid.d.ts +31 -0
  195. package/dist/components/custom/paper-grid/constants.d.ts +95 -0
  196. package/dist/components/custom/paper-grid/index.d.ts +5 -0
  197. package/dist/components/custom/paper-grid/shaders/paper-grid.glsl.d.ts +4 -0
  198. package/dist/components/custom/paper-grid/shaders/paper-grid.wgsl.d.ts +1 -0
  199. package/dist/components/custom/search/FuzzySearch.vue.d.ts +8 -2
  200. package/dist/components/custom/search/SearchBar.vue.d.ts +8 -0
  201. package/dist/components/custom/search/index.d.ts +2 -0
  202. package/dist/components/custom/search/searchVariants.d.ts +19 -0
  203. package/dist/components/custom/selectable-chip/SelectableChip.vue.d.ts +50 -0
  204. package/dist/components/custom/selectable-chip/chipVariants.d.ts +39 -0
  205. package/dist/components/custom/selectable-chip/index.d.ts +2 -0
  206. package/dist/components/custom/selectable-chip/selectableChipVariants.d.ts +15 -0
  207. package/dist/components/custom/split-chars/SplitChars.vue.d.ts +39 -0
  208. package/dist/components/custom/split-chars/index.d.ts +1 -0
  209. package/dist/components/custom/tabs/SegmentedTabs.vue.d.ts +8 -6
  210. package/dist/components/custom/tabs/constants.d.ts +28 -2
  211. package/dist/components/custom/timeline/ScrubberTimeline.vue.d.ts +16 -0
  212. package/dist/components/custom/timeline/geometry.d.ts +0 -8
  213. package/dist/components/custom/toggle-chip/index.d.ts +15 -4
  214. package/dist/components/custom/watercolor-dot/WatercolorDot.vue.d.ts +32 -17
  215. package/dist/components/custom/watercolor-dot/useWatercolorBlob.d.ts +10 -5
  216. package/dist/components/ui/_shared/index.d.ts +2 -0
  217. package/dist/components/ui/_shared/useControlSize.d.ts +46 -0
  218. package/dist/components/ui/_shared/useSurfaceAxis.d.ts +17 -3
  219. package/dist/components/ui/badge/Badge.vue.d.ts +14 -0
  220. package/dist/components/ui/badge/index.d.ts +1 -0
  221. package/dist/components/ui/button/Button.vue.d.ts +1 -0
  222. package/dist/components/ui/button/index.d.ts +1 -1
  223. package/dist/components/ui/card/Card.vue.d.ts +46 -1
  224. package/dist/components/ui/card/ScrollCard.vue.d.ts +1 -1
  225. package/dist/components/ui/card/index.d.ts +1 -1
  226. package/dist/components/ui/carousel/CarouselContent.vue.d.ts +2 -4
  227. package/dist/components/ui/carousel/CarouselNext.vue.d.ts +1 -1
  228. package/dist/components/ui/carousel/CarouselPrevious.vue.d.ts +1 -1
  229. package/dist/components/ui/command/CommandDialog.vue.d.ts +9 -3
  230. package/dist/components/ui/command/CommandList.vue.d.ts +3 -0
  231. package/dist/components/ui/context-menu/ContextMenuContent.vue.d.ts +5 -1
  232. package/dist/components/ui/dialog/DialogContent.vue.d.ts +9 -0
  233. package/dist/components/ui/drawer/Drawer.vue.d.ts +33 -14
  234. package/dist/components/ui/drawer/constants.d.ts +8 -6
  235. package/dist/components/ui/drawer/index.d.ts +12 -2
  236. package/dist/components/ui/dropdown-menu/DropdownMenuContent.vue.d.ts +4 -0
  237. package/dist/components/ui/hover-card/HoverCardContent.vue.d.ts +3 -0
  238. package/dist/components/ui/input/Input.vue.d.ts +3 -0
  239. package/dist/components/ui/number-field/NumberFieldInput.vue.d.ts +6 -1
  240. package/dist/components/ui/select/SelectContent.vue.d.ts +12 -0
  241. package/dist/components/ui/slider/Slider.vue.d.ts +9 -0
  242. package/dist/components/ui/switch/Switch.vue.d.ts +2 -0
  243. package/dist/components/ui/textarea/Textarea.vue.d.ts +3 -0
  244. package/dist/components/ui/tooltip/TooltipContent.vue.d.ts +3 -0
  245. package/dist/composables/color/index.d.ts +2 -0
  246. package/dist/composables/color/useAccentTone.d.ts +34 -0
  247. package/dist/composables/dom/index.d.ts +1 -0
  248. package/dist/composables/dom/useDragVelocity.d.ts +46 -0
  249. package/dist/composables/glass/useVizChoreography.d.ts +123 -0
  250. package/dist/composables/glass/wave/index.d.ts +4 -0
  251. package/dist/composables/glass/wave/waveField.d.ts +115 -0
  252. package/dist/composables/glass/wave/waveField.glsl.d.ts +1 -0
  253. package/dist/composables/glass/wave/waveField.wgsl.d.ts +1 -0
  254. package/dist/composables/glass/webgl/createCanvasLifecycle.d.ts +105 -2
  255. package/dist/composables/glass/webgl/shaders/flow.wgsl.d.ts +1 -0
  256. package/dist/composables/glass/webgl/useWebGLCanvas.d.ts +40 -2
  257. package/dist/composables/glass/webgpu/index.d.ts +2 -1
  258. package/dist/composables/glass/webgpu/useGpuSubstrate.d.ts +65 -7
  259. package/dist/composables/glass/webgpu/useWebGPUCanvas.d.ts +50 -2
  260. package/dist/composables/glass/webgpu/webgpuDevice.d.ts +42 -0
  261. package/dist/composables/motion/core/index.d.ts +7 -0
  262. package/dist/composables/motion/core/useHaptic.d.ts +48 -0
  263. package/dist/composables/motion/core/writeVelocityWeight.d.ts +31 -0
  264. package/dist/composables/motion/index.d.ts +2 -0
  265. package/dist/composables/motion/scrollReader.d.ts +25 -0
  266. package/dist/composables/motion/springPresets.d.ts +19 -1
  267. package/dist/composables/motion/suite.d.ts +2 -2
  268. package/dist/composables/motion/useBloomUp.d.ts +97 -0
  269. package/dist/composables/motion/useCelebrationBurst.d.ts +50 -0
  270. package/dist/composables/motion/useCharStagger.d.ts +44 -0
  271. package/dist/composables/motion/useDockCtaReceive.d.ts +29 -22
  272. package/dist/composables/motion/useGooMorph.d.ts +72 -0
  273. package/dist/composables/motion/useLiquidFlex.d.ts +18 -0
  274. package/dist/composables/motion/useLiquidMorph.d.ts +108 -0
  275. package/dist/composables/motion/useLiquidPress.d.ts +12 -0
  276. package/dist/composables/motion/useLiquidReveal.d.ts +7 -4
  277. package/dist/composables/motion/useMorphField.d.ts +157 -0
  278. package/dist/composables/motion/useScrollChrome.d.ts +58 -0
  279. package/dist/composables/motion/useScrollPin.d.ts +49 -0
  280. package/dist/composables/motion/useScrollScene.d.ts +67 -0
  281. package/dist/composables/motion/useScrollTrigger.d.ts +67 -0
  282. package/dist/composables/motion/useSpringPress.d.ts +5 -5
  283. package/dist/composables/motion/useStaggerReveal.d.ts +24 -0
  284. package/dist/composables/sidebar/index.d.ts +4 -1
  285. package/dist/composables/sidebar/types.d.ts +68 -3
  286. package/dist/composables/sidebar/useClickDelegate.d.ts +4 -0
  287. package/dist/composables/sidebar/useLazyLoader.d.ts +5 -0
  288. package/dist/composables/sidebar/useScrollTo.d.ts +5 -0
  289. package/dist/composables/virtual/index.d.ts +4 -0
  290. package/dist/composables/virtual/useVirtualSectionWindow.d.ts +72 -0
  291. package/dist/composables/virtual/useWindowedStore.d.ts +43 -0
  292. package/dist/composables/virtual/virtualSectionLayout.d.ts +103 -0
  293. package/dist/concentric.js +552 -309
  294. package/dist/configurator.js +2 -2
  295. package/dist/confirm-dialog.js +62 -47
  296. package/dist/constellation.js +635 -235
  297. package/dist/context-menu.js +2 -2
  298. package/dist/controls.js +2 -2
  299. package/dist/createCanvasLifecycle-CuCrGRfq.js +173 -0
  300. package/dist/dark.js +1 -1
  301. package/dist/data-table.js +1 -1
  302. package/dist/deck.d.ts +1 -0
  303. package/dist/deck.js +105 -0
  304. package/dist/dialog.js +4 -3
  305. package/dist/dock.js +1320 -631
  306. package/dist/dockContext-gFVS2gMg.js +18 -0
  307. package/dist/dom.js +7 -6
  308. package/dist/dot-flow-field.js +1268 -387
  309. package/dist/dot-matrix.d.ts +1 -0
  310. package/dist/dot-matrix.js +774 -0
  311. package/dist/dotMatrixField-CRO0ZFk4.js +48 -0
  312. package/dist/drawer.js +106 -96
  313. package/dist/{dropdown-menu-CofSZuk8.js → dropdown-menu-CCfcCQ68.js} +83 -79
  314. package/dist/dropdown-menu.js +1 -1
  315. package/dist/easing.js +188 -176
  316. package/dist/expandable-container.js +43 -21
  317. package/dist/fading-scroll.js +1 -1
  318. package/dist/flow.glsl-DqEGRpl4.js +4 -0
  319. package/dist/forms.js +64 -62
  320. package/dist/fourier-field.js +1069 -178
  321. package/dist/fourier-math.js +1 -1
  322. package/dist/glass-panel.js +2 -2
  323. package/dist/glass-ui.css +1 -1
  324. package/dist/glass-ui.js +164 -119
  325. package/dist/goo-blob.js +175 -1230
  326. package/dist/goo-dot-matrix.d.ts +1 -0
  327. package/dist/goo-dot-matrix.js +811 -0
  328. package/dist/handmark.js +137 -94
  329. package/dist/header-ribbon.js +1 -1
  330. package/dist/hover-card.js +1 -1
  331. package/dist/hover-popover.js +1 -1
  332. package/dist/icon-chip.js +1 -1
  333. package/dist/icon-tooltip.js +1 -1
  334. package/dist/index.d.ts +5 -0
  335. package/dist/instrument-chassis.js +1 -1
  336. package/dist/keyboard.js +1 -1
  337. package/dist/label.js +1 -1
  338. package/dist/labeled-field.js +70 -60
  339. package/dist/metric-badge.js +1 -1
  340. package/dist/metric-cell.js +1 -1
  341. package/dist/metric-stack.js +12 -3
  342. package/dist/motion-core.js +59 -34
  343. package/dist/motion-curves.js +30 -14
  344. package/dist/motion.js +231 -96
  345. package/dist/notification.js +1 -1
  346. package/dist/number-field.js +1 -1
  347. package/dist/pager-dots.js +2 -91
  348. package/dist/paper-backdrop.js +1 -1
  349. package/dist/paper-grid.d.ts +1 -0
  350. package/dist/paper-grid.js +761 -0
  351. package/dist/paperGrid-CXCSMbZ1.js +191 -0
  352. package/dist/popover.js +1 -1
  353. package/dist/{presets-BW2b-fr1.js → presets-QvYgnFIZ.js} +14 -13
  354. package/dist/{procedural-color.glsl-CIFsJhwI.js → procedural-color.wgsl-C1Y5PkM6.js} +2 -2
  355. package/dist/progress.js +1 -1
  356. package/dist/pulse.js +2 -2
  357. package/dist/reactive.js +2 -2
  358. package/dist/{useCanvas2D-B8w_E3Rr.js → resolveCanvasColor-DsSJk06I.js} +16 -6
  359. package/dist/scrollReader-rc3Ns_NM.js +42 -0
  360. package/dist/scrolling-text.js +1 -1
  361. package/dist/search.js +162 -306
  362. package/dist/select.js +3 -3
  363. package/dist/selectable-chip.d.ts +1 -0
  364. package/dist/selectable-chip.js +48 -0
  365. package/dist/separator.js +1 -1
  366. package/dist/{sheet-tZMg0E0w.js → sheet-DUCIoOe0.js} +4 -4
  367. package/dist/sheet.js +1 -1
  368. package/dist/sidebar.js +216 -114
  369. package/dist/slider-CcICPe3u.js +149 -0
  370. package/dist/slider.js +1 -1
  371. package/dist/sortable-list.js +3 -3
  372. package/dist/spectrum-walk-cBNSx_zm.js +33 -0
  373. package/dist/springPresets-DfoQ6XRY.js +64 -0
  374. package/dist/stacked-icons.js +54 -39
  375. package/dist/styles/animations.css +13 -3
  376. package/dist/styles/cards.css +271 -2
  377. package/dist/styles/completion-seal.css +131 -0
  378. package/dist/styles/components.css +1 -4
  379. package/dist/styles/configurator.css +221 -1
  380. package/dist/styles/critical-partition.mjs +66 -11
  381. package/dist/styles/deferred.css +4 -0
  382. package/dist/styles/dock/adaptive-legibility.css +119 -0
  383. package/dist/styles/dock/cta-seat.css +87 -0
  384. package/dist/styles/dock/density.css +167 -6
  385. package/dist/styles/dock/fission-bridge.css +552 -0
  386. package/dist/styles/dock/layers.css +90 -78
  387. package/dist/styles/dock/morph-bridge.css +67 -18
  388. package/dist/styles/dock/morph.css +57 -118
  389. package/dist/styles/dock/overflow.css +12 -0
  390. package/dist/styles/dock/search.css +68 -0
  391. package/dist/styles/dock/shape.css +250 -0
  392. package/dist/styles/dock/shell.css +24 -8
  393. package/dist/styles/dock/stack-rail.css +352 -0
  394. package/dist/styles/dock-controls/dark-mode-toggle.css +17 -4
  395. package/dist/styles/dock-controls/icon-button.css +46 -0
  396. package/dist/styles/dock-controls/triggers.css +23 -1
  397. package/dist/styles/dock-controls.css +18 -0
  398. package/dist/styles/dock.css +27 -2
  399. package/dist/styles/drawer.css +166 -0
  400. package/dist/styles/glass/accent-tone.css +66 -0
  401. package/dist/styles/glass/control-surfaces.css +167 -0
  402. package/dist/styles/glass/glass-atom.css +244 -0
  403. package/dist/styles/glass/glass-capsule.css +143 -0
  404. package/dist/styles/glass/glass-chip.css +215 -0
  405. package/dist/styles/glass/ladder.css +67 -17
  406. package/dist/styles/glass/liquid-enter.css +252 -0
  407. package/dist/styles/glass/liquid-morph.css +850 -0
  408. package/dist/styles/glass/material.css +70 -1
  409. package/dist/styles/glass/reveal.css +22 -2
  410. package/dist/styles/glass/rim.css +31 -9
  411. package/dist/styles/glass/surfaces.css +81 -150
  412. package/dist/styles/glass.css +21 -0
  413. package/dist/styles/icon-chip.css +45 -0
  414. package/dist/styles/index.css +26 -0
  415. package/dist/styles/jubilance.css +65 -0
  416. package/dist/styles/motion/morph-field.css +229 -0
  417. package/dist/styles/paper.css +230 -14
  418. package/dist/styles/scroll-choreography.css +135 -78
  419. package/dist/styles/scroll-chrome.css +123 -0
  420. package/dist/styles/scroll-driven.css +38 -0
  421. package/dist/styles/segmented-tabs.css +110 -40
  422. package/dist/styles/select.css +151 -0
  423. package/dist/styles/theme/bridges.css +22 -0
  424. package/dist/styles/theme/radius.css +18 -1
  425. package/dist/styles/tokens/color-radius.css +14 -4
  426. package/dist/styles/tokens/dark-arm.css +94 -12
  427. package/dist/styles/tokens/glass-deep.css +33 -4
  428. package/dist/styles/tokens/glass-fx.css +122 -1
  429. package/dist/styles/tokens/glass.css +226 -34
  430. package/dist/styles/tokens/light-dark.css +3 -3
  431. package/dist/styles/tokens/offsets.css +27 -0
  432. package/dist/styles/tokens/property-regs.css +277 -0
  433. package/dist/styles/tokens/scale-paper.css +86 -30
  434. package/dist/styles/tokens/scheme-motion.css +106 -133
  435. package/dist/styles/tokens/scheme-spring.css +239 -0
  436. package/dist/styles/tokens/scroll-tokens.css +15 -15
  437. package/dist/styles/tokens/shadow.css +73 -13
  438. package/dist/styles/tokens/sizing-config.css +110 -0
  439. package/dist/styles/tokens/sizing.css +55 -104
  440. package/dist/styles/tokens.css +14 -2
  441. package/dist/styles/typography/semantic.css +18 -5
  442. package/dist/styles/utilities/a11y-overrides.css +20 -5
  443. package/dist/styles/utilities/base-misc.css +52 -0
  444. package/dist/styles/utilities/btn.css +84 -3
  445. package/dist/styles/utilities/components.css +31 -6
  446. package/dist/styles/utilities/metal.css +26 -0
  447. package/dist/switch.js +1 -1
  448. package/dist/tabs.js +199 -182
  449. package/dist/timeline.js +241 -206
  450. package/dist/toast.js +1 -1
  451. package/dist/toggle-chip.js +14 -41
  452. package/dist/toggle-group.js +1 -1
  453. package/dist/tooltip.js +1 -1
  454. package/dist/typewriter.js +2 -2
  455. package/dist/uniformBridgeWGPU-BCse1SHU.js +1268 -0
  456. package/dist/{useAnimatedNumber-BfUq2bUM.js → useAnimatedNumber-BntskVdQ.js} +1 -1
  457. package/dist/{useConfiguratorState-DNlOh6c8.js → useConfiguratorState-22BcULzF.js} +41 -38
  458. package/dist/useControlSize-DAZ062x7.js +9 -0
  459. package/dist/useDockCtaReceive-DX7RDb6_.js +88 -0
  460. package/dist/{useDragMorph-DgxnsVJz.js → useDragMorph-DGLSewwB.js} +4 -4
  461. package/dist/useDragVelocity-CjcfX3dA.js +57 -0
  462. package/dist/useFuzzySearch-0fTYOhS4.js +177 -0
  463. package/dist/useGooMorph-Cs13NgYL.js +104 -0
  464. package/dist/useGpuSubstrate-Ceet70xq.js +347 -0
  465. package/dist/useHaptic-CPz5BT9I.js +134 -0
  466. package/dist/{useLiquidFlex-mZPMiNXh.js → useLiquidFlex-Cc6PjVJD.js} +8 -3
  467. package/dist/useLiquidPress-CtOQ8Z0_.js +43 -0
  468. package/dist/useMorphField-ogBM7ubT.js +141 -0
  469. package/dist/usePointerVelocityField-BrkqZFhj.js +91 -0
  470. package/dist/useScrollChrome-JuImi1DM.js +258 -0
  471. package/dist/{useSpringMount-q1amq1SJ.js → useSpringMount-DFxtzmkR.js} +1 -1
  472. package/dist/useSpringPress-VM9RgVq3.js +36 -0
  473. package/dist/{useSurfaceAxis-g0NloYEF.js → useSurfaceAxis-Ci7Dnaw2.js} +1 -1
  474. package/dist/{useTouchGate-CBZTD4SD.js → useTouchGate-B1Lf9XqN.js} +1 -1
  475. package/dist/{vendor-CKmzio-i.js → vendor-B2qOlYY3.js} +24 -11
  476. package/dist/virtual.d.ts +1 -0
  477. package/dist/virtual.js +225 -0
  478. package/dist/watercolor-dot.js +107 -74
  479. package/dist/writeVelocityWeight-Bh83xZfs.js +19 -0
  480. package/package.json +140 -9
  481. package/src/styles/animations.css +13 -3
  482. package/src/styles/cards.css +271 -2
  483. package/src/styles/completion-seal.css +131 -0
  484. package/src/styles/configurator.css +221 -1
  485. package/src/styles/critical-partition.mjs +66 -11
  486. package/src/styles/dock/adaptive-legibility.css +119 -0
  487. package/src/styles/dock/cta-seat.css +87 -0
  488. package/src/styles/dock/density.css +167 -6
  489. package/src/styles/dock/fission-bridge.css +552 -0
  490. package/src/styles/dock/layers.css +90 -78
  491. package/src/styles/dock/morph-bridge.css +67 -18
  492. package/src/styles/dock/morph.css +57 -118
  493. package/src/styles/dock/overflow.css +12 -0
  494. package/src/styles/dock/search.css +68 -0
  495. package/src/styles/dock/shape.css +250 -0
  496. package/src/styles/dock/shell.css +24 -8
  497. package/src/styles/dock/stack-rail.css +352 -0
  498. package/src/styles/dock-controls/dark-mode-toggle.css +17 -4
  499. package/src/styles/dock-controls/icon-button.css +46 -0
  500. package/src/styles/dock-controls/triggers.css +23 -1
  501. package/src/styles/dock-controls.css +18 -0
  502. package/src/styles/dock.css +27 -2
  503. package/src/styles/drawer.css +164 -0
  504. package/src/styles/glass/accent-tone.css +66 -0
  505. package/src/styles/glass/control-surfaces.css +167 -0
  506. package/src/styles/glass/glass-atom.css +244 -0
  507. package/src/styles/glass/glass-capsule.css +143 -0
  508. package/src/styles/glass/glass-chip.css +215 -0
  509. package/src/styles/glass/ladder.css +67 -17
  510. package/src/styles/glass/liquid-enter.css +252 -0
  511. package/src/styles/glass/liquid-morph.css +850 -0
  512. package/src/styles/glass/material.css +70 -1
  513. package/src/styles/glass/reveal.css +22 -2
  514. package/src/styles/glass/rim.css +31 -9
  515. package/src/styles/glass/surfaces.css +81 -150
  516. package/src/styles/glass.css +21 -0
  517. package/src/styles/icon-chip.css +45 -0
  518. package/src/styles/index.css +26 -0
  519. package/src/styles/jubilance.css +65 -0
  520. package/src/styles/motion/morph-field.css +229 -0
  521. package/src/styles/paper.css +230 -14
  522. package/src/styles/scroll-choreography.css +135 -78
  523. package/src/styles/scroll-chrome.css +123 -0
  524. package/src/styles/scroll-driven.css +38 -0
  525. package/src/styles/segmented-tabs.css +110 -36
  526. package/src/styles/select.css +151 -0
  527. package/src/styles/theme/bridges.css +22 -0
  528. package/src/styles/theme/radius.css +18 -1
  529. package/src/styles/tokens/color-radius.css +14 -4
  530. package/src/styles/tokens/dark-arm.css +94 -12
  531. package/src/styles/tokens/glass-deep.css +33 -4
  532. package/src/styles/tokens/glass-fx.css +122 -1
  533. package/src/styles/tokens/glass.css +226 -34
  534. package/src/styles/tokens/light-dark.css +3 -3
  535. package/src/styles/tokens/offsets.css +27 -0
  536. package/src/styles/tokens/property-regs.css +277 -0
  537. package/src/styles/tokens/scale-paper.css +86 -30
  538. package/src/styles/tokens/scheme-motion.css +106 -133
  539. package/src/styles/tokens/scheme-spring.css +239 -0
  540. package/src/styles/tokens/scroll-tokens.css +15 -15
  541. package/src/styles/tokens/shadow.css +73 -13
  542. package/src/styles/tokens/sizing-config.css +110 -0
  543. package/src/styles/tokens/sizing.css +55 -104
  544. package/src/styles/tokens.css +14 -2
  545. package/src/styles/typography/semantic.css +18 -5
  546. package/src/styles/utilities/a11y-overrides.css +20 -5
  547. package/src/styles/utilities/base-misc.css +52 -0
  548. package/src/styles/utilities/btn.css +84 -3
  549. package/src/styles/utilities/components.css +31 -6
  550. package/src/styles/utilities/metal.css +26 -0
  551. package/dist/ContextMenuContent-uWnVxDab.js +0 -85
  552. package/dist/DialogContent-DcKK8k9i.js +0 -103
  553. package/dist/DialogFooter-BZNX5aVr.js +0 -165
  554. package/dist/HoverCardContent-b2uaAMRR.js +0 -83
  555. package/dist/Input-h9v5LeXf.js +0 -55
  556. package/dist/NumberFieldContent-Cq7EzX-U.js +0 -138
  557. package/dist/PaperBackdrop-Vf3tlpvd.js +0 -31
  558. package/dist/SelectScrollDownButton-CrA8F0iV.js +0 -252
  559. package/dist/Separator-pKF0oA4M.js +0 -37
  560. package/dist/Switch-CwYNf8bo.js +0 -47
  561. package/dist/TooltipProvider-rzvnXz60.js +0 -104
  562. package/dist/badge-D9XWW4GT.js +0 -53
  563. package/dist/button-DYCUa-tH.js +0 -126
  564. package/dist/color-DweYl7pE.js +0 -86
  565. package/dist/components/custom/concentric/composables/ringField.d.ts +0 -44
  566. package/dist/components/custom/constellation/composables/useConstellationPointer.d.ts +0 -35
  567. package/dist/components/custom/constellation/constellationDraw.d.ts +0 -30
  568. package/dist/components/custom/dock/DockRail.vue.d.ts +0 -89
  569. package/dist/createCanvasLifecycle-CHD5XrDW.js +0 -82
  570. package/dist/dockContext-BNfyPLe9.js +0 -17
  571. package/dist/procedural-color.wgsl-CToRHugY.js +0 -224
  572. package/dist/resolveCanvasColor-CUyHr5Uu.js +0 -11
  573. package/dist/slider-DVb4HXU-.js +0 -142
  574. package/dist/springPresets-CFQGQfdl.js +0 -40
  575. package/dist/styles/dock/rail-extend.css +0 -397
  576. package/dist/useLiquidPress-kfb4KX79.js +0 -39
  577. package/dist/usePointerVelocityField-JbRd5CrM.js +0 -137
  578. package/dist/useRAFLoop-UYa4zoiN.js +0 -109
  579. package/dist/useSpringPress-BsqPr7FJ.js +0 -34
  580. package/src/styles/dock/rail-extend.css +0 -397
  581. /package/dist/{CollapsibleContent-68dEXIAl.js → CollapsibleContent-D7UNsXQr.js} +0 -0
  582. /package/dist/{InstrumentChassis-Bx0F_q5g.js → InstrumentChassis-UUJX0-mW.js} +0 -0
  583. /package/dist/{Label-C2ejNWeC.js → Label-B_HPqi_q.js} +0 -0
  584. /package/dist/{ModalOverlay-D1VrWyoi.js → ModalOverlay-DfAf7o15.js} +0 -0
  585. /package/dist/{SelectGroup-DwwjicPY.js → SelectGroup-Dcczq44n.js} +0 -0
  586. /package/dist/{_plugin-vue_export-helper-C1je1s0u.js → _plugin-vue_export-helper-gLzyemvl.js} +0 -0
  587. /package/dist/{compile-CQEFRTmx.js → compile-INR_tPk_.js} +0 -0
  588. /package/dist/{constants-BPOqYJAm.js → constants-BWj7DkMn.js} +0 -0
  589. /package/dist/{createContext-CpBNTRMI.js → createContext-Cd23P9j0.js} +0 -0
  590. /package/dist/{math-BINYmg5z.js → math-5RcIgCB4.js} +0 -0
  591. /package/dist/{menuItemVariants-D8VNHq22.js → menuItemVariants-DAXquKja.js} +0 -0
  592. /package/dist/{prng-Za0Rmgag.js → prng-B5tIXTtj.js} +0 -0
  593. /package/dist/{useGlobalDark-C28t0VWJ.js → useGlobalDark-B4HO9LXG.js} +0 -0
  594. /package/dist/{useIntersectionPause-Bj4sPkMh.js → useIntersectionPause-DO8Vl4gs.js} +0 -0
  595. /package/dist/{useInterval-CvK1mL92.js → useInterval-BVUI-sez.js} +0 -0
  596. /package/dist/{useKeyboardShortcuts-BXWXOZ-9.js → useKeyboardShortcuts-BEut54Tk.js} +0 -0
  597. /package/dist/{useResizeObserver-Bpoi7tRE.js → useResizeObserver-DdEn3v2f.js} +0 -0
  598. /package/dist/{useResolveTokenColor-vAylyR0x.js → useResolveTokenColor-ChOnCckA.js} +0 -0
  599. /package/dist/{useScrollProgress-D7bYU2RK.js → useScrollProgress-A0M6qRl7.js} +0 -0
  600. /package/dist/{useSortable-DHjtZSEd.js → useSortable-DQV_UNDw.js} +0 -0
  601. /package/dist/{useTextHighlight-CWEi25hf.js → useTextHighlight-Bma3UlKA.js} +0 -0
  602. /package/dist/{useTimer-BHJOKr35.js → useTimer-BBLGPXR8.js} +0 -0
  603. /package/dist/{useUserInvalidAria-y5namzeE.js → useUserInvalidAria-EmmwWPOl.js} +0 -0
  604. /package/dist/{useViewportReady-anbac3m6.js → useViewportReady-C9pYnn7K.js} +0 -0
  605. /package/dist/{vReveal-Czsb7zQC.js → vReveal-CT1KtwZA.js} +0 -0
  606. /package/dist/{vSpecular-CQFmLNzA.js → vSpecular-EX7loENl.js} +0 -0
package/dist/aurora.js CHANGED
@@ -1,15 +1,17 @@
1
- import { t as e } from "./_plugin-vue_export-helper-C1je1s0u.js";
2
- import { a as t, i as n, n as r, r as i, t as a } from "./procedural-color.glsl-CIFsJhwI.js";
3
- import { a as o, c as s, i as c, l, n as u, r as d, s as f, t as p } from "./procedural-color.wgsl-CToRHugY.js";
4
- import { n as m, t as h } from "./compile-CQEFRTmx.js";
5
- import { i as g, n as _, r as v, t as y } from "./presets-BW2b-fr1.js";
6
- import { a as ee, c as b, i as te, l as ne, o as x, t as S } from "./color-DweYl7pE.js";
7
- import { t as re } from "./useIntersectionPause-Bj4sPkMh.js";
8
- import { t as ie } from "./useScrollProgress-D7bYU2RK.js";
9
- import { computed as C, createElementBlock as ae, createElementVNode as oe, defineComponent as se, normalizeClass as ce, normalizeStyle as le, onBeforeUnmount as w, onMounted as T, openBlock as ue, readonly as de, ref as E, unref as D, watch as O } from "vue";
10
- import { interpolateHue as fe, rawOklabToOklch as pe, srgbToOKLab as me } from "@mkbabb/value.js";
1
+ import { t as e } from "./_plugin-vue_export-helper-gLzyemvl.js";
2
+ import { t } from "./usePointerVelocityField-BrkqZFhj.js";
3
+ import { a as n, c as r, i, l as a, n as o, o as s, r as c, s as l, t as u } from "./procedural-color.wgsl-C1Y5PkM6.js";
4
+ import { t as d } from "./flow.glsl-DqEGRpl4.js";
5
+ import { i as f, n as p, r as m, t as h } from "./useGpuSubstrate-Ceet70xq.js";
6
+ import { n as g, t as _ } from "./compile-INR_tPk_.js";
7
+ import { a as v, i as y, n as ee, o as te, r as ne, t as b } from "./presets-QvYgnFIZ.js";
8
+ import { a as re, c as x, i as ie, l as S, o as C, t as w } from "./color-B9jZdhJX.js";
9
+ import { t as ae } from "./useIntersectionPause-DO8Vl4gs.js";
10
+ import { t as oe } from "./useScrollProgress-A0M6qRl7.js";
11
+ import { computed as T, createElementBlock as se, createElementVNode as ce, defineComponent as le, normalizeClass as ue, normalizeStyle as de, onBeforeUnmount as fe, onMounted as pe, openBlock as me, readonly as he, ref as E, unref as D, watch as O } from "vue";
12
+ import { interpolateHue as ge, rawOklabToOklch as _e, srgbToOKLab as ve } from "@mkbabb/value.js";
11
13
  //#region src/components/custom/aurora/constants/shaders/aurora.vert.ts
12
- var he = "#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}", ge = "\nconst float CURL_EPS = 0.012;\n\n// Scalar fbm potential psi — the host's noise basis, wrapped so the curl operator is\n// basis-agnostic. A host carrying its own fbm defines this as: return fbm(p);\nfloat potentialFBM(vec2 p);\n\n// ∇×ψ in 2D — the divergence-free flow direction. Central finite differences of the\n// scalar potential give the partials; the cross-pairing (∂y, −∂x) is the 2D curl.\nvec2 curlFBM(vec2 p) {\n float dx = potentialFBM(p + vec2(CURL_EPS, 0.0)) - potentialFBM(p - vec2(CURL_EPS, 0.0));\n float dy = potentialFBM(p + vec2(0.0, CURL_EPS)) - potentialFBM(p - vec2(0.0, CURL_EPS));\n // ψ-gradient is (dx, dy)/(2·eps); the curl rotates it 90°: (∂ψ/∂y, −∂ψ/∂x).\n vec2 g = vec2(dx, dy) / (2.0 * CURL_EPS);\n return vec2(g.y, -g.x);\n}\n", _e = "// ── 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", ve = "// ── 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", ye = "// Khronos PBR-Neutral tonemap (hue + saturation preserving)\nvec3 aces(vec3 color) {\n const float startCompression = 0.8 - 0.04;\n const float desaturation = 0.15;\n float x = min(color.r, min(color.g, color.b));\n float offset = x < 0.08 ? x - 6.25 * x * x : 0.04;\n color -= offset;\n float peak = max(color.r, max(color.g, color.b));\n if (peak < startCompression) return clamp(color, 0.0, 1.0);\n float d = 1.0 - startCompression;\n float newPeak = 1.0 - d * d / (peak + d - startCompression);\n color *= newPeak / peak;\n float g = 1.0 - 1.0 / (desaturation * (peak - newPeak) + 1.0);\n return clamp(mix(color, newPeak * vec3(1.0), g), 0.0, 1.0);\n}\n", be = "// ── 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). The along-spine value\n // modulation is the loaded-brush light/dark that gives each stroke its OWN directional\n // luminance band — the structure-tensor signal a low-contrast color field (a uniform-hue\n // oil field) has no other source for (the §4.2 anisotropy lift on the flat-palette case).\n c *= 1.0 + streak * streakAmp * 3.2;\n\n // Cross-stroke ridge/valley shading — a catch-light along the spine centre, darkening to\n // the stroke edges. This is the strongest DIRECTIONAL luminance cue: it carves a bright\n // ridge down the stroke's length and dark grooves between adjacent strokes, so a field of\n // parallel strokes reads as parallel light/dark bands (the high-A van-Gogh signature) even\n // where the underlying colour is flat. s.edgeN is 0 at the edge, 1 at the spine.\n float ridgeShade = smoothstep(0.0, 0.55, s.edgeN);\n c *= 1.0 + (ridgeShade - 0.5) * 0.30;\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 // The specular glint is gated to ACCUMULATED-thick paint (height²) — a per-fine-stroke\n // sharp glint on every thin fill dab read as bright isotropic SPECKLE that buried the\n // directional stroke bands (the §4.2 anisotropy / §4.3 slope sink on the low-contrast\n // oil field). Only where real impasto has built up does the raked glint fire, so the\n // highlight tracks the macro ridges, not the fine speckle.\n float specGate = clamp(height, 0.0, 1.0);\n lit += uImpasto * relief * spec * specGate * 0.5 * 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 FAR less angular jitter (the field already orients them\n // — over-jittering the tensor strokes scatters their directions and the rendered\n // field reads isotropic, the §4.2 anisotropy floor). The flow-oriented (non-tensor)\n // strokes keep the full jitter (they have no per-cell orientation source).\n float jScale = (uStrokeOrient == 1) ? 0.15 : 1.0;\n float angJ = (hash21(cc + seed + 11.0) - 0.5) * 0.9 * jScale; // +/- 0.45/0.07 rad\n float curlScale = (uStrokeOrient == 1) ? 0.30 : 1.0;\n float localCurl = (fbm(center * (2.6 + seed * 0.11) + seed * 1.9) - 0.5) * 0.55 * uFlowCurl * curlScale;\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", xe = "// ── Van-Gogh — DEDICATED atomic-dab body (W-AUR-VANGOGH-REBUILD) ─────────────\n// REBUILT FROM FIRST PRINCIPLES. The prior path routed van-Gogh through the shared\n// oil paintStrokeMedium cascade — four DENSE layers of LONG-THIN tensor-oriented\n// strokes (lenMul up to 5.2, widMul 0.22) hugging a continuous coherent field. Packed\n// that tight, thin strokes that follow continuous iso-bands MERGE into smooth flowing\n// filaments — the \"marbled flow-bands\" read (a spirograph/fingerprint, not impasto),\n// AND the per-cell 8-tap structure tensor × 4 layers × 9 cells = ~324 sampleBase/pixel\n// made it run at ~4fps. Van Gogh's sky is NOT continuous flow — it is DISCRETE, SHORT,\n// LOADED comma dabs, separated by visible ground, queued into swirl ROWS.\n//\n// The rebuild is a purpose-built two-layer DAB pass, disjoint from the oil cascade:\n// • orientation from the analytic SWIRL flowField (van-Gogh skies ARE swirl rows) +\n// per-cell angular jitter — NOT the expensive per-cell tensor (the lag source, and\n// the marble source: a jittered discrete dab can NOT merge into a continuous\n// filament the way a tensor-locked thin stroke does).\n// • SHORT comma dabs (len/wid ≈ 2.6 : 1 — a loaded crescent mark, not a 10:1\n// filament), so each reads as a SEPARABLE atom.\n// • SPARSE placement with visible inter-dab ground (density ≈ 0.42), over a darkened\n// swirl underpainting (the Starry-Night visible ground the bright dabs read over).\n// • a single sampleBase per cell + OKLCh broken-color jitter (pigment shimmer);\n// full-height impasto crown per dab (each catches its own raked glint).\n// This is ~2 layers × 9 cells × ~1 sampleBase ≈ 20/pixel (a ~16× cost cut, ~60fps), and\n// jittered short dabs CANNOT marble.\n\n// One atomic comma dab — a CLEAN analytic crescent SDF (NOT the oil curvedStroke).\n// The oil curvedStroke's spine-projection + rounded end-caps + fwidth-AA + bristle\n// edge each leave a thin sub-pixel skirt, and a field of those skirts over open ground\n// abuts into a faint network of SEAM LINES. A van-Gogh dab needs none of that\n// machinery — it is a short oriented capsule (rounded line segment) whose half-width\n// tapers along its length (the loaded-head → dragged-tail comma). The capsule SDF is\n// derivative-free (smoothstep AA against the analytic distance), so no fwidth skirt, no\n// best-of-9 seam. Returns a StrokeHit (coverage/color/alongT/edgeN) for paintOver.\n// cellSize — dab cell pitch (UV units); lenMul/widMul — dab aspect off the cell;\n// density — placement gate (sparse → visible gaps); seed — layer decorrelation.\nStrokeHit vangoghDab(vec2 p, float cellSize, float lenMul, float widMul,\n float density, float jitterAmt, float angJitter,\n vec2 flow, float t, float seed) {\n vec2 cell = floor(p / cellSize);\n StrokeHit best = noHit();\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 // Sparse density gate — leaves visible canvas ground between dabs (atomicity).\n if (hash21(cc * 1.7 + seed * 0.3) > density) continue;\n vec2 jit = (hash22(cc + seed) - 0.5) * jitterAmt;\n vec2 center = (cc + 0.5 + jit) * cellSize;\n\n // Orientation: the analytic swirl flow at the cell center + per-cell angular\n // jitter (so adjacent dabs FAN — the divisionist scatter that keeps the dabs from\n // locking into perfectly-concentric rows) + a flow-noise curl wobble.\n float ang = (hash21(cc + seed + 11.0) - 0.5) * angJitter;\n float curl = (fbm(center * 3.1 + seed * 1.9) - 0.5) * (0.18 + 0.3 * uFlowCurl);\n vec2 dir = rotateDir(safeDir(flow), ang + curl);\n\n // SHORT comma — len ≈ the cell pitch (a contained dab, not a tiling filament).\n float len = cellSize * lenMul * (0.82 + 0.30 * hash21(cc + seed + 23.0));\n float halfW = max(cellSize * widMul * (0.86 + 0.28 * hash21(cc + seed + 41.0)), 0.010);\n\n // Local dab frame: along = projection onto the dab axis (centred 0), perp = cross.\n vec2 rel = p - center;\n float along = dot(rel, dir);\n float perp = dot(rel, vec2(-dir.y, dir.x));\n // The comma TAPER: half-width peaks ~1/3 from the loaded head and drags to a fine\n // tail (the asymmetric crescent). u in [0,1] along the dab from head to tail.\n float halfLen = len * 0.5;\n float u = clamp((along + halfLen) / max(len, 1e-5), 0.0, 1.0);\n float head = smoothstep(0.0, 0.30, u); // rounded loaded head\n float tail = pow(clamp((1.0 - u) / 0.70, 0.0, 1.0), 1.3); // dragged thin tail\n float prof = clamp(min(head, tail) * 1.05, 0.0, 1.0);\n float wNow = halfW * prof;\n // A gentle comma CURVE — bow the spine sideways so the mark reads as a crescent.\n float bowAmt = (hash21(cc + seed + 53.0) - 0.5) * halfW * 1.4;\n float spine = bowAmt * sin(u * PI); // 0 at ends, max mid\n float d = abs(perp - spine);\n // Derivative-free AA: a fixed fraction of the dab width (no fwidth skirt). Coverage\n // is 1 in the dab body, smoothly 0 at the edge; outside the [head,tail] span wNow→0\n // so the dab self-terminates with no end-cap blob.\n float aa = max(halfW * 0.45, 0.002);\n float cov = (wNow > 1e-5) ? (1.0 - smoothstep(wNow - aa, wNow + aa, d)) : 0.0;\n if (cov < 0.004) continue;\n\n // One sampleBase per cell + OKLCh broken-color jitter (pigment patch shimmer). The\n // dab pigment is chroma-boosted (saturate3 1.22) AND value-lifted (×1.14) at the\n // SOURCE — the loaded brush mark deposits BRIGHTER + more saturated pigment than the\n // base field, so the loaded-mark pixels lift BOTH the §4.1 colourfulness AND the field\n // mean-luma. The higher mean raises the gap-fraction threshold (meanLum·0.6) so the\n // moderate-depth inter-dab ground falls below it (the gap-fraction atomicity read is\n // DECOUPLED from the ground floor depth — the ground can stay only moderately dark, so\n // the §4.3 slope is NOT over-steepened, while the bright dabs manufacture the gap). The\n // dark ground is untouched, so this lifts C + gap WITHOUT a band-conflicting deep ground.\n vec3 colMid = brokenColorJitter(\n saturate3(sampleBase(center, t), 1.22),\n hash21(cc + seed + 89.0),\n hash21(cc * 2.3 + seed + 97.0),\n 1.0\n );\n\n // edgeN: 1 at the spine, 0 at the edge (the impasto crown reads it).\n float edgeN = (wNow > 1e-5) ? clamp(1.0 - d / wNow, 0.0, 1.0) : 0.0;\n if (cov > best.coverage) {\n best = StrokeHit(cov, colMid, u, (wNow > 1e-5 ? (perp - spine) / wNow : 0.0), edgeN);\n }\n }\n }\n return best;\n}\n\nvec3 mediumVangogh(vec3 col, vec2 p, float t) {\n vec3 result = col;\n float height = 0.0;\n\n // The swirl flow basis (van-Gogh sky = swirl rows). Computed ONCE per pixel.\n vec2 flow = flowField(p, t);\n\n // Dab cell pitch in UV [0,1]. CHUNKY, SEPARABLE marks — the big layer is ~1/14 of\n // the frame so it reads as a distinct loaded brush dab (not a fine speckle, not a\n // screen-spanning filament). uStrokeScale (authored ~110) modulates the pitch\n // INVERSELY (higher = finer dabs) around the ~0.07 chunky default; clamped so the\n // dab never collapses to noise or balloons past one screen. uStrokeAnisotropy\n // stretches the comma aspect (longer, more directional marks).\n float pitch = clamp(7.0 / max(uStrokeScale, 20.0), 0.044, 0.12);\n // The dab LENGTH ≈ the cell pitch (lenAniso ~1.2-1.7) so a dab sits WITHIN its cell\n // and leaves visible ground around it — a longer dab (lenMul > 2) spans neighbour\n // cells and tiles the field into continuous coverage (no gaps, the gap-fraction floor\n // miss + the marble). Narrow width keeps it a directional crescent, not a wide shard.\n // lenAniso/widAniso — the comma aspect. The W-AUR-VANGOGH-CONFIG reconcile lengthened\n // the comma (1.25-1.75 → 1.5-2.0) and THINNED it (0.40-0.30 → 0.32-0.24): the rebuilt\n // dabs measured §4.2 structure-tensor anisotropy A=0.699, BELOW the reference band\n // floor 0.732 (the dabs read slightly too short-fat → locally isotropic, dragging mean\n // A down). A LONGER, THINNER comma hugs the swirl-flow tangent more strongly so the\n // rendered field reads coherent the way the tensor metric measures it (A lift), while\n // the thinner width LEAVES MORE inter-dab ground (the gap-fraction atomicity read is\n // preserved, not closed). The length stays ≈ the cell pitch so each dab remains a\n // CONTAINED separable mark, not a tiling filament.\n float lenAniso = mix(2.1, 2.7, uStrokeAnisotropy); // comma length / cell pitch (a contained dab)\n float widAniso = mix(0.23, 0.16, uStrokeAnisotropy); // comma width / cell pitch (a thin loaded crescent)\n\n // Layer 1 — the big swirl-row dabs: the loaded gestural marks, SPARSE so the dark\n // underpainting shows BETWEEN them (the atomicity read — visible inter-dab ground).\n // hardness 0.74 — the loaded brush deposits with a SOFT shoulder, so adjacent dabs\n // feather instead of razor-clipping into hard polygon shards. jitterAmt 0.85 fully\n // scatters the cell centres so the placement GRID never reads as a periodic seam.\n // angJitter EASED 0.55 → 0.38 (the reconcile A lift): a tighter angular spread keeps\n // adjacent dabs more flow-aligned (coherent) while the per-cell curl wobble still\n // breaks the perfectly-concentric-rows read (the dabs FAN gently, not lock to a grid).\n // angJitter eased 0.38 → 0.26 + hardness 0.74 → 0.84 (the §4.2 A lift): a tighter angular\n // spread keeps adjacent dabs strongly flow-aligned (the structure-tensor coherence) and a\n // sharper edge gives each dab a STRONGER directional gradient (a soft dab's faint edge\n // reads weakly anisotropic). The per-cell curl wobble still breaks the perfectly-\n // concentric-rows read, so the dabs FAN gently rather than locking to a grid.\n StrokeHit dBig = vangoghDab(p, pitch, lenAniso, widAniso,\n 0.30, 0.50, 0.08, flow, t, 1.3);\n // streakFreq raised 14 → 34 (the §4.3 slope fix): a high-frequency streak runs ALONG the\n // dab spine, injecting fine-scale luma energy that flattens the too-steep β WITHOUT\n // adding new isotropic marks (the along-spine streak stays directional, so the §4.2\n // anisotropy the long-thin dab carries is preserved — the band-conflict resolution).\n paintOver(result, height, dBig, 48.0, 0.34,\n uImpasto * 0.75 * uStrokeAmount, 0.84, 1.3, 1.0 /* full crown */);\n\n // Layer 2 — two-thirds-pitch accent dabs (the divisionist sparkle between the big\n // marks — denser placement but still contained dabs, so dark canvas survives between).\n // angJitter eased 0.5 → 0.34 (same A-lift reasoning — the accent dabs queue along the\n // flow rather than scattering isotropically); hardness 0.78 → 0.86 sharpens the gradient.\n StrokeHit dSml = vangoghDab(p + vec2(-5.1, 8.4), pitch * 0.62, lenAniso * 0.95,\n widAniso * 0.95, 0.36, 0.60, 0.20, flow, t, 4.1);\n paintOver(result, height, dSml, 42.0, 0.22,\n uImpasto * 0.55 * uStrokeAmount, 0.86, 4.1, 1.0);\n\n // Layer 3 — the FINE divisionist flecks (W-AUR-VANGOGH-CONFIG reconcile). The two-layer\n // field read as a few BIG cobalt dabs over a smooth empty teal ground (the side-by-side\n // against starry-night-crop.png: the painting is DENSE small varied flecks, not sparse\n // large marks). The flecks are LONG·THIN directional commas hugging the swirl tangent\n // (the A-preserving fleck — short fat flecks read isotropic and drag §4.2 down). Sparse\n // density 0.30 keeps the inter-fleck ground open (the gap-fraction atomicity read).\n // The flecks are FINE (≈0.42× pitch) directional commas, tightly flow-aligned (angJitter\n // 0.22) and SPARSE (0.30) — the divisionist sparkle between the loaded marks that keeps\n // the inter-dab ground open (atomicity) while adding the small-mark mid-band energy. Thin\n // + flow-aligned so the §4.2 anisotropy the long dabs carry survives the added small marks.\n StrokeHit dFleck = vangoghDab(p + vec2(8.7, -3.2), pitch * 0.42, lenAniso * 1.0,\n widAniso * 0.68, 0.30, 0.85, 0.22, flow, t, 7.7);\n paintOver(result, height, dFleck, 30.0, 0.16,\n uImpasto * 0.40 * uStrokeAmount, 0.80, 7.7, 1.0);\n\n // ── Flow-aligned GROUND swirl-GROOVING (W-AUR-VANGOGH-CONFIG reconcile) ──\n // The bare ground between the dabs was a SMOOTH low-frequency teal gradient — the §4.3\n // power-spectrum slope ran too STEEP (β≈−2.0: too much low-frequency nuclei-field energy\n // vs the high band). Van Gogh's actual sky has the swirl BRUSHWORK everywhere, so groove\n // the OPEN GROUND with a flow-aligned ripple. It is SUBTRACTIVE-biased (it only darkens,\n // never brightens the ground), so it DEEPENS the inter-dab valleys (MORE gap-fraction —\n // atomicity preserved/strengthened) while injecting the across-flow mid-band luma energy\n // that flattens β. Gated to the open ground (low height) via the (1-height) gate so it\n // never lifts/erodes the loaded dab bodies (the dab read is untouched). Across the swirl\n // FLOW axis so the grooves run WITH the sky's brushwork direction.\n // The ground swirl-combing — a FIXED-global-angle directional grain on the open ground.\n // A flow-FOLLOWING comb varies its direction with the swirl across the readback window,\n // so its blurred structure tensor averages toward isotropic (no §4.2 lift). A comb at one\n // GLOBAL angle (van-Gogh's dominant diagonal stroke grain) is COHERENT over the whole\n // window ⇒ every open-ground pixel reads the SAME direction (the §4.2 mean-anisotropy lift\n // the swirl comb could not reach). Two resolved bands (≈22 + ≈44 across the global axis ⇒\n // ~21px + ~11px at 464px, both in the §4.3 fit window) also feed the β flatten. Gated to\n // the open ground (low height) so the loaded dabs are untouched; SUBTRACTIVE-biased so the\n // inter-dab valleys deepen (gap-fraction preserved, not filled).\n vec2 gdir = normalize(vec2(0.82, 0.57)); // the dominant global stroke grain axis\n float gacross = dot(p, vec2(-gdir.y, gdir.x));\n float groundGate = 1.0 - smoothstep(0.05, 0.28, height);\n float groove = sin(gacross * 22.0) + 0.5 * sin(gacross * 44.0 + 1.3);\n result *= 1.0 - 0.16 * groundGate * (0.5 - 0.5 * groove);\n\n // Canvas tooth (fine linen weave — base relief the raking light catches).\n float tooth1 = vnoise(p * 220.0);\n float tooth2 = vnoise(p * 220.0 * vec2(0.6, 2.4) + 37.0);\n float tooth = (0.6 * tooth1 + 0.4 * tooth2) - 0.5;\n result *= 1.0 + tooth * 0.06 * uCanvasGrain;\n\n // Relight the accumulated impasto height (raking light catches each dab's crown —\n // the visible impasto ridge that makes a dab read as a RAISED loaded mark).\n float canvasBase = tooth * 0.06 * 0.5;\n result = relightImpasto(result, height, canvasBase);\n\n // The visible Starry-Night GROUND — the open canvas between dabs (low accumulated\n // height) darkens toward the deep underpainting, so the bright impasto dabs read as\n // SEPARABLE atomic marks over a darker field (the dab↔ground contrast the user's\n // \"reads as strokes\" bar needs, AND the painterly-statistics gap-fraction floor — the\n // dark inter-dab ground must register as a measurable fraction of the field). 0.30\n // floor over a WIDER smoothstep (0..0.40): deep enough that the open gaps read as the\n // dark Starry-Night sky between marks (clearing the 4% gap floor) while the gradual\n // ramp keeps the gaps reading as flowing underpainting, not the hard black ink LINES a\n // narrow near-black step (0.26 / 0.0..0.32) produced.\n // The smoothstep FLOOR starts at 0.10 (not 0): a dab's faint AA coverage TAIL deposits\n // a sliver of height, and where many tails align along the swirl flow those slivers\n // accumulate into a faint continuous height ridge that read as thin curved LINES in the\n // ground. Holding the ground full-dark until a real dab CORE (height > 0.10) lifts out\n // keeps the open field clean — only the loaded dab bodies emerge, the tails stay ground.\n result *= mix(0.115, 1.0, smoothstep(0.085, 0.31, height));\n\n // The van-Gogh chroma — a light OKLCh punch. EASED to 0.92 (off 1.04) in the\n // W-AUR-VANGOGH-CONFIG reconcile: the rebuilt discrete-dab register's per-dab OKLCh\n // broken-color jitter (the ±16° hue + ±chroma swing per cell) lifts the field's\n // colourfulness on its own, so the prior 1.04 punch on TOP overshot the §4.1\n // reference band (rendered C=100.5 vs the painting's measured 70.67 ± 15/+25 →\n // ceiling 95.67 — neon, not Starry Night's rich-but-not-garish blue/gold). The 0.92\n // demote pulls the rendered colourfulness back IN-band while the broken-color jitter\n // keeps the per-dab pigment variation (the discrete-stroke read is untouched — this\n // is a chroma scale, not a dab-geometry change). Band-anchored on the real painting\n // (proof:aurora-arresting), NOT a widened band.\n result = saturate3(result, 1.0);\n return result;\n}\n\n", Se = "StrokeProfile 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 340.0, // toothScale — W-AUR-VANGOGH-CONFIG reconcile: FINER 240 → 340. Oil's §4.3 β sat\n // right AT the −1.45 band ceiling (±0.02 frame variance straddling\n // it, surfaced once the vangogh bands went green and the spec reached\n // the oil β assert). MEASURED on the live GPU: ADDING fine high-\n // frequency tooth energy steepens oil's power-spectrum slope (more\n // negative — coarsening it the other way pushed β SHALLOWER to\n // −1.33). A finer, stronger tooth moves β to a safe distance inside\n // the −5/3 band [−1.85, −1.45]. Oil-only (oil-pastel sets 280).\n 0.14, // toothAmp — raised 0.09 → 0.14 (same oil-β steepen: more fine-grain high band).\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.92, // densityFill — oil fills DENSELY with the directional low-relief fill so the\n // flat-colour regions between the macro strokes are TEXTURED with\n // flow-aligned fine strokes (the §4.2 anisotropy needs every pixel\n // to carry directional gradient; a flat untextured region reads as\n // an isotropic/degenerate tensor and drags mean A down). The fill\n // is now directional + low-streak, so dense coverage textures\n // WITHOUT the prior round-dab speckle.\n 1.0 // groundFloor — oil keeps the full base (no ground darken; fills everywhere)\n );\n\n // W-AUR-VANGOGH-REBUILD — the van-Gogh medium NO LONGER routes through this oil\n // StrokeProfile cascade. It is a DEDICATED atomic-dab body (mediumVangogh / vangoghDab\n // below) — the prior profile-driven cascade (four dense layers of long-thin\n // tensor-oriented strokes) merged into the \"marbled flow-bands\" read and ran at ~4fps.\n // profileFor now serves ONLY the oil + oil-pastel stroke mediums; the MEDIUM_VANGOGH\n // case is removed (no caller). MEDIUM_VANGOGH stays #define'd (the main() dispatch +\n // isPainterlyStroke still key off uMedium==5).\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 = 0; // tapered directional smear — a round dab reads locally\n // isotropic (steep β / low A); the creamy oil-pastel mark\n // is a broad DIRECTIONAL smear, which carries the §4.2\n // coherence + adds the directional mid-band the §4.3 slope\n // needs (the round-dab profile rolled β off to −2.8)\n prof.bristleAmp = 0.10; // smooth creamy edges (waxy pastel, not bristle)\n prof.streakFreq = 18.0; // finer internal smear — adds the mid/high-band the smooth\n // round-dab profile lacked (the β-too-steep fix); the\n // directional along-spine streak flattens the §4.3 slope\n // without crossing the strokes\n prof.streakAmp = 0.20;\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 = 0.80; // the waxy chroma — eased off 1.16 so the rendered C clears\n // the §4.1 band ceiling with margin while the K-M subtractive\n // overlap path (paintOverOklab) keeps the chroma OFF the grey floor\n prof.hardness = 0.58; // creamy but the strokes still REGISTER — a too-soft blend\n // (0.42) smeared the marks into the smooth colour zones and\n // the field rolled off too steep (β≈−2.9, a near-gradient);\n // a crisper deposit keeps the mid/high-band the −5/3 slope needs\n prof.densityBig = 0.62; // broad directional coverage — but NOT over-dense: too many\n // overlapping creamy strokes point in slightly-varied\n // directions and the local §4.2 coherence DROPS (over-dense\n // measured A=0.57 < the 0.62-density A=0.63). The mid-density\n // flow-aligned deposit is the coherence sweet spot.\n prof.densityMed = 0.78;\n prof.densitySml = 0.90;\n prof.energyGrade = 0.80; // a strong Starry-Night length cascade — varies stroke length\n // by luma/coherence so the dab-size energy spreads across\n // scales (the −5/3 cascade the uniform-length 0.0 grade lacked,\n // the §4.3 β-too-steep fix). Not the full van-Gogh 1.0 (oil-\n // pastel keeps its creamy broad character).\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", 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\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 // The blend weight is a low-power curve (A^0.35) so even MODERATE-coherence zones queue\n // strongly along the tangent (a flowing van-Gogh band reads coherent the way the\n // structure-tensor metric measures it; a linear A under-orients the mid-band and the\n // rendered field reads too isotropic — the §4.2 anisotropy lift). The flat-zone (A≈0)\n // relax to the fallback survives (0^0.35=0), so tensor noise never reads as jitter.\n float blendW = pow(A, 0.28);\n vec2 dir = normalize(mix(fallbackDir, tangent, blendW) + 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", we = "// ── 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.\n" + Se + "\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 // W-AUR-STUDIO D5 — the −5/3 radii respacing was MEASURED + REVERTED (recorded, not\n // silently struck). The φ-adjacent geometric candidate (2.4 / 1.45 / 0.87, a fixed\n // ≈1.66× step) moved oil-pastel β −2.534 → −2.413 — TOWARD the −5/3 band but NOT into\n // it (floor −1.85), so the keep-iff-into-band gate failed. The single-pass WebGL2 path\n // is the true limiter (HC-aurora §5): the oil-pastel β + the oil/oil-pastel anisotropy\n // residual stays the oil/oil-pastel stroke-cascade ceiling. BB.W-AUR-KUWAHARA DECIDED the\n // booking: the SOFT anisotropic-Kuwahara finish ships as the OPT-IN mediumKuwahara() body\n // (uMedium==7) the consumer selects — NOT a re-tune of these oil/oil-pastel knobs. The\n // hand-set 2.4 / 1.1 / 0.45 stays — it is van-Gogh's landed-band spacing.\n float sBig = baseScale * 2.4;\n float sMed = baseScale * 1.1;\n float sSml = baseScale * 0.45;\n\n // Strokes are elongated HARD (long·thin) so the rendered field reads strongly\n // directional — the §4.2 structure-tensor anisotropy lift. A short fat dab is locally\n // isotropic and drags the measured A down; a long thin stroke that hugs the tensor\n // tangent is what gives Starry Night its A≈0.83. The width muls are pulled tighter to\n // keep the long·thin aspect.\n float lenMulBig = mix(3.0, 5.2, uStrokeAnisotropy);\n float widMulBig = mix(0.42, 0.22, uStrokeAnisotropy);\n float lenMulMed = mix(2.8, 4.8, uStrokeAnisotropy);\n float widMulMed = mix(0.40, 0.21, uStrokeAnisotropy);\n float lenMulSml = mix(2.2, 3.6, uStrokeAnisotropy);\n float widMulSml = mix(0.36, 0.23, 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 strokes (more frequent, smaller). Even the knife layer-3 stays the\n // medium's DIRECTIONAL shape, not a round dab — the round-dab sparkle was a fine-scale\n // isotropic speckle that dragged the §4.2 anisotropy down + rolled the §4.3 slope shallow.\n int smlShape = prof.shapeType;\n StrokeHit hSml = bestOil(p + vec2(-5.1, 8.4), sSml, lenMulSml, widMulSml,\n jitterAmt * 0.9, 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.6,\n uImpasto * prof.impastoAmp * 0.45 * uStrokeAmount, prof.hardness, 4.1, prof.impastoFloor);\n\n // Layer 4 — fill strokes (dense, fine) — cover 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. The fill is\n // ELONGATED + flow-oriented (NOT a round dab) — a dense round-dab fill is locally\n // isotropic and was the dominant §4.2 anisotropy sink; an oriented fine stroke keeps the\n // fill directional so the whole field reads coherent.\n float sFill = baseScale * 0.22;\n float lenMulFill = mix(2.0, 3.2, uStrokeAnisotropy);\n float widMulFill = mix(0.34, 0.24, uStrokeAnisotropy);\n int fillShape = (mode == 1) ? 3 : prof.shapeType; // knife=even; others inherit the medium's directional shape\n StrokeHit hFill = bestOil(p + vec2(3.9, -6.2), sFill, lenMulFill, widMulFill,\n jitterAmt * 0.8, prof.densityFill, fillShape,\n prof.bristleAmp * 0.6, flow, t, 8.9, prof.energyGrade);\n // The fill is a low-relief, low-streak base coat — it covers bald spots WITHOUT adding\n // fine bright speckle (a high-impasto/high-streak fill caught a per-dab glint that read\n // as isotropic noise over the directional macro strokes — the §4.2/§4.3 sink). Its\n // positional jitter is tamed too so the fill strokes stay flow-aligned, not scattered.\n paintOver(col, height, hFill, prof.streakFreq * 1.2, prof.streakAmp * 0.35,\n uImpasto * prof.impastoAmp * 0.18 * 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#define MEDIUM_KUWAHARA 7\n\n// ── Kuwahara — the anisotropic generalized Kuwahara painterly finish (uMedium==7,\n// BB.W-AUR-KUWAHARA) ───────────────────────────────────────────────────────────────\n// The SOTA edge-preserving painterly smoothing: the generalized/anisotropic Kuwahara\n// of Kyprianidis 2010, the SOFT polynomial-weighted variant (NOT the pre-2010 hard\n// argmin, which BANDS the flat field into an 8-spoke pinwheel — the §4.2 anti-regression).\n// Aurora is a PROCEDURAL field (no input texture), so the operator runs over the\n// sampleBase() color field directly: an elliptical kernel oriented along the structure\n// tensor (elongated along the edge-tangent, narrowed across by the anisotropy), divided\n// into 8 overlapping sectors via smooth weights; each sector accumulates a weighted mean\n// + variance, and the output is the variance-weighted blend of the sector means\n// (1/(1+var^q) — the low-variance sectors dominate, the SOFT criterion). The result is\n// flat painterly oil-paint patches with crisp colour-zone boundaries — the §4.3 −5/3\n// cascade carried by the multi-scale patch structure, the §4.2 anisotropy carried by the\n// ellipse hugging the tensor. Single-pass small-radius WebGL2 form: 4 rings × 8 angular\n// taps = 32 procedural neighbourhood samples (cost-bounded; the offscreen-park / PRM\n// freeze the substrate owns is untouched — a parked frame attaches zero of these taps).\nvec3 mediumKuwahara(vec3 col, vec2 p, float t) {\n // Orient the kernel along the structure-tensor edge-tangent + read the coherence A.\n vec3 stf = structureTensorField(p, t, flowField(p, t));\n vec2 tangent = stf.xy;\n float A = stf.z;\n // The ellipse axes: major along the tangent, minor across. Anisotropy squeezes the\n // minor axis (coherent zones → long-thin patch along the colour band; flat zones →\n // near-circular so the operator degrades gracefully to the isotropic Kuwahara). The\n // base radius rides uStrokeScale so the painterly patch size tracks the studio knob.\n float radius = clamp(0.010 + uStrokeScale * 0.00018, 0.006, 0.024);\n float aniso = mix(1.0, 0.34, clamp(A, 0.0, 1.0)); // minor/major axis ratio\n vec2 ax = tangent; // major-axis unit vector (edge-tangent)\n vec2 ay = vec2(-tangent.y, tangent.x); // minor-axis unit vector (edge-normal)\n\n // 8 sectors, soft polynomial weights. Each sector i centers on angle i·45°; a sample's\n // sector weight is a smooth gaussian-of-angular-distance so sectors OVERLAP (no hard\n // argmin → no pinwheel banding). The center sample seeds all sectors equally.\n const int SECTORS = 8;\n vec3 m[8]; // per-sector weighted colour sum\n vec3 s[8]; // per-sector weighted colour² sum (for variance)\n float w[8]; // per-sector weight sum\n for (int i = 0; i < SECTORS; i++) { m[i] = vec3(0.0); s[i] = vec3(0.0); w[i] = 0.0; }\n\n // 4 rings × 8 angular taps over the elliptical disc. Each tap: sample the procedural\n // field, then deposit into the sectors by the soft angular weight × the radial falloff.\n const int RINGS = 4;\n const int TAPS = 8;\n for (int r = 1; r <= RINGS; r++) {\n float rr = float(r) / float(RINGS); // 0.25 .. 1.0\n float radial = 1.0 - 0.6 * rr; // inner taps weigh more (gaussian-ish)\n for (int a = 0; a < TAPS; a++) {\n float ang = 6.2831853 * float(a) / float(TAPS);\n // Elliptical offset: major axis full radius, minor axis squeezed by aniso.\n vec2 dirE = ax * cos(ang) + ay * (sin(ang) * aniso);\n vec2 off = dirE * (radius * rr);\n vec3 c = sampleBase(p + off, t);\n // The sample's angular bin (its sector) — soft, so it spills into neighbours.\n for (int k = 0; k < SECTORS; k++) {\n float sectorAng = 6.2831853 * float(k) / float(SECTORS);\n // Smallest signed angular distance to the sector center.\n float d = ang - sectorAng;\n d = atan(sin(d), cos(d)); // wrap to [-π, π]\n float sw = exp(-d * d * 2.2) * radial; // gaussian angular weight × radial\n m[k] += c * sw;\n s[k] += c * c * sw;\n w[k] += sw;\n }\n }\n }\n\n // Per-sector mean + scalar luma-variance, then the SOFT variance-weighted blend.\n vec3 accum = vec3(0.0);\n float accumW = 0.0;\n for (int k = 0; k < SECTORS; k++) {\n float wk = max(w[k], 1e-4);\n vec3 mean = m[k] / wk;\n vec3 var3 = max(s[k] / wk - mean * mean, vec3(0.0));\n float variance = dot(var3, W_LUMA); // perceptual scalar variance\n // SOFT criterion: low-variance sectors (flat, inside a colour zone) dominate; a\n // high-variance sector (straddling an edge) is suppressed. q controls the sharpness;\n // a finite q (not argmin) keeps the blend continuous → no pinwheel spoke.\n float sectorW = 1.0 / (1.0 + pow(variance * 320.0, 4.0));\n accum += mean * sectorW;\n accumW += sectorW;\n }\n vec3 result = accum / max(accumW, 1e-4);\n\n // The center sample anchors the result in near-flat zones (where every sector reads the\n // same colour) so the operator is a no-op there — the smoothing only BITES at edges.\n vec3 center = sampleBase(p, t);\n result = mix(center, result, clamp(0.35 + 0.65 * A, 0.0, 1.0));\n\n // A faint canvas tooth + pigment-saturation lift so the flattened patches read as\n // oil-paint, not a posterized blur (the painterly identity the smoothing alone lacks).\n float tooth = vnoise(p * 300.0) - 0.5;\n result *= 1.0 + tooth * 0.06 * uCanvasGrain;\n result = saturate3(result, 1.04);\n\n // Blend toward the incoming col by uStrokeAmount so the studio amount knob controls\n // the finish strength (uStrokeAmount=0 → the raw field passes through; default → full).\n return mix(col, result, clamp(uStrokeAmount, 0.0, 1.0));\n}\n\n" + xe + "// 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}", k = "\n", Te = `#version 300 es
14
+ var ye = "#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}", be = "// ── 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", xe = "// ── 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", Se = "// Khronos PBR-Neutral tonemap (hue + saturation preserving)\nvec3 aces(vec3 color) {\n const float startCompression = 0.8 - 0.04;\n const float desaturation = 0.15;\n float x = min(color.r, min(color.g, color.b));\n float offset = x < 0.08 ? x - 6.25 * x * x : 0.04;\n color -= offset;\n float peak = max(color.r, max(color.g, color.b));\n if (peak < startCompression) return clamp(color, 0.0, 1.0);\n float d = 1.0 - startCompression;\n float newPeak = 1.0 - d * d / (peak + d - startCompression);\n color *= newPeak / peak;\n float g = 1.0 - 1.0 / (desaturation * (peak - newPeak) + 1.0);\n return clamp(mix(color, newPeak * vec3(1.0), g), 0.0, 1.0);\n}\n", Ce = "// ── 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). The along-spine value\n // modulation is the loaded-brush light/dark that gives each stroke its OWN directional\n // luminance band — the structure-tensor signal a low-contrast color field (a uniform-hue\n // oil field) has no other source for (the §4.2 anisotropy lift on the flat-palette case).\n c *= 1.0 + streak * streakAmp * 3.2;\n\n // Cross-stroke ridge/valley shading — a catch-light along the spine centre, darkening to\n // the stroke edges. This is the strongest DIRECTIONAL luminance cue: it carves a bright\n // ridge down the stroke's length and dark grooves between adjacent strokes, so a field of\n // parallel strokes reads as parallel light/dark bands (the high-A van-Gogh signature) even\n // where the underlying colour is flat. s.edgeN is 0 at the edge, 1 at the spine.\n float ridgeShade = smoothstep(0.0, 0.55, s.edgeN);\n c *= 1.0 + (ridgeShade - 0.5) * 0.30;\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 // The specular glint is gated to ACCUMULATED-thick paint (height²) — a per-fine-stroke\n // sharp glint on every thin fill dab read as bright isotropic SPECKLE that buried the\n // directional stroke bands (the §4.2 anisotropy / §4.3 slope sink on the low-contrast\n // oil field). Only where real impasto has built up does the raked glint fire, so the\n // highlight tracks the macro ridges, not the fine speckle.\n float specGate = clamp(height, 0.0, 1.0);\n lit += uImpasto * relief * spec * specGate * 0.5 * 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 FAR less angular jitter (the field already orients them\n // — over-jittering the tensor strokes scatters their directions and the rendered\n // field reads isotropic, the §4.2 anisotropy floor). The flow-oriented (non-tensor)\n // strokes keep the full jitter (they have no per-cell orientation source).\n float jScale = (uStrokeOrient == 1) ? 0.15 : 1.0;\n float angJ = (hash21(cc + seed + 11.0) - 0.5) * 0.9 * jScale; // +/- 0.45/0.07 rad\n float curlScale = (uStrokeOrient == 1) ? 0.30 : 1.0;\n float localCurl = (fbm(center * (2.6 + seed * 0.11) + seed * 1.9) - 0.5) * 0.55 * uFlowCurl * curlScale;\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", we = "// ── Van-Gogh — DEDICATED atomic-dab body (W-AUR-VANGOGH-REBUILD) ─────────────\n// REBUILT FROM FIRST PRINCIPLES. The prior path routed van-Gogh through the shared\n// oil paintStrokeMedium cascade — four DENSE layers of LONG-THIN tensor-oriented\n// strokes (lenMul up to 5.2, widMul 0.22) hugging a continuous coherent field. Packed\n// that tight, thin strokes that follow continuous iso-bands MERGE into smooth flowing\n// filaments — the \"marbled flow-bands\" read (a spirograph/fingerprint, not impasto),\n// AND the per-cell 8-tap structure tensor × 4 layers × 9 cells = ~324 sampleBase/pixel\n// made it run at ~4fps. Van Gogh's sky is NOT continuous flow — it is DISCRETE, SHORT,\n// LOADED comma dabs, separated by visible ground, queued into swirl ROWS.\n//\n// The rebuild is a purpose-built two-layer DAB pass, disjoint from the oil cascade:\n// • orientation from the analytic SWIRL flowField (van-Gogh skies ARE swirl rows) +\n// per-cell angular jitter — NOT the expensive per-cell tensor (the lag source, and\n// the marble source: a jittered discrete dab can NOT merge into a continuous\n// filament the way a tensor-locked thin stroke does).\n// • SHORT comma dabs (len/wid ≈ 2.6 : 1 — a loaded crescent mark, not a 10:1\n// filament), so each reads as a SEPARABLE atom.\n// • SPARSE placement with visible inter-dab ground (density ≈ 0.42), over a darkened\n// swirl underpainting (the Starry-Night visible ground the bright dabs read over).\n// • a single sampleBase per cell + OKLCh broken-color jitter (pigment shimmer);\n// full-height impasto crown per dab (each catches its own raked glint).\n// This is ~2 layers × 9 cells × ~1 sampleBase ≈ 20/pixel (a ~16× cost cut, ~60fps), and\n// jittered short dabs CANNOT marble.\n\n// One atomic comma dab — a CLEAN analytic crescent SDF (NOT the oil curvedStroke).\n// The oil curvedStroke's spine-projection + rounded end-caps + fwidth-AA + bristle\n// edge each leave a thin sub-pixel skirt, and a field of those skirts over open ground\n// abuts into a faint network of SEAM LINES. A van-Gogh dab needs none of that\n// machinery — it is a short oriented capsule (rounded line segment) whose half-width\n// tapers along its length (the loaded-head → dragged-tail comma). The capsule SDF is\n// derivative-free (smoothstep AA against the analytic distance), so no fwidth skirt, no\n// best-of-9 seam. Returns a StrokeHit (coverage/color/alongT/edgeN) for paintOver.\n// cellSize — dab cell pitch (UV units); lenMul/widMul — dab aspect off the cell;\n// density — placement gate (sparse → visible gaps); seed — layer decorrelation.\nStrokeHit vangoghDab(vec2 p, float cellSize, float lenMul, float widMul,\n float density, float jitterAmt, float angJitter,\n vec2 flow, float t, float seed) {\n vec2 cell = floor(p / cellSize);\n StrokeHit best = noHit();\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 // Sparse density gate — leaves visible canvas ground between dabs (atomicity).\n if (hash21(cc * 1.7 + seed * 0.3) > density) continue;\n vec2 jit = (hash22(cc + seed) - 0.5) * jitterAmt;\n vec2 center = (cc + 0.5 + jit) * cellSize;\n\n // Orientation: the analytic swirl flow at the cell center + per-cell angular\n // jitter (so adjacent dabs FAN — the divisionist scatter that keeps the dabs from\n // locking into perfectly-concentric rows) + a flow-noise curl wobble.\n float ang = (hash21(cc + seed + 11.0) - 0.5) * angJitter;\n float curl = (fbm(center * 3.1 + seed * 1.9) - 0.5) * (0.18 + 0.3 * uFlowCurl);\n vec2 dir = rotateDir(safeDir(flow), ang + curl);\n\n // SHORT comma — len ≈ the cell pitch (a contained dab, not a tiling filament).\n float len = cellSize * lenMul * (0.82 + 0.30 * hash21(cc + seed + 23.0));\n float halfW = max(cellSize * widMul * (0.86 + 0.28 * hash21(cc + seed + 41.0)), 0.010);\n\n // Local dab frame: along = projection onto the dab axis (centred 0), perp = cross.\n vec2 rel = p - center;\n float along = dot(rel, dir);\n float perp = dot(rel, vec2(-dir.y, dir.x));\n // The comma TAPER: half-width peaks ~1/3 from the loaded head and drags to a fine\n // tail (the asymmetric crescent). u in [0,1] along the dab from head to tail.\n float halfLen = len * 0.5;\n float u = clamp((along + halfLen) / max(len, 1e-5), 0.0, 1.0);\n float head = smoothstep(0.0, 0.30, u); // rounded loaded head\n float tail = pow(clamp((1.0 - u) / 0.70, 0.0, 1.0), 1.3); // dragged thin tail\n float prof = clamp(min(head, tail) * 1.05, 0.0, 1.0);\n float wNow = halfW * prof;\n // A gentle comma CURVE — bow the spine sideways so the mark reads as a crescent.\n float bowAmt = (hash21(cc + seed + 53.0) - 0.5) * halfW * 1.4;\n float spine = bowAmt * sin(u * PI); // 0 at ends, max mid\n float d = abs(perp - spine);\n // Derivative-free AA: a fixed fraction of the dab width (no fwidth skirt). Coverage\n // is 1 in the dab body, smoothly 0 at the edge; outside the [head,tail] span wNow→0\n // so the dab self-terminates with no end-cap blob.\n float aa = max(halfW * 0.45, 0.002);\n float cov = (wNow > 1e-5) ? (1.0 - smoothstep(wNow - aa, wNow + aa, d)) : 0.0;\n if (cov < 0.004) continue;\n\n // One sampleBase per cell + OKLCh broken-color jitter (pigment patch shimmer). The\n // dab pigment is chroma-boosted (saturate3 1.22) AND value-lifted (×1.14) at the\n // SOURCE — the loaded brush mark deposits BRIGHTER + more saturated pigment than the\n // base field, so the loaded-mark pixels lift BOTH the §4.1 colourfulness AND the field\n // mean-luma. The higher mean raises the gap-fraction threshold (meanLum·0.6) so the\n // moderate-depth inter-dab ground falls below it (the gap-fraction atomicity read is\n // DECOUPLED from the ground floor depth — the ground can stay only moderately dark, so\n // the §4.3 slope is NOT over-steepened, while the bright dabs manufacture the gap). The\n // dark ground is untouched, so this lifts C + gap WITHOUT a band-conflicting deep ground.\n vec3 colMid = brokenColorJitter(\n saturate3(sampleBase(center, t), 1.22),\n hash21(cc + seed + 89.0),\n hash21(cc * 2.3 + seed + 97.0),\n 1.0\n );\n\n // edgeN: 1 at the spine, 0 at the edge (the impasto crown reads it).\n float edgeN = (wNow > 1e-5) ? clamp(1.0 - d / wNow, 0.0, 1.0) : 0.0;\n if (cov > best.coverage) {\n best = StrokeHit(cov, colMid, u, (wNow > 1e-5 ? (perp - spine) / wNow : 0.0), edgeN);\n }\n }\n }\n return best;\n}\n\nvec3 mediumVangogh(vec3 col, vec2 p, float t) {\n vec3 result = col;\n float height = 0.0;\n\n // The swirl flow basis (van-Gogh sky = swirl rows). Computed ONCE per pixel.\n vec2 flow = flowField(p, t);\n\n // Dab cell pitch in UV [0,1]. CHUNKY, SEPARABLE marks — the big layer is ~1/14 of\n // the frame so it reads as a distinct loaded brush dab (not a fine speckle, not a\n // screen-spanning filament). uStrokeScale (authored ~110) modulates the pitch\n // INVERSELY (higher = finer dabs) around the ~0.07 chunky default; clamped so the\n // dab never collapses to noise or balloons past one screen. uStrokeAnisotropy\n // stretches the comma aspect (longer, more directional marks).\n float pitch = clamp(7.0 / max(uStrokeScale, 20.0), 0.044, 0.12);\n // The dab LENGTH ≈ the cell pitch (lenAniso ~1.2-1.7) so a dab sits WITHIN its cell\n // and leaves visible ground around it — a longer dab (lenMul > 2) spans neighbour\n // cells and tiles the field into continuous coverage (no gaps, the gap-fraction floor\n // miss + the marble). Narrow width keeps it a directional crescent, not a wide shard.\n // lenAniso/widAniso — the comma aspect. The W-AUR-VANGOGH-CONFIG reconcile lengthened\n // the comma (1.25-1.75 → 1.5-2.0) and THINNED it (0.40-0.30 → 0.32-0.24): the rebuilt\n // dabs measured §4.2 structure-tensor anisotropy A=0.699, BELOW the reference band\n // floor 0.732 (the dabs read slightly too short-fat → locally isotropic, dragging mean\n // A down). A LONGER, THINNER comma hugs the swirl-flow tangent more strongly so the\n // rendered field reads coherent the way the tensor metric measures it (A lift), while\n // the thinner width LEAVES MORE inter-dab ground (the gap-fraction atomicity read is\n // preserved, not closed). The length stays ≈ the cell pitch so each dab remains a\n // CONTAINED separable mark, not a tiling filament.\n float lenAniso = mix(2.1, 2.7, uStrokeAnisotropy); // comma length / cell pitch (a contained dab)\n float widAniso = mix(0.23, 0.16, uStrokeAnisotropy); // comma width / cell pitch (a thin loaded crescent)\n\n // Layer 1 — the big swirl-row dabs: the loaded gestural marks, SPARSE so the dark\n // underpainting shows BETWEEN them (the atomicity read — visible inter-dab ground).\n // hardness 0.74 — the loaded brush deposits with a SOFT shoulder, so adjacent dabs\n // feather instead of razor-clipping into hard polygon shards. jitterAmt 0.85 fully\n // scatters the cell centres so the placement GRID never reads as a periodic seam.\n // angJitter EASED 0.55 → 0.38 (the reconcile A lift): a tighter angular spread keeps\n // adjacent dabs more flow-aligned (coherent) while the per-cell curl wobble still\n // breaks the perfectly-concentric-rows read (the dabs FAN gently, not lock to a grid).\n // angJitter eased 0.38 → 0.26 + hardness 0.74 → 0.84 (the §4.2 A lift): a tighter angular\n // spread keeps adjacent dabs strongly flow-aligned (the structure-tensor coherence) and a\n // sharper edge gives each dab a STRONGER directional gradient (a soft dab's faint edge\n // reads weakly anisotropic). The per-cell curl wobble still breaks the perfectly-\n // concentric-rows read, so the dabs FAN gently rather than locking to a grid.\n StrokeHit dBig = vangoghDab(p, pitch, lenAniso, widAniso,\n 0.30, 0.50, 0.08, flow, t, 1.3);\n // streakFreq raised 14 → 34 (the §4.3 slope fix): a high-frequency streak runs ALONG the\n // dab spine, injecting fine-scale luma energy that flattens the too-steep β WITHOUT\n // adding new isotropic marks (the along-spine streak stays directional, so the §4.2\n // anisotropy the long-thin dab carries is preserved — the band-conflict resolution).\n paintOver(result, height, dBig, 48.0, 0.34,\n uImpasto * 0.75 * uStrokeAmount, 0.84, 1.3, 1.0 /* full crown */);\n\n // Layer 2 — two-thirds-pitch accent dabs (the divisionist sparkle between the big\n // marks — denser placement but still contained dabs, so dark canvas survives between).\n // angJitter eased 0.5 → 0.34 (same A-lift reasoning — the accent dabs queue along the\n // flow rather than scattering isotropically); hardness 0.78 → 0.86 sharpens the gradient.\n StrokeHit dSml = vangoghDab(p + vec2(-5.1, 8.4), pitch * 0.62, lenAniso * 0.95,\n widAniso * 0.95, 0.36, 0.60, 0.20, flow, t, 4.1);\n paintOver(result, height, dSml, 42.0, 0.22,\n uImpasto * 0.55 * uStrokeAmount, 0.86, 4.1, 1.0);\n\n // Layer 3 — the FINE divisionist flecks (W-AUR-VANGOGH-CONFIG reconcile). The two-layer\n // field read as a few BIG cobalt dabs over a smooth empty teal ground (the side-by-side\n // against starry-night-crop.png: the painting is DENSE small varied flecks, not sparse\n // large marks). The flecks are LONG·THIN directional commas hugging the swirl tangent\n // (the A-preserving fleck — short fat flecks read isotropic and drag §4.2 down). Sparse\n // density 0.30 keeps the inter-fleck ground open (the gap-fraction atomicity read).\n // The flecks are FINE (≈0.42× pitch) directional commas, tightly flow-aligned (angJitter\n // 0.22) and SPARSE (0.30) — the divisionist sparkle between the loaded marks that keeps\n // the inter-dab ground open (atomicity) while adding the small-mark mid-band energy. Thin\n // + flow-aligned so the §4.2 anisotropy the long dabs carry survives the added small marks.\n StrokeHit dFleck = vangoghDab(p + vec2(8.7, -3.2), pitch * 0.42, lenAniso * 1.0,\n widAniso * 0.68, 0.30, 0.85, 0.22, flow, t, 7.7);\n paintOver(result, height, dFleck, 30.0, 0.16,\n uImpasto * 0.40 * uStrokeAmount, 0.80, 7.7, 1.0);\n\n // ── Flow-aligned GROUND swirl-GROOVING (W-AUR-VANGOGH-CONFIG reconcile) ──\n // The bare ground between the dabs was a SMOOTH low-frequency teal gradient — the §4.3\n // power-spectrum slope ran too STEEP (β≈−2.0: too much low-frequency nuclei-field energy\n // vs the high band). Van Gogh's actual sky has the swirl BRUSHWORK everywhere, so groove\n // the OPEN GROUND with a flow-aligned ripple. It is SUBTRACTIVE-biased (it only darkens,\n // never brightens the ground), so it DEEPENS the inter-dab valleys (MORE gap-fraction —\n // atomicity preserved/strengthened) while injecting the across-flow mid-band luma energy\n // that flattens β. Gated to the open ground (low height) via the (1-height) gate so it\n // never lifts/erodes the loaded dab bodies (the dab read is untouched). Across the swirl\n // FLOW axis so the grooves run WITH the sky's brushwork direction.\n // The ground swirl-combing — a FIXED-global-angle directional grain on the open ground.\n // A flow-FOLLOWING comb varies its direction with the swirl across the readback window,\n // so its blurred structure tensor averages toward isotropic (no §4.2 lift). A comb at one\n // GLOBAL angle (van-Gogh's dominant diagonal stroke grain) is COHERENT over the whole\n // window ⇒ every open-ground pixel reads the SAME direction (the §4.2 mean-anisotropy lift\n // the swirl comb could not reach). Two resolved bands (≈22 + ≈44 across the global axis ⇒\n // ~21px + ~11px at 464px, both in the §4.3 fit window) also feed the β flatten. Gated to\n // the open ground (low height) so the loaded dabs are untouched; SUBTRACTIVE-biased so the\n // inter-dab valleys deepen (gap-fraction preserved, not filled).\n vec2 gdir = normalize(vec2(0.82, 0.57)); // the dominant global stroke grain axis\n float gacross = dot(p, vec2(-gdir.y, gdir.x));\n float groundGate = 1.0 - smoothstep(0.05, 0.28, height);\n float groove = sin(gacross * 22.0) + 0.5 * sin(gacross * 44.0 + 1.3);\n result *= 1.0 - 0.16 * groundGate * (0.5 - 0.5 * groove);\n\n // Canvas tooth (fine linen weave — base relief the raking light catches).\n float tooth1 = vnoise(p * 220.0);\n float tooth2 = vnoise(p * 220.0 * vec2(0.6, 2.4) + 37.0);\n float tooth = (0.6 * tooth1 + 0.4 * tooth2) - 0.5;\n result *= 1.0 + tooth * 0.06 * uCanvasGrain;\n\n // Relight the accumulated impasto height (raking light catches each dab's crown —\n // the visible impasto ridge that makes a dab read as a RAISED loaded mark).\n float canvasBase = tooth * 0.06 * 0.5;\n result = relightImpasto(result, height, canvasBase);\n\n // The visible Starry-Night GROUND — the open canvas between dabs (low accumulated\n // height) darkens toward the deep underpainting, so the bright impasto dabs read as\n // SEPARABLE atomic marks over a darker field (the dab↔ground contrast the user's\n // \"reads as strokes\" bar needs, AND the painterly-statistics gap-fraction floor — the\n // dark inter-dab ground must register as a measurable fraction of the field). 0.30\n // floor over a WIDER smoothstep (0..0.40): deep enough that the open gaps read as the\n // dark Starry-Night sky between marks (clearing the 4% gap floor) while the gradual\n // ramp keeps the gaps reading as flowing underpainting, not the hard black ink LINES a\n // narrow near-black step (0.26 / 0.0..0.32) produced.\n // The smoothstep FLOOR starts at 0.10 (not 0): a dab's faint AA coverage TAIL deposits\n // a sliver of height, and where many tails align along the swirl flow those slivers\n // accumulate into a faint continuous height ridge that read as thin curved LINES in the\n // ground. Holding the ground full-dark until a real dab CORE (height > 0.10) lifts out\n // keeps the open field clean — only the loaded dab bodies emerge, the tails stay ground.\n result *= mix(0.115, 1.0, smoothstep(0.085, 0.31, height));\n\n // The van-Gogh chroma — a light OKLCh punch. EASED to 0.92 (off 1.04) in the\n // W-AUR-VANGOGH-CONFIG reconcile: the rebuilt discrete-dab register's per-dab OKLCh\n // broken-color jitter (the ±16° hue + ±chroma swing per cell) lifts the field's\n // colourfulness on its own, so the prior 1.04 punch on TOP overshot the §4.1\n // reference band (rendered C=100.5 vs the painting's measured 70.67 ± 15/+25 →\n // ceiling 95.67 — neon, not Starry Night's rich-but-not-garish blue/gold). The 0.92\n // demote pulls the rendered colourfulness back IN-band while the broken-color jitter\n // keeps the per-dab pigment variation (the discrete-stroke read is untouched — this\n // is a chroma scale, not a dab-geometry change). Band-anchored on the real painting\n // (proof:aurora-arresting), NOT a widened band.\n result = saturate3(result, 1.0);\n return result;\n}\n\n", Te = "StrokeProfile 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 340.0, // toothScale — W-AUR-VANGOGH-CONFIG reconcile: FINER 240 → 340. Oil's §4.3 β sat\n // right AT the −1.45 band ceiling (±0.02 frame variance straddling\n // it, surfaced once the vangogh bands went green and the spec reached\n // the oil β assert). MEASURED on the live GPU: ADDING fine high-\n // frequency tooth energy steepens oil's power-spectrum slope (more\n // negative — coarsening it the other way pushed β SHALLOWER to\n // −1.33). A finer, stronger tooth moves β to a safe distance inside\n // the −5/3 band [−1.85, −1.45]. Oil-only (oil-pastel sets 280).\n 0.14, // toothAmp — raised 0.09 → 0.14 (same oil-β steepen: more fine-grain high band).\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.92, // densityFill — oil fills DENSELY with the directional low-relief fill so the\n // flat-colour regions between the macro strokes are TEXTURED with\n // flow-aligned fine strokes (the §4.2 anisotropy needs every pixel\n // to carry directional gradient; a flat untextured region reads as\n // an isotropic/degenerate tensor and drags mean A down). The fill\n // is now directional + low-streak, so dense coverage textures\n // WITHOUT the prior round-dab speckle.\n 1.0 // groundFloor — oil keeps the full base (no ground darken; fills everywhere)\n );\n\n // W-AUR-VANGOGH-REBUILD — the van-Gogh medium NO LONGER routes through this oil\n // StrokeProfile cascade. It is a DEDICATED atomic-dab body (mediumVangogh / vangoghDab\n // below) — the prior profile-driven cascade (four dense layers of long-thin\n // tensor-oriented strokes) merged into the \"marbled flow-bands\" read and ran at ~4fps.\n // profileFor now serves ONLY the oil + oil-pastel stroke mediums; the MEDIUM_VANGOGH\n // case is removed (no caller). MEDIUM_VANGOGH stays #define'd (the main() dispatch +\n // isPainterlyStroke still key off uMedium==5).\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 = 0; // tapered directional smear — a round dab reads locally\n // isotropic (steep β / low A); the creamy oil-pastel mark\n // is a broad DIRECTIONAL smear, which carries the §4.2\n // coherence + adds the directional mid-band the §4.3 slope\n // needs (the round-dab profile rolled β off to −2.8)\n prof.bristleAmp = 0.10; // smooth creamy edges (waxy pastel, not bristle)\n prof.streakFreq = 18.0; // finer internal smear — adds the mid/high-band the smooth\n // round-dab profile lacked (the β-too-steep fix); the\n // directional along-spine streak flattens the §4.3 slope\n // without crossing the strokes\n prof.streakAmp = 0.20;\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 = 0.80; // the waxy chroma — eased off 1.16 so the rendered C clears\n // the §4.1 band ceiling with margin while the K-M subtractive\n // overlap path (paintOverOklab) keeps the chroma OFF the grey floor\n prof.hardness = 0.58; // creamy but the strokes still REGISTER — a too-soft blend\n // (0.42) smeared the marks into the smooth colour zones and\n // the field rolled off too steep (β≈−2.9, a near-gradient);\n // a crisper deposit keeps the mid/high-band the −5/3 slope needs\n prof.densityBig = 0.62; // broad directional coverage — but NOT over-dense: too many\n // overlapping creamy strokes point in slightly-varied\n // directions and the local §4.2 coherence DROPS (over-dense\n // measured A=0.57 < the 0.62-density A=0.63). The mid-density\n // flow-aligned deposit is the coherence sweet spot.\n prof.densityMed = 0.78;\n prof.densitySml = 0.90;\n prof.energyGrade = 0.80; // a strong Starry-Night length cascade — varies stroke length\n // by luma/coherence so the dab-size energy spreads across\n // scales (the −5/3 cascade the uniform-length 0.0 grade lacked,\n // the §4.3 β-too-steep fix). Not the full van-Gogh 1.0 (oil-\n // pastel keeps its creamy broad character).\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", Ee = "// ── 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 // The blend weight is a low-power curve (A^0.35) so even MODERATE-coherence zones queue\n // strongly along the tangent (a flowing van-Gogh band reads coherent the way the\n // structure-tensor metric measures it; a linear A under-orients the mid-band and the\n // rendered field reads too isotropic — the §4.2 anisotropy lift). The flat-zone (A≈0)\n // relax to the fallback survives (0^0.35=0), so tensor noise never reads as jitter.\n float blendW = pow(A, 0.28);\n vec2 dir = normalize(mix(fallbackDir, tangent, blendW) + 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", De = "// ── 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.\n" + Te + "\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 // W-AUR-STUDIO D5 — the −5/3 radii respacing was MEASURED + REVERTED (recorded, not\n // silently struck). The φ-adjacent geometric candidate (2.4 / 1.45 / 0.87, a fixed\n // ≈1.66× step) moved oil-pastel β −2.534 → −2.413 — TOWARD the −5/3 band but NOT into\n // it (floor −1.85), so the keep-iff-into-band gate failed. The single-pass WebGL2 path\n // is the true limiter (HC-aurora §5): the oil-pastel β + the oil/oil-pastel anisotropy\n // residual stays the oil/oil-pastel stroke-cascade ceiling. BB.W-AUR-KUWAHARA DECIDED the\n // booking: the SOFT anisotropic-Kuwahara finish ships as the OPT-IN mediumKuwahara() body\n // (uMedium==7) the consumer selects — NOT a re-tune of these oil/oil-pastel knobs. The\n // hand-set 2.4 / 1.1 / 0.45 stays — it is van-Gogh's landed-band spacing.\n float sBig = baseScale * 2.4;\n float sMed = baseScale * 1.1;\n float sSml = baseScale * 0.45;\n\n // Strokes are elongated HARD (long·thin) so the rendered field reads strongly\n // directional — the §4.2 structure-tensor anisotropy lift. A short fat dab is locally\n // isotropic and drags the measured A down; a long thin stroke that hugs the tensor\n // tangent is what gives Starry Night its A≈0.83. The width muls are pulled tighter to\n // keep the long·thin aspect.\n float lenMulBig = mix(3.0, 5.2, uStrokeAnisotropy);\n float widMulBig = mix(0.42, 0.22, uStrokeAnisotropy);\n float lenMulMed = mix(2.8, 4.8, uStrokeAnisotropy);\n float widMulMed = mix(0.40, 0.21, uStrokeAnisotropy);\n float lenMulSml = mix(2.2, 3.6, uStrokeAnisotropy);\n float widMulSml = mix(0.36, 0.23, 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 strokes (more frequent, smaller). Even the knife layer-3 stays the\n // medium's DIRECTIONAL shape, not a round dab — the round-dab sparkle was a fine-scale\n // isotropic speckle that dragged the §4.2 anisotropy down + rolled the §4.3 slope shallow.\n int smlShape = prof.shapeType;\n StrokeHit hSml = bestOil(p + vec2(-5.1, 8.4), sSml, lenMulSml, widMulSml,\n jitterAmt * 0.9, 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.6,\n uImpasto * prof.impastoAmp * 0.45 * uStrokeAmount, prof.hardness, 4.1, prof.impastoFloor);\n\n // Layer 4 — fill strokes (dense, fine) — cover 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. The fill is\n // ELONGATED + flow-oriented (NOT a round dab) — a dense round-dab fill is locally\n // isotropic and was the dominant §4.2 anisotropy sink; an oriented fine stroke keeps the\n // fill directional so the whole field reads coherent.\n float sFill = baseScale * 0.22;\n float lenMulFill = mix(2.0, 3.2, uStrokeAnisotropy);\n float widMulFill = mix(0.34, 0.24, uStrokeAnisotropy);\n int fillShape = (mode == 1) ? 3 : prof.shapeType; // knife=even; others inherit the medium's directional shape\n StrokeHit hFill = bestOil(p + vec2(3.9, -6.2), sFill, lenMulFill, widMulFill,\n jitterAmt * 0.8, prof.densityFill, fillShape,\n prof.bristleAmp * 0.6, flow, t, 8.9, prof.energyGrade);\n // The fill is a low-relief, low-streak base coat — it covers bald spots WITHOUT adding\n // fine bright speckle (a high-impasto/high-streak fill caught a per-dab glint that read\n // as isotropic noise over the directional macro strokes — the §4.2/§4.3 sink). Its\n // positional jitter is tamed too so the fill strokes stay flow-aligned, not scattered.\n paintOver(col, height, hFill, prof.streakFreq * 1.2, prof.streakAmp * 0.35,\n uImpasto * prof.impastoAmp * 0.18 * 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#define MEDIUM_KUWAHARA 7\n\n// ── Kuwahara — the anisotropic generalized Kuwahara painterly finish (uMedium==7,\n// BB.W-AUR-KUWAHARA) ───────────────────────────────────────────────────────────────\n// The SOTA edge-preserving painterly smoothing: the generalized/anisotropic Kuwahara\n// of Kyprianidis 2010, the SOFT polynomial-weighted variant (NOT the pre-2010 hard\n// argmin, which BANDS the flat field into an 8-spoke pinwheel — the §4.2 anti-regression).\n// Aurora is a PROCEDURAL field (no input texture), so the operator runs over the\n// sampleBase() color field directly: an elliptical kernel oriented along the structure\n// tensor (elongated along the edge-tangent, narrowed across by the anisotropy), divided\n// into 8 overlapping sectors via smooth weights; each sector accumulates a weighted mean\n// + variance, and the output is the variance-weighted blend of the sector means\n// (1/(1+var^q) — the low-variance sectors dominate, the SOFT criterion). The result is\n// flat painterly oil-paint patches with crisp colour-zone boundaries — the §4.3 −5/3\n// cascade carried by the multi-scale patch structure, the §4.2 anisotropy carried by the\n// ellipse hugging the tensor. Single-pass small-radius WebGL2 form: 4 rings × 8 angular\n// taps = 32 procedural neighbourhood samples (cost-bounded; the offscreen-park / PRM\n// freeze the substrate owns is untouched — a parked frame attaches zero of these taps).\nvec3 mediumKuwahara(vec3 col, vec2 p, float t) {\n // Orient the kernel along the structure-tensor edge-tangent + read the coherence A.\n vec3 stf = structureTensorField(p, t, flowField(p, t));\n vec2 tangent = stf.xy;\n float A = stf.z;\n // The ellipse axes: major along the tangent, minor across. Anisotropy squeezes the\n // minor axis (coherent zones → long-thin patch along the colour band; flat zones →\n // near-circular so the operator degrades gracefully to the isotropic Kuwahara). The\n // base radius rides uStrokeScale so the painterly patch size tracks the studio knob.\n float radius = clamp(0.010 + uStrokeScale * 0.00018, 0.006, 0.024);\n float aniso = mix(1.0, 0.34, clamp(A, 0.0, 1.0)); // minor/major axis ratio\n vec2 ax = tangent; // major-axis unit vector (edge-tangent)\n vec2 ay = vec2(-tangent.y, tangent.x); // minor-axis unit vector (edge-normal)\n\n // 8 sectors, soft polynomial weights. Each sector i centers on angle i·45°; a sample's\n // sector weight is a smooth gaussian-of-angular-distance so sectors OVERLAP (no hard\n // argmin → no pinwheel banding). The center sample seeds all sectors equally.\n const int SECTORS = 8;\n vec3 m[8]; // per-sector weighted colour sum\n vec3 s[8]; // per-sector weighted colour² sum (for variance)\n float w[8]; // per-sector weight sum\n for (int i = 0; i < SECTORS; i++) { m[i] = vec3(0.0); s[i] = vec3(0.0); w[i] = 0.0; }\n\n // 4 rings × 8 angular taps over the elliptical disc. Each tap: sample the procedural\n // field, then deposit into the sectors by the soft angular weight × the radial falloff.\n const int RINGS = 4;\n const int TAPS = 8;\n for (int r = 1; r <= RINGS; r++) {\n float rr = float(r) / float(RINGS); // 0.25 .. 1.0\n float radial = 1.0 - 0.6 * rr; // inner taps weigh more (gaussian-ish)\n for (int a = 0; a < TAPS; a++) {\n float ang = 6.2831853 * float(a) / float(TAPS);\n // Elliptical offset: major axis full radius, minor axis squeezed by aniso.\n vec2 dirE = ax * cos(ang) + ay * (sin(ang) * aniso);\n vec2 off = dirE * (radius * rr);\n vec3 c = sampleBase(p + off, t);\n // The sample's angular bin (its sector) — soft, so it spills into neighbours.\n for (int k = 0; k < SECTORS; k++) {\n float sectorAng = 6.2831853 * float(k) / float(SECTORS);\n // Smallest signed angular distance to the sector center.\n float d = ang - sectorAng;\n d = atan(sin(d), cos(d)); // wrap to [-π, π]\n float sw = exp(-d * d * 2.2) * radial; // gaussian angular weight × radial\n m[k] += c * sw;\n s[k] += c * c * sw;\n w[k] += sw;\n }\n }\n }\n\n // Per-sector mean + scalar luma-variance, then the SOFT variance-weighted blend.\n vec3 accum = vec3(0.0);\n float accumW = 0.0;\n for (int k = 0; k < SECTORS; k++) {\n float wk = max(w[k], 1e-4);\n vec3 mean = m[k] / wk;\n vec3 var3 = max(s[k] / wk - mean * mean, vec3(0.0));\n float variance = dot(var3, W_LUMA); // perceptual scalar variance\n // SOFT criterion: low-variance sectors (flat, inside a colour zone) dominate; a\n // high-variance sector (straddling an edge) is suppressed. q controls the sharpness;\n // a finite q (not argmin) keeps the blend continuous → no pinwheel spoke.\n float sectorW = 1.0 / (1.0 + pow(variance * 320.0, 4.0));\n accum += mean * sectorW;\n accumW += sectorW;\n }\n vec3 result = accum / max(accumW, 1e-4);\n\n // The center sample anchors the result in near-flat zones (where every sector reads the\n // same colour) so the operator is a no-op there — the smoothing only BITES at edges.\n vec3 center = sampleBase(p, t);\n result = mix(center, result, clamp(0.35 + 0.65 * A, 0.0, 1.0));\n\n // A faint canvas tooth + pigment-saturation lift so the flattened patches read as\n // oil-paint, not a posterized blur (the painterly identity the smoothing alone lacks).\n float tooth = vnoise(p * 300.0) - 0.5;\n result *= 1.0 + tooth * 0.06 * uCanvasGrain;\n result = saturate3(result, 1.04);\n\n // Blend toward the incoming col by uStrokeAmount so the studio amount knob controls\n // the finish strength (uStrokeAmount=0 → the raw field passes through; default → full).\n return mix(col, result, clamp(uStrokeAmount, 0.0, 1.0));\n}\n\n" + we + "// 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}", k = "\n", Oe = `#version 300 es
13
15
  precision highp float;
14
16
 
15
17
  in vec2 vUv;
@@ -101,6 +103,11 @@ uniform float uSaturation;
101
103
  uniform float uPaperGrain;
102
104
  uniform float uAlpha;
103
105
 
106
+ // BD.W-AUR-VIVIDNESS — the §3 chroma-floor strength (0 = off / pre-floor identity,
107
+ // 1 = the vivid default). The shader lifts any pale zone's OKLab chroma toward
108
+ // uVividness * VIVID_TARGET, hue-preserving, warm-anchored below the near-gray epsilon.
109
+ uniform float uVividness;
110
+
104
111
  // ── Time rate ────────────────────────────────────────────────────────────────
105
112
  // The authoring coefficients (uNucleiDrift, uPaletteDrift, uWarpDrift) live in a
106
113
  // human-friendly 0..~0.05 band — that scale is what the config schema and every
@@ -144,7 +151,7 @@ float vnoise(vec2 p) {
144
151
  // The rotated-octave FBM rotation constant — spliced from the shared chunk (AV.W2
145
152
  // — the one FBM_ROT). The loop below stays aurora-local (2.02 lacunarity +
146
153
  // uniform-driven octaves, per §3a — only the rotation constant converges).
147
- ${a}
154
+ ${n}
148
155
 
149
156
  float fbm(vec2 p) {
150
157
  float v = 0.0;
@@ -165,7 +172,7 @@ float fbm(vec2 p) {
165
172
  // lacunarity loop the rest of the warp uses). The curl operator is consumed ONLY on
166
173
  // the OPT-IN warpMode == "curl" branch (uWarpMode == 3) — the default warp paths are
167
174
  // byte-untouched (no curl call), so every existing aurora gate stays byte-equivalent.
168
- ${ge}
175
+ ${d}
169
176
 
170
177
  // The scalar fbm potential ψ for curlFBM — aurora's own fbm. The splice above
171
178
  // forward-declares potentialFBM; this defines it (ES 3.00 allows the prototype).
@@ -179,19 +186,19 @@ float potentialFBM(vec2 p) { return fbm(p); }
179
186
  // fbm above (the cost-tiering is preserved per AX.W12 §3a — this is an ADDITIVE basis,
180
187
  // not a blanket upgrade). The aurora-local hash21/vnoise/fbm loop stays (legitimately
181
188
  // divergent per the chunk's §3a).
182
- ${t}
189
+ ${a}
183
190
 
184
191
  // The sRGB OETF (linearToSrgb) — spliced from the shared procedural-color chunk
185
192
  // (AV.W2 — the single OETF source; the AV.W1 local copy is deleted here). Aurora's
186
193
  // linear pipeline closes the seam with this transfer before fragColor (the
187
194
  // proof:aurora-space-gamma seam).
188
- ${r}
195
+ ${s}
189
196
 
190
197
  // W5 — the four Ottosson OKLab/OKLCh matrices + their space fns, spliced from the
191
198
  // SAME shared chunk the goo-blob uses (zero new payload, 1e-6-verified). PI is
192
199
  // defined first because the chunk's oklabToOklch folds the hue into [0, 2pi).
193
200
  const float PI = 3.141592653589793;
194
- ${i}
201
+ ${l}
195
202
 
196
203
  // ── OKLCh palette ramp (W5 interp · AX.W11 single-sourced twin) ───────────────
197
204
  // The muddy-midtone kill: distant-hue stops interpolated by a plain linear mix()
@@ -203,7 +210,7 @@ ${i}
203
210
  // the smoothstep-eased samplePaletteRamp dispatcher) is SPLICED from the shared
204
211
  // procedural-color chunk (the single ramp source). composition.glsl.ts's
205
212
  // samplePalette calls samplePaletteRamp.
206
- ${n}
213
+ ${r}
207
214
 
208
215
  // Interleaved Gradient Noise (Jimenez) — a 1-LSB triangular dither applied in
209
216
  // DISPLAY space AFTER the OETF, so 8-bit mid-tone banding on the soft gradient is
@@ -279,30 +286,26 @@ vec2 domainWarp(vec2 p, float t) {
279
286
  return warped;
280
287
  }
281
288
 
282
- ` + _e + k + ve + k + "// ── 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" + ye + k + Ce + k + be + k + we + k + "\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 // 7 kuwahara (anisotropic-Kuwahara edge-preserving painterly finish — BB.W-AUR-KUWAHARA).\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). Kuwahara is OPT-IN (default-off — no preset selects\n // it unless a config explicitly sets medium:\"kuwahara\"); the smooth default + the\n // van-Gogh HERO + the existing painterly bodies are byte-unchanged by its addition.\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 else if (uMedium == 7) col = mediumKuwahara(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";
289
+ ` + be + k + xe + k + "// ── 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// BD.W-AUR-VIVIDNESS — the §3 chroma FLOOR. Operate in OKLab: lightness + hue are\n// untouched, chroma is lifted toward the floor. A pale zone BLOOMS to transmission-fit;\n// a vivid zone (already above the floor) is byte-untouched. The near-gray hue guard is\n// STRUCTURAL: below VIVID_EPS the (a,b) direction is precision noise, so the lift\n// synthesizes along the WARM anchor (amber, both components positive) — NEVER the noisy\n// near-zero vector, NEVER a cold/teal hue. uVividness:0 returns c identically (the gated\n// byte-identity opt-out). Mode factor: a dark route lifts the floor a touch (a dim field\n// needs more chroma to read vivid through glass) — derived from the field's own L.\nconst float VIVID_TARGET = 0.115;\nconst float VIVID_EPS = 0.012;\n// The warm amber anchor in OKLab (a,b): OKLCh hue 70°, unit chroma. cos/sin(70°) — both\n// POSITIVE (warm yellow-orange). The near-gray fallback direction, the warm-floor law.\nconst vec2 VIVID_WARM_ANCHOR = vec2(0.34202, 0.93969);\nvec3 vividnessFloor(vec3 c) {\n if (uVividness <= 0.0001) return c;\n vec3 lab = linOklab(c);\n float C = length(lab.yz);\n // Mode-aware: dim fields (low L) get a slightly higher target so they read vivid\n // through glass. mix factor is gentle (1.0 .. 1.18) so light routes are unaffected.\n float modeLift = mix(1.18, 1.0, clamp(lab.x * 1.4, 0.0, 1.0));\n float Cmin = uVividness * VIVID_TARGET * modeLift;\n vec2 hueDir = (C > VIVID_EPS) ? lab.yz / C : VIVID_WARM_ANCHOR;\n lab.yz = hueDir * max(C, Cmin);\n return max(oklabToLinearSrgb(lab), vec3(0.0));\n}\n\n" + Se + k + Ee + k + Ce + k + De + k + "\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 // 7 kuwahara (anisotropic-Kuwahara edge-preserving painterly finish — BB.W-AUR-KUWAHARA).\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). Kuwahara is OPT-IN (default-off — no preset selects\n // it unless a config explicitly sets medium:\"kuwahara\"); the smooth default + the\n // van-Gogh HERO + the existing painterly bodies are byte-unchanged by its addition.\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 else if (uMedium == 7) col = mediumKuwahara(col, pN, t);\n\n // Saturation trim\n col = saturate3(col, uSaturation);\n\n // BD.W-AUR-VIVIDNESS — the §3 chroma floor (warm-anchored, hue-preserving). Lifts a\n // pale zone toward the transmissive floor so warm glass over the field reads\n // transmissive-not-gray; a vivid zone is untouched; uVividness:0 is a no-op.\n col = vividnessFloor(col);\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";
283
290
  //#endregion
284
291
  //#region src/components/custom/aurora/constants/renderMode.ts
285
292
  function A() {
286
293
  let e;
287
294
  try {
288
- e = f();
295
+ e = p();
289
296
  } catch {
290
297
  return !1;
291
298
  }
292
299
  return e === null ? !1 : e.includes("swiftshader") || e.includes("llvmpipe") || e.includes("software") || e.includes("basic render") || e.includes("microsoft basic");
293
300
  }
294
- function Ee(e, t = {}) {
295
- if (e !== "css" && !t.forceWebGLUnderSoftwareRaster && A()) return "css";
296
- if (e !== "auto") return e;
297
- if (typeof navigator > "u" || typeof window > "u") return "webgl";
298
- let n = typeof navigator.hardwareConcurrency == "number" && navigator.hardwareConcurrency <= 4, r = typeof window.matchMedia == "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches, i = navigator.connection?.saveData === !0;
299
- return n || r || i ? "css" : "webgl";
301
+ function j(e, t = {}) {
302
+ return e !== "css" && !t.forceWebGLUnderSoftwareRaster && A() ? "css" : e === "auto" ? "webgl" : e;
300
303
  }
301
304
  //#endregion
302
305
  //#region src/components/custom/aurora/composables/glSetup.ts
303
- var j = "[Aurora]", De = /* @__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(".");
304
- function Oe(e, t, n) {
305
- let r = h(e, e.VERTEX_SHADER, t, j), i = h(e, e.FRAGMENT_SHADER, n, j), a = m(e, r, i, j);
306
+ var M = "[Aurora]", ke = /* @__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.uVividness".split(".");
307
+ function Ae(e, t, n) {
308
+ let r = _(e, e.VERTEX_SHADER, t, M), i = _(e, e.FRAGMENT_SHADER, n, M), a = g(e, r, i, M);
306
309
  e.useProgram(a);
307
310
  let o = e.createVertexArray();
308
311
  e.bindVertexArray(o);
@@ -318,7 +321,7 @@ function Oe(e, t, n) {
318
321
  let c = e.getAttribLocation(a, "aPos");
319
322
  e.enableVertexAttribArray(c), e.vertexAttribPointer(c, 2, e.FLOAT, !1, 0, 0);
320
323
  let l = {};
321
- for (let t of De) l[t] = e.getUniformLocation(a, t);
324
+ for (let t of ke) l[t] = e.getUniformLocation(a, t);
322
325
  return {
323
326
  program: a,
324
327
  vs: r,
@@ -332,69 +335,91 @@ function Oe(e, t, n) {
332
335
  }
333
336
  //#endregion
334
337
  //#region src/components/custom/aurora/composables/color.ts
335
- function M(e, t = 8, n) {
338
+ function N(e, t = 8, n) {
336
339
  let r = n ?? new Float32Array(t * 3), i = Math.min(e.length, t);
337
340
  for (let t = 0; t < i; t++) {
338
- let [n, i, a] = b(e[t]);
341
+ let [n, i, a] = x(e[t]);
339
342
  r[t * 3 + 0] = n, r[t * 3 + 1] = i, r[t * 3 + 2] = a;
340
343
  }
341
344
  for (let e = i; e < t; e++) r[e * 3 + 0] = 0, r[e * 3 + 1] = 0, r[e * 3 + 2] = 0;
342
345
  return r;
343
346
  }
344
- function N(e) {
347
+ function P(e) {
345
348
  if (e.length === 0) return "transparent";
346
- if (e.length === 1) return x(e[0]);
349
+ if (e.length === 1) return C(e[0]);
347
350
  let t = e.length;
348
351
  return `linear-gradient(135deg, ${e.map((e, n) => {
349
352
  let r = Math.round(n / (t - 1) * 100);
350
- return `${x(e)} ${r}%`;
353
+ return `${C(e)} ${r}%`;
351
354
  }).join(", ")})`;
352
355
  }
353
- function ke(e) {
354
- 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] = me(n / 255, r / 255, i / 255), [c, l, u] = pe(a, o, s);
356
+ function je(e) {
357
+ 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] = ve(n / 255, r / 255, i / 255), [c, l, u] = _e(a, o, s);
355
358
  return {
356
359
  L: c,
357
360
  C: l,
358
361
  h: u
359
362
  };
360
363
  }
361
- var Ae = [.35, .95];
362
- function P(e, t = {}) {
363
- let n = typeof e == "string" ? S(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] = Ae, m = n.L - a / 2, h = n.L + a / 2;
364
- m < f && (h += f - m, m = f), h > p && (m -= h - p, h = p), m = Math.max(f, m), h = Math.min(p, h);
365
- let g = [];
366
- for (let e = 0; e < d; e++) {
367
- let t = d === 1 ? 0 : e / (d - 1), r = m + (h - m) * F(t, c), a = n.C * o, f;
368
- if (l === "bell") f = a + (n.C - a) * je(t);
364
+ var Me = [.35, .95];
365
+ function F(e, t = {}) {
366
+ let n = typeof e == "string" ? w(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, avoidHues: d } = t, f = Math.max(2, Math.min(8, Math.round(r))), [p, m] = Me, h = n.L - a / 2, g = n.L + a / 2;
367
+ h < p && (g += p - h, h = p), g > m && (h -= g - m, g = m), h = Math.max(p, h), g = Math.min(m, g);
368
+ let _ = [];
369
+ for (let e = 0; e < f; e++) {
370
+ let t = f === 1 ? 0 : e / (f - 1), r = h + (g - h) * I(t, c), a = n.C * o, p;
371
+ if (l === "bell") p = a + (n.C - a) * Ne(t);
369
372
  else {
370
- let e = l === "sine" ? F(t, "sine") : t;
371
- f = n.C - (n.C - a) * e;
373
+ let e = l === "sine" ? I(t, "sine") : t;
374
+ p = n.C - (n.C - a) * e;
372
375
  }
373
- let p = te(n.h, i, s, t);
374
- u > 0 && (p = Fe(p, t, u)), g.push(ee({
376
+ let m = ie(n.h, i, s, t);
377
+ u > 0 && (m = Le(m, t, u)), d && d.length > 0 && (m = ze(m, d)), _.push(re({
375
378
  L: r,
376
- C: f,
377
- h: p
379
+ C: p,
380
+ h: m
378
381
  }));
379
382
  }
380
- return g;
383
+ return _;
381
384
  }
382
- function F(e, t) {
385
+ function I(e, t) {
383
386
  return t === "sine" ? .5 - .5 * Math.cos(Math.PI * e) : e;
384
387
  }
385
- function je(e) {
388
+ function Ne(e) {
386
389
  return Math.sin(Math.PI * e);
387
390
  }
388
- var Me = 70, Ne = 250, Pe = 22;
389
- function Fe(e, t, n) {
391
+ var Pe = 70, Fe = 250, Ie = 22;
392
+ function Le(e, t, n) {
390
393
  let r = (t - .5) * 2;
391
394
  if (r === 0 || n === 0) return e;
392
- let i = (fe(e, r >= 0 ? Me : Ne, 1, "shorter") - e + 540) % 360 - 180;
393
- return ((e + Math.sign(i) * Math.min(Math.abs(r) * n * Pe, Math.abs(i))) % 360 + 360) % 360;
395
+ let i = (ge(e, r >= 0 ? Pe : Fe, 1, "shorter") - e + 540) % 360 - 180;
396
+ return ((e + Math.sign(i) * Math.min(Math.abs(r) * n * Ie, Math.abs(i))) % 360 + 360) % 360;
397
+ }
398
+ var L = (e) => (e % 360 + 360) % 360;
399
+ function Re(e, t, n) {
400
+ let r = L(e), i = L(t), a = L(n);
401
+ return i <= a ? r >= i && r <= a : r >= i || r <= a;
402
+ }
403
+ function R(e, t) {
404
+ let n = Math.abs(L(e) - L(t)) % 360;
405
+ return n > 180 ? 360 - n : n;
406
+ }
407
+ function ze(e, t) {
408
+ let n = .5, r = L(e);
409
+ for (let e = 0; e < t.length + 2; e++) {
410
+ let e = !1;
411
+ for (let [i, a] of t) {
412
+ if (!Re(r, i, a)) continue;
413
+ let t = L(i - n), o = L(a + n);
414
+ r = R(r, t) <= R(r, o) ? t : o, e = !0;
415
+ }
416
+ if (!e) return r;
417
+ }
418
+ return r;
394
419
  }
395
420
  //#endregion
396
421
  //#region src/components/custom/aurora/composables/uniformBridge.ts
397
- var I = {
422
+ var z = {
398
423
  smooth: 0,
399
424
  pastel: 1,
400
425
  watercolor: 2,
@@ -403,48 +428,48 @@ var I = {
403
428
  vangogh: 5,
404
429
  "oil-pastel": 6,
405
430
  kuwahara: 7
406
- }, L = {
431
+ }, B = {
407
432
  shorter: 0,
408
433
  longer: 1,
409
434
  increasing: 2,
410
435
  decreasing: 3
411
- }, Ie = {
436
+ }, Be = {
412
437
  none: 0,
413
438
  radial: 1,
414
439
  swirl: 2,
415
440
  diagonal: 3,
416
441
  multi: 4,
417
442
  tensor: 5
418
- }, R = {
443
+ }, V = {
419
444
  flow: 0,
420
445
  tensor: 1
421
- }, z = {
446
+ }, H = {
422
447
  fbm: 0,
423
448
  cellular: 1,
424
449
  hybrid: 2,
425
450
  curl: 3
426
- }, Le = {
451
+ }, Ve = {
427
452
  oil: 0,
428
453
  knife: 1,
429
454
  chunky: 3
430
- }, B = {
455
+ }, U = {
431
456
  L: .985,
432
457
  C: .0125,
433
458
  h: 77.5
434
459
  };
435
- function Re(e) {
436
- return e === void 0 ? ne(B.L, B.C, B.h) : Array.isArray(e) ? e : ne(e.L, e.C, e.h);
460
+ function He(e) {
461
+ return e === void 0 ? S(U.L, U.C, U.h) : Array.isArray(e) ? e : S(e.L, e.C, e.h);
437
462
  }
438
- function ze(e) {
439
- return I[e.medium];
463
+ function Ue(e) {
464
+ return z[e.medium];
440
465
  }
441
- function Be(e) {
442
- return Le[e.strokeMode];
466
+ function We(e) {
467
+ return Ve[e.strokeMode];
443
468
  }
444
- function Ve(e) {
445
- return e.medium === "vangogh" || e.medium === "oil-pastel" || e.medium === "crayon" || e.medium === "kuwahara" ? R.tensor : R[e.strokeOrient ?? "flow"];
469
+ function Ge(e) {
470
+ return e.medium === "vangogh" || e.medium === "oil-pastel" || e.medium === "crayon" || e.medium === "kuwahara" ? V.tensor : V[e.strokeOrient ?? "flow"];
446
471
  }
447
- function He(e, t, n, r) {
472
+ function Ke(e, t, n, r) {
448
473
  let i = (e) => 1 - e, a = {
449
474
  palette: new Float32Array(24),
450
475
  pos: new Float32Array(12),
@@ -457,7 +482,7 @@ function He(e, t, n, r) {
457
482
  angle: new Float32Array(6)
458
483
  };
459
484
  return function(o) {
460
- e.useProgram(t), M(o.palette, 8, a.palette), e.uniform3fv(n.uPalette, a.palette), e.uniform1i(n.uStopCount, Math.min(o.palette.length, 8));
485
+ 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));
461
486
  let s = Math.min(o.nuclei.length, 6);
462
487
  e.uniform1i(n.uNucleiCount, s);
463
488
  for (let e = 0; e < s; e++) {
@@ -465,22 +490,22 @@ function He(e, t, n, r) {
465
490
  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;
466
491
  }
467
492
  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;
468
- 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, ze(o)), e.uniform1i(n.uHuePath, L[o.huePath ?? "shorter"]), e.uniform1i(n.uFlowPattern, Ie[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, Be(o)), e.uniform1i(n.uStrokeOrient, Ve(o));
493
+ 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, H[o.warpMode]), e.uniform1i(n.uNoiseOctaves, o.noiseOctaves), e.uniform1i(n.uMedium, Ue(o)), e.uniform1i(n.uHuePath, B[o.huePath ?? "shorter"]), e.uniform1i(n.uFlowPattern, Be[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, We(o)), e.uniform1i(n.uStrokeOrient, Ge(o));
469
494
  {
470
495
  let t = o.lightDir ?? [
471
496
  -.5,
472
497
  .6,
473
498
  .62
474
- ], r = Re(o.lightColor);
499
+ ], r = He(o.lightColor);
475
500
  e.uniform3f(n.uLightDir, t[0], t[1], t[2]), e.uniform3f(n.uLightColor, r[0], r[1], r[2]);
476
501
  }
477
- 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);
502
+ 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), e.uniform1f(n.uVividness, o.vividness ?? 1);
478
503
  };
479
504
  }
480
505
  //#endregion
481
506
  //#region src/components/custom/aurora/composables/cursorModel.ts
482
- var V = .22, Ue = .18, We = .992, Ge = .96, H = .3;
483
- function Ke() {
507
+ var W = .22, qe = .18, Je = .992, Ye = .96, G = .3;
508
+ function Xe() {
484
509
  return {
485
510
  x: .5,
486
511
  y: .5,
@@ -494,48 +519,51 @@ function Ke() {
494
519
  burst: 0
495
520
  };
496
521
  }
497
- function U(e, t = 1) {
522
+ function K(e, t = 1) {
498
523
  let n = Math.max(0, Math.min(1, t));
499
- e.x += (e.targetX - e.x) * V, e.y += (e.targetY - e.y) * V, e.strength += (e.targetStrength - e.strength) * Ue, e.targetStrength *= We, e.velX *= (1 - H) * n, e.velY *= (1 - H) * n, e.burst *= Ge * n;
524
+ e.x += (e.targetX - e.x) * W, e.y += (e.targetY - e.y) * W, e.strength += (e.targetStrength - e.strength) * qe, e.targetStrength *= Je, e.velX *= (1 - G) * n, e.velY *= (1 - G) * n, e.burst *= Ye * n;
500
525
  }
501
- function qe(e, t, n) {
526
+ function Ze(e, t, n) {
502
527
  let r = .12, i = Math.max(-r, Math.min(r, t)), a = Math.max(-r, Math.min(r, n));
503
- e.velX += (i - e.velX) * H, e.velY += (a - e.velY) * H;
528
+ e.velX += (i - e.velX) * G, e.velY += (a - e.velY) * G;
504
529
  let o = Math.hypot(i, a);
505
530
  e.burst = Math.min(1, e.burst + o * 4);
506
531
  }
507
- function W(e) {
532
+ function q(e) {
508
533
  return e.targetStrength > .001 || e.strength > .001 || Math.abs(e.x - e.targetX) > .001 || Math.abs(e.y - e.targetY) > .001;
509
534
  }
510
535
  //#endregion
511
536
  //#region src/components/custom/aurora/composables/frameLoop.ts
512
- function Je(e) {
513
- let { gl: t, prog: n, uniforms: r, cursor: i, getConfig: a, getReducedMotion: o } = e, s = (e) => 1 - e;
514
- function c() {
537
+ function Qe(e) {
538
+ let { gl: t, prog: n, uniforms: r, cursor: i, getConfig: a, getReducedMotion: o, pointerField: s } = e, c = (e) => 1 - e, l = null;
539
+ function u() {
515
540
  return +!o();
516
541
  }
517
- function l(e) {
518
- 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) {
519
- 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;
520
- 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);
542
+ function d(e) {
543
+ if (t.useProgram(n), t.uniform2f(r.uCursor, i.x, c(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) {
544
+ let n = u(), a = e * .25, o = Math.cos(a) * .5, s = Math.sin(a) * .5, c = (i.x - .5) * 2, l = (.5 - i.y) * 2, d = i.strength * n;
545
+ o = o * (1 - d) + c * d, s = s * (1 - d) + l * d, t.uniform3f(r.uLightDir, o * n + (1 - n) * -.5, s * n + (1 - n) * .6, .62);
521
546
  }
522
547
  t.clear(t.COLOR_BUFFER_BIT), t.drawArrays(t.TRIANGLES, 0, 3);
523
548
  }
524
- function u() {
549
+ function f() {
525
550
  if (o()) return !1;
526
551
  let e = a();
527
- return e.nucleiDrift !== 0 || e.paletteDrift !== 0 || e.breathDepth !== 0 || e.warpDrift !== 0 || e.interactivity?.light && i.burst > .001 ? !0 : W(i);
552
+ return e.nucleiDrift !== 0 || e.paletteDrift !== 0 || e.breathDepth !== 0 || e.warpDrift !== 0 || e.interactivity?.light && i.burst > .001 ? !0 : q(i);
528
553
  }
529
554
  return {
530
555
  frame: (e) => {
531
- U(i, c()), l(e);
556
+ let t = l === null ? 0 : Math.max(0, (e - l) * 1e3);
557
+ l = e, s.tick(u() === 0 ? 0 : t);
558
+ let n = s.acceleration.value, r = s.velocity.value, a = -(n.x * r.x + n.y * r.y);
559
+ a > 0 && u() > 0 && (i.burst = Math.min(1, i.burst + a * 6)), K(i, u()), d(e);
532
560
  },
533
- needsAnimation: u
561
+ needsAnimation: f
534
562
  };
535
563
  }
536
564
  //#endregion
537
565
  //#region src/components/custom/aurora/constants/shaders/aurora.wgsl.ts
538
- var Ye = `
566
+ var $e = `
539
567
  const MAX_NUCLEI: i32 = 6;
540
568
  const MAX_STOPS: i32 = 8;
541
569
  const PI: f32 = 3.141592653589793;
@@ -552,7 +580,8 @@ struct Uniforms {
552
580
  scalars1: vec4<f32>,
553
581
  // scalars2: (uAlpha, uNucleiDrift, uPaletteDrift, uBreathDepth)
554
582
  scalars2: vec4<f32>,
555
- // scalars3: (uBreathPeriod, uCursorStrength, uCursorRadius, _pad)
583
+ // scalars3: (uBreathPeriod, uCursorStrength, uCursorRadius, uVividness)
584
+ // BD.W-AUR-VIVIDNESS — the .w pad lane now carries the §3 chroma-floor strength.
556
585
  scalars3: vec4<f32>,
557
586
  // cursor: (uCursor.x, uCursor.y, _pad, _pad)
558
587
  cursor: vec4<f32>,
@@ -564,6 +593,16 @@ struct Uniforms {
564
593
  nuc0: array<vec4<f32>, 6>,
565
594
  nuc1: array<vec4<f32>, 6>,
566
595
  nuc2: array<vec4<f32>, 6>,
596
+ // BC.W-VIZ-AURORA (T4) — the painterly-medium scalar lanes. APPENDED after the
597
+ // arrays so EVERY existing offset (and the smooth-default parity capture) is
598
+ // byte-identical (these lanes are written 0 on a smooth config). scalars4:
599
+ // (uStrokeAmount, uStrokeScale, uStrokeAnisotropy, uCanvasGrain). scalars5:
600
+ // (uWetEdge, uGranulation, uBrokenColor, uKuwaharaQ). kuwahara: (uKuwaharaRadius,
601
+ // uKuwaharaSectors, _, _) — the Kuwahara radius/sectors/q in lockstep with
602
+ // packAuroraWGPUUniforms (the typed-struct discipline).
603
+ scalars4: vec4<f32>,
604
+ scalars5: vec4<f32>,
605
+ kuwahara: vec4<f32>,
567
606
  };
568
607
 
569
608
  @group(0) @binding(0) var<uniform> u: Uniforms;
@@ -597,7 +636,7 @@ fn vnoise(p: vec2<f32>) -> f32 {
597
636
  return mix(mix(a, b, uu.x), mix(c, d, uu.x), uu.y);
598
637
  }
599
638
 
600
- ${p}
639
+ ${u}
601
640
 
602
641
  fn fbm(p0: vec2<f32>) -> f32 {
603
642
  var v = 0.0;
@@ -613,9 +652,9 @@ fn fbm(p0: vec2<f32>) -> f32 {
613
652
  return v;
614
653
  }
615
654
 
616
- ${u}
617
- ${d}
655
+ ${o}
618
656
  ${c}
657
+ ${i}
619
658
 
620
659
  // Interleaved Gradient Noise (Jimenez) — display-space 1-LSB dither.
621
660
  fn ign(p: vec2<f32>) -> f32 {
@@ -752,6 +791,27 @@ fn saturate3(c: vec3<f32>, amt: f32) -> vec3<f32> {
752
791
  return max(oklabToLinearSrgb(oklchToOklab(lch)), vec3<f32>(0.0));
753
792
  }
754
793
 
794
+ // BD.W-AUR-VIVIDNESS — the §3 chroma FLOOR, the WGSL twin of the GLSL vividnessFloor.
795
+ // OKLab chroma lifted toward the floor, hue + L preserved; below VIVID_EPS the (a,b)
796
+ // direction is precision noise so the lift synthesizes along the WARM anchor (amber,
797
+ // both components positive) — never teal/navy. vividness (scalars3.w) == 0 is a no-op
798
+ // (the byte-identity opt-out). Mode factor lifts a dim field a touch (read through glass).
799
+ const VIVID_TARGET: f32 = 0.115;
800
+ const VIVID_EPS: f32 = 0.012;
801
+ const VIVID_WARM_ANCHOR: vec2<f32> = vec2<f32>(0.34202, 0.93969);
802
+ fn vividnessFloor(c: vec3<f32>, vividness: f32) -> vec3<f32> {
803
+ if (vividness <= 0.0001) { return c; }
804
+ var lab = linOklab(c);
805
+ let C = length(lab.yz);
806
+ let modeLift = mix(1.18, 1.0, clamp(lab.x * 1.4, 0.0, 1.0));
807
+ let Cmin = vividness * VIVID_TARGET * modeLift;
808
+ var hueDir = VIVID_WARM_ANCHOR;
809
+ if (C > VIVID_EPS) { hueDir = lab.yz / C; }
810
+ let yz = hueDir * max(C, Cmin);
811
+ lab = vec3<f32>(lab.x, yz.x, yz.y);
812
+ return max(oklabToLinearSrgb(lab), vec3<f32>(0.0));
813
+ }
814
+
755
815
  // ── PBR-Neutral tonemap (named aces() in the GLSL — the tonemap slot) ──
756
816
  fn aces(color0: vec3<f32>) -> vec3<f32> {
757
817
  let startCompression = 0.8 - 0.04;
@@ -770,6 +830,286 @@ fn aces(color0: vec3<f32>) -> vec3<f32> {
770
830
  return clamp(mix(color, newPeak * vec3<f32>(1.0), g), vec3<f32>(0.0), vec3<f32>(1.0));
771
831
  }
772
832
 
833
+ // ── BC.W-VIZ-AURORA (T4) — the painterly-medium bodies (the keystone WGSL port) ──
834
+ // Spliced HERE, after every helper the mediums consume is defined (domainWarp,
835
+ // nucleiField, samplePalette, linOklab, saturate3, oklabToOklch/oklchToOklab,
836
+ // hash21/vnoise/fbm) and before the vertex/fragment stages. applyMedium(col,p,t)
837
+ // is the dispatch the fragment stage calls.
838
+
839
+ // ── Painterly luma + medium scalar accessors ───────────────────────────────
840
+ const W_LUMA: vec3<f32> = vec3<f32>(0.2126, 0.7152, 0.0722);
841
+
842
+ fn mStrokeAmount() -> f32 { return u.scalars4.x; }
843
+ fn mStrokeScale() -> f32 { return u.scalars4.y; }
844
+ fn mStrokeAnisotropy() -> f32 { return u.scalars4.z; }
845
+ fn mCanvasGrain() -> f32 { return u.scalars4.w; }
846
+ fn mWetEdge() -> f32 { return u.scalars5.x; }
847
+ fn mGranulation() -> f32 { return u.scalars5.y; }
848
+ fn mBrokenColor() -> f32 { return u.scalars5.z; }
849
+ fn mKuwaharaQ() -> f32 { return u.scalars5.w; }
850
+ fn mKuwaharaRadius() -> f32 { return u.kuwahara.x; }
851
+
852
+ // ── Base color re-sample (the edge-mask operator the mediums Sobel/sector-sample) ──
853
+ // The WGSL twin of mediums.glsl.ts sampleBase: warp -> nuclei field -> palette -> value.
854
+ fn sampleBase(p: vec2<f32>, t: f32) -> vec3<f32> {
855
+ let pw = domainWarp(p, t);
856
+ let nf = nucleiField(pw, t);
857
+ var c = samplePalette(nf.paletteId);
858
+ c = c * (1.0 + u.scalars0.z * nf.valueMod);
859
+ return c;
860
+ }
861
+
862
+ // ── Broken color (hue + chroma jitter at constant OKLCh L) ──────────────────
863
+ fn brokenColorJitter(c: vec3<f32>, hueSeed: f32, valueSeed: f32, strength: f32) -> vec3<f32> {
864
+ let amt = clamp(mBrokenColor() * strength, 0.0, 1.0);
865
+ if (amt <= 0.001) { return c; }
866
+ var lch = oklabToOklch(linOklab(c));
867
+ lch.z = lch.z + (hueSeed - 0.5) * (32.0 * PI / 180.0) * amt;
868
+ lch.y = max(lch.y * (1.0 + (valueSeed - 0.5) * 0.28 * amt), 0.0);
869
+ return max(oklabToLinearSrgb(oklchToOklab(lch)), vec3<f32>(0.0));
870
+ }
871
+
872
+ // ── Structure-tensor / edge-tangent-flow (AW.W4.1; the WGSL twin) ───────────
873
+ // Returns vec3(tangent.x, tangent.y, A) — the minor-eigenvector edge-tangent + coherence.
874
+ fn structureTensorField(p: vec2<f32>, t: f32, fallbackDir: vec2<f32>) -> vec3<f32> {
875
+ let e = 0.0035;
876
+ let l00 = dot(sampleBase(p + vec2<f32>(-e, -e), t), W_LUMA);
877
+ let l10 = dot(sampleBase(p + vec2<f32>(0.0, -e), t), W_LUMA);
878
+ let l20 = dot(sampleBase(p + vec2<f32>( e, -e), t), W_LUMA);
879
+ let l01 = dot(sampleBase(p + vec2<f32>(-e, 0.0), t), W_LUMA);
880
+ let l21 = dot(sampleBase(p + vec2<f32>( e, 0.0), t), W_LUMA);
881
+ let l02 = dot(sampleBase(p + vec2<f32>(-e, e), t), W_LUMA);
882
+ let l12 = dot(sampleBase(p + vec2<f32>(0.0, e), t), W_LUMA);
883
+ let l22 = dot(sampleBase(p + vec2<f32>( e, e), t), W_LUMA);
884
+ let Gx = (l20 + 2.0 * l21 + l22) - (l00 + 2.0 * l01 + l02);
885
+ let Gy = (l02 + 2.0 * l12 + l22) - (l00 + 2.0 * l10 + l20);
886
+ let Jxx = Gx * Gx;
887
+ let Jyy = Gy * Gy;
888
+ let Jxy = Gx * Gy;
889
+ let tr = Jxx + Jyy;
890
+ let disc = sqrt(max((Jxx - Jyy) * (Jxx - Jyy) + 4.0 * Jxy * Jxy, 0.0));
891
+ let lambdaMaj = 0.5 * (tr + disc);
892
+ let lambdaMin = 0.5 * (tr - disc);
893
+ var tangent: vec2<f32>;
894
+ if (disc < 1e-7) {
895
+ tangent = fallbackDir;
896
+ } else {
897
+ let theta = 0.5 * atan2(2.0 * Jxy, Jxx - Jyy);
898
+ tangent = vec2<f32>(-sin(theta), cos(theta));
899
+ }
900
+ var A = (lambdaMaj - lambdaMin) / (lambdaMaj + lambdaMin + 1e-6);
901
+ A = clamp(A, 0.0, 1.0);
902
+ if (dot(tangent, fallbackDir) < 0.0) { tangent = -tangent; }
903
+ let blendW = pow(A, 0.28);
904
+ let dir = normalize(mix(fallbackDir, tangent, blendW) + vec2<f32>(1e-6, 1e-6));
905
+ return vec3<f32>(dir, A);
906
+ }
907
+
908
+ // ── Flow field (the WGSL twin — the simple flow seed the medium fallback dirs use) ──
909
+ fn flowField(p: vec2<f32>, t: f32) -> vec2<f32> {
910
+ // Curl-noise-driven swirl (the medium-default dir; the full radial/swirl/diagonal
911
+ // dispatch rides uniforms the WGSL struct does not carry — the procedural mediums
912
+ // use this multi/curl seed as the fallback the structure tensor relaxes toward).
913
+ let n = fbm(p * 2.0 + vec2<f32>(t * 0.02, t * 0.02));
914
+ let a = n * 6.2831853;
915
+ var dir = vec2<f32>(cos(a), sin(a));
916
+ // Cursor swirl bias (matches the GLSL flow cursor term — the steady attraction).
917
+ let cursorStrength = u.scalars3.y;
918
+ let cursorRadius = u.scalars3.z;
919
+ let cursor = u.cursor.xy;
920
+ if (cursorStrength > 0.001) {
921
+ let toCur = cursor - p;
922
+ let d = length(toCur);
923
+ let r = max(cursorRadius, 0.01);
924
+ let w = exp(-(d * d) / (r * r * 0.5));
925
+ let tangent = vec2<f32>(-toCur.y, toCur.x) / max(d, 1e-4);
926
+ let a0 = atan2(dir.y, dir.x);
927
+ let a1 = atan2(tangent.y, tangent.x);
928
+ var da = a1 - a0;
929
+ da = atan2(sin(da), cos(da));
930
+ let aa = a0 + da * w * cursorStrength;
931
+ dir = vec2<f32>(cos(aa), sin(aa));
932
+ }
933
+ return dir;
934
+ }
935
+
936
+ // ── Watercolor — wet-edge cauliflowers + granulation + wash banding ─────────
937
+ fn mediumWatercolor(col0: vec3<f32>, p: vec2<f32>, t: f32) -> vec3<f32> {
938
+ var col = col0;
939
+ let eps = 0.004;
940
+ let cx1 = sampleBase(p + vec2<f32>(eps, 0.0), t);
941
+ let cx2 = sampleBase(p - vec2<f32>(eps, 0.0), t);
942
+ let cy1 = sampleBase(p + vec2<f32>(0.0, eps), t);
943
+ let cy2 = sampleBase(p - vec2<f32>(0.0, eps), t);
944
+ let gx = dot(cx1 - cx2, W_LUMA);
945
+ let gy = dot(cy1 - cy2, W_LUMA);
946
+ let edge = sqrt(gx * gx + gy * gy) / (2.0 * eps);
947
+ let mask = smoothstep(0.0, 2.5, edge);
948
+ col = col * mix(1.0, 0.78, mask * mWetEdge());
949
+
950
+ let paper = 0.5 * vnoise(p * 160.0) + 0.5 * vnoise(p * 360.0);
951
+ let pigLoad = 1.0 - dot(col, W_LUMA);
952
+ col = col * (1.0 - mGranulation() * pigLoad * (paper - 0.5));
953
+
954
+ let band = fbm(vec2<f32>(p.x * 1.5, p.y * 0.4));
955
+ col = col * (1.0 + 0.04 * (band - 0.5));
956
+ return col;
957
+ }
958
+
959
+ // ── Pastel — anisotropic stroke noise along the flow + tooth grain ──────────
960
+ fn mediumPastel(col0: vec3<f32>, p: vec2<f32>, t: f32) -> vec3<f32> {
961
+ var col = col0;
962
+ let flow = flowField(p, t);
963
+ let perp = vec2<f32>(-flow.y, flow.x);
964
+ let strokeScale = mStrokeScale();
965
+ let along = dot(p, flow) * strokeScale;
966
+ var across = dot(p, perp) * strokeScale;
967
+ across = across + 0.03 * (vnoise(p * 260.0) - 0.5);
968
+
969
+ let aniso = mix(1.0, 0.18, mStrokeAnisotropy());
970
+ let stroke = fbm(vec2<f32>(along * aniso, across));
971
+ col = col * mix(1.0, 0.82 + 0.32 * stroke, mStrokeAmount());
972
+
973
+ let tooth = vnoise(p * 800.0);
974
+ col = col * (1.0 - 0.08 * mStrokeAmount() * (tooth - 0.5));
975
+ return col;
976
+ }
977
+
978
+ // ── Crayon — DRY wax pigment on paper tooth (the tooth-multiply + scumble) ──
979
+ fn mediumCrayon(col0: vec3<f32>, p: vec2<f32>, t: f32) -> vec3<f32> {
980
+ let flow = structureTensorField(p, t, flowField(p, t)).xy;
981
+ let ang = atan2(flow.y, flow.x);
982
+ let ca = cos(-ang);
983
+ let sa = sin(-ang);
984
+ let pr = vec2<f32>(p.x * ca - p.y * sa, p.x * sa + p.y * ca);
985
+
986
+ let aniso = mix(0.45, 0.95, mStrokeAnisotropy());
987
+ let scale = max(mStrokeScale() * 1.6, 180.0);
988
+
989
+ let t1 = vnoise(vec2<f32>(pr.x * scale * aniso, pr.y * scale));
990
+ let t2 = vnoise(vec2<f32>(pr.x * scale * aniso * 0.4, pr.y * scale * 0.4) + vec2<f32>(11.0, 11.0));
991
+ let t3 = vnoise(vec2<f32>(pr.x * scale * aniso * 2.1, pr.y * scale * 2.1) + vec2<f32>(23.0, 23.0));
992
+ let tooth = 0.55 * t1 + 0.30 * t2 + 0.15 * t3;
993
+
994
+ let pressure = clamp(mStrokeAmount(), 0.0, 1.0);
995
+ let bite = mix(0.18, 0.46, pressure);
996
+ var result = col0 * (1.0 - bite * (0.5 - tooth));
997
+
998
+ let scumbleMask = smoothstep(0.42, 0.78, vnoise(vec2<f32>(pr.x * scale * 0.55, pr.y * scale * 0.22) + vec2<f32>(7.0, 7.0)));
999
+ let scumbleCoverage = scumbleMask * pressure * 0.7;
1000
+ let paper = col0 * mix(0.80, 0.94, pressure);
1001
+ result = mix(paper, result, scumbleCoverage);
1002
+
1003
+ let pigmentCell = floor(pr * max(scale * 0.18, 32.0));
1004
+ let pigmentMask = smoothstep(0.28, 0.82, vnoise(pr * scale * 0.21 + vec2<f32>(19.0, 19.0)));
1005
+ result = brokenColorJitter(
1006
+ result,
1007
+ hash21(pigmentCell + vec2<f32>(17.0, 17.0)),
1008
+ hash21(pigmentCell * 2.1 + vec2<f32>(31.0, 31.0)),
1009
+ 0.45 + 0.55 * pigmentMask
1010
+ );
1011
+
1012
+ result = saturate3(result, 1.08);
1013
+ return result;
1014
+ }
1015
+
1016
+ // ── Kuwahara — the SOFT anisotropic generalized Kuwahara painterly finish ────
1017
+ // THE KEYSTONE (Kyprianidis 2010). Aurora is a PROCEDURAL field (no input texture) so the
1018
+ // operator runs over sampleBase directly: an elliptical kernel oriented along the structure
1019
+ // tensor (elongated along the edge-tangent, narrowed across by the anisotropy), 8 OVERLAPPING
1020
+ // sectors via SMOOTH gaussian-angular weights, blended by 1/(1+var^q) (the SOFT criterion →
1021
+ // no 8-spoke pinwheel by construction). 4 rings × 8 angular taps = 32 procedural samples.
1022
+ fn mediumKuwahara(col0: vec3<f32>, p: vec2<f32>, t: f32) -> vec3<f32> {
1023
+ let stf = structureTensorField(p, t, flowField(p, t));
1024
+ let tangent = stf.xy;
1025
+ let A = stf.z;
1026
+ // The studio knob drives the patch radius; clamp into the procedural patch band.
1027
+ let radius = clamp(mKuwaharaRadius() + mStrokeScale() * 0.00018, 0.006, 0.024);
1028
+ let aniso = mix(1.0, 0.34, clamp(A, 0.0, 1.0));
1029
+ let ax = tangent;
1030
+ let ay = vec2<f32>(-tangent.y, tangent.x);
1031
+
1032
+ // Per-sector weighted sums (8 sectors). WGSL has no runtime-indexed local arrays of
1033
+ // vectors as cleanly as GLSL, so unroll the sector accumulators explicitly.
1034
+ var mSum: array<vec3<f32>, 8>;
1035
+ var sSum: array<vec3<f32>, 8>;
1036
+ var wSum: array<f32, 8>;
1037
+ for (var i = 0; i < 8; i = i + 1) {
1038
+ mSum[i] = vec3<f32>(0.0);
1039
+ sSum[i] = vec3<f32>(0.0);
1040
+ wSum[i] = 0.0;
1041
+ }
1042
+
1043
+ let RINGS = 4;
1044
+ let TAPS = 8;
1045
+ for (var r = 1; r <= RINGS; r = r + 1) {
1046
+ let rr = f32(r) / f32(RINGS);
1047
+ let radial = 1.0 - 0.6 * rr;
1048
+ for (var a = 0; a < TAPS; a = a + 1) {
1049
+ let ang = 6.2831853 * f32(a) / f32(TAPS);
1050
+ let dirE = ax * cos(ang) + ay * (sin(ang) * aniso);
1051
+ let off = dirE * (radius * rr);
1052
+ let c = sampleBase(p + off, t);
1053
+ for (var k = 0; k < 8; k = k + 1) {
1054
+ let sectorAng = 6.2831853 * f32(k) / 8.0;
1055
+ var d = ang - sectorAng;
1056
+ d = atan2(sin(d), cos(d));
1057
+ let sw = exp(-d * d * 2.2) * radial;
1058
+ mSum[k] = mSum[k] + c * sw;
1059
+ sSum[k] = sSum[k] + c * c * sw;
1060
+ wSum[k] = wSum[k] + sw;
1061
+ }
1062
+ }
1063
+ }
1064
+
1065
+ let q = max(mKuwaharaQ(), 1.0);
1066
+ var accum = vec3<f32>(0.0);
1067
+ var accumW = 0.0;
1068
+ for (var k = 0; k < 8; k = k + 1) {
1069
+ let wk = max(wSum[k], 1e-4);
1070
+ let mean = mSum[k] / wk;
1071
+ let var3 = max(sSum[k] / wk - mean * mean, vec3<f32>(0.0));
1072
+ let variance = dot(var3, W_LUMA);
1073
+ let sectorW = 1.0 / (1.0 + pow(variance * 320.0, q));
1074
+ accum = accum + mean * sectorW;
1075
+ accumW = accumW + sectorW;
1076
+ }
1077
+ var result = accum / max(accumW, 1e-4);
1078
+
1079
+ // Anchor in near-flat zones so the smoothing only bites at edges.
1080
+ let center = sampleBase(p, t);
1081
+ result = mix(center, result, clamp(0.35 + 0.65 * A, 0.0, 1.0));
1082
+
1083
+ // Faint canvas tooth + pigment-saturation so the flattened patches read as oil paint.
1084
+ let tooth = vnoise(p * 300.0) - 0.5;
1085
+ result = result * (1.0 + tooth * 0.06 * mCanvasGrain());
1086
+ result = saturate3(result, 1.04);
1087
+
1088
+ return mix(col0, result, clamp(mStrokeAmount(), 0.0, 1.0));
1089
+ }
1090
+
1091
+ // ── Medium dispatch (the WGSL twin of the GLSL main() if-ladder) ────────────
1092
+ // uMedium: 0 smooth · 1 pastel · 2 watercolor · 3 oil · 4 crayon · 5 vangogh ·
1093
+ // 6 oil-pastel · 7 kuwahara. The cheap peer mediums (pastel/watercolor/crayon) +
1094
+ // kuwahara port their own bodies; the oil/vangogh/oil-pastel STROKE cascade renders
1095
+ // the anisotropic-Kuwahara PAINTERLY finish here (a real oil-paint read, never the
1096
+ // smooth core — the "NO FALLBACKS on Safari" mandate; the full per-dab Starry-Night
1097
+ // stroke WGSL is the booked W-AURORA-WGPU-MEDIUMS-STROKES tail).
1098
+ fn applyMedium(col: vec3<f32>, p: vec2<f32>, t: f32) -> vec3<f32> {
1099
+ let medium = u.ints1.x;
1100
+ if (medium == 1) { return mediumPastel(col, p, t); }
1101
+ if (medium == 2) { return mediumWatercolor(col, p, t); }
1102
+ if (medium == 4) { return mediumCrayon(col, p, t); }
1103
+ // oil(3) / vangogh(5) / oil-pastel(6) / kuwahara(7) all render the painterly
1104
+ // anisotropic-Kuwahara finish on the WGSL primary (the WebGL2 fallback carries the
1105
+ // full per-dab stroke cascade for oil/vangogh/oil-pastel).
1106
+ if (medium == 3 || medium == 5 || medium == 6 || medium == 7) {
1107
+ return mediumKuwahara(col, p, t);
1108
+ }
1109
+ return col;
1110
+ }
1111
+
1112
+
773
1113
  // ── Full-screen triangle vertex stage (the pilot idiom — no vertex buffer) ──
774
1114
  struct VSOut {
775
1115
  @builtin(position) pos: vec4<f32>,
@@ -799,6 +1139,7 @@ fn fs_main(in: VSOut) -> @location(0) vec4<f32> {
799
1139
  let saturation = u.scalars1.z;
800
1140
  let paperGrain = u.scalars1.w;
801
1141
  let alpha = u.scalars2.x;
1142
+ let vividness = u.scalars3.w;
802
1143
 
803
1144
  let uv = in.uv;
804
1145
  let pN = uv;
@@ -813,13 +1154,20 @@ fn fs_main(in: VSOut) -> @location(0) vec4<f32> {
813
1154
  let breath = sin(t * 6.2831 / max(breathPeriod, 1.0));
814
1155
  col = col * (1.0 + breathDepth * breath * 0.5);
815
1156
 
816
- // The painterly mediums (uMedium 1-6) are the WebGL2 fallback's register; the WGSL
817
- // primary renders the smooth core for the default parity capture (the booked
818
- // W-AURORA-WGPU-MEDIUMS successor ports the painterly bodies).
1157
+ // BC.W-VIZ-AURORA (T4) — the painterly-medium dispatch. uMedium 0 (smooth) is a
1158
+ // no-op pass-through so the DEFAULT smooth config is byte-identical to the prior
1159
+ // WGSL primary (the parity capture); uMedium 1-7 render the ported painterly
1160
+ // bodies (pastel/watercolor/crayon/kuwahara) or the anisotropic-Kuwahara finish
1161
+ // (oil/vangogh/oil-pastel) — never a silent smooth degrade on Safari 26.
1162
+ col = applyMedium(col, pN, t);
819
1163
 
820
1164
  // Saturation trim.
821
1165
  col = saturate3(col, saturation);
822
1166
 
1167
+ // BD.W-AUR-VIVIDNESS — the §3 chroma floor (warm-anchored, hue-preserving). The WGSL
1168
+ // twin of the GLSL call; vividness:0 is a no-op so the smooth/pale default stays gated.
1169
+ col = vividnessFloor(col, vividness);
1170
+
823
1171
  // Tonemap + film grain.
824
1172
  col = aces(col);
825
1173
  // gl_FragCoord.xy → the device-pixel position (the WGSL @builtin(position)).
@@ -838,7 +1186,7 @@ fn fs_main(in: VSOut) -> @location(0) vec4<f32> {
838
1186
  // Premultiply on DISPLAY-space color (matches the WebGL2 path: OETF, then premul).
839
1187
  return vec4<f32>(col * alpha, alpha);
840
1188
  }
841
- `, G = {
1189
+ `, J = {
842
1190
  scalars0: 0,
843
1191
  scalars1: 4,
844
1192
  scalars2: 8,
@@ -849,10 +1197,13 @@ fn fs_main(in: VSOut) -> @location(0) vec4<f32> {
849
1197
  palette: 28,
850
1198
  nuc0: 60,
851
1199
  nuc1: 84,
852
- nuc2: 108
853
- }, K = (e) => 1 - e;
854
- function Xe() {
855
- let e = /* @__PURE__ */ new ArrayBuffer(528);
1200
+ nuc2: 108,
1201
+ scalars4: 132,
1202
+ scalars5: 136,
1203
+ kuwahara: 140
1204
+ }, et = .01, tt = 8, nt = 4, Y = (e) => 1 - e;
1205
+ function rt() {
1206
+ let e = /* @__PURE__ */ new ArrayBuffer(576);
856
1207
  return {
857
1208
  buffer: e,
858
1209
  f32: new Float32Array(e),
@@ -860,56 +1211,56 @@ function Xe() {
860
1211
  paletteScratch: new Float32Array(24)
861
1212
  };
862
1213
  }
863
- function Ze(e, t, n, r) {
1214
+ function it(e, t, n, r) {
864
1215
  let { f32: i, i32: a, paletteScratch: o } = e;
865
- i[G.scalars0 + 0] = r, i[G.scalars0 + 1] = t.softmaxBeta, i[G.scalars0 + 2] = t.valueVariance, i[G.scalars0 + 3] = t.warpAmount, i[G.scalars1 + 0] = t.warpScale, i[G.scalars1 + 1] = t.warpDrift, i[G.scalars1 + 2] = t.saturation, i[G.scalars1 + 3] = t.paperGrain, i[G.scalars2 + 0] = t.alpha, i[G.scalars2 + 1] = t.nucleiDrift, i[G.scalars2 + 2] = t.paletteDrift, i[G.scalars2 + 3] = t.breathDepth, i[G.scalars3 + 0] = t.breathPeriod, i[G.scalars3 + 1] = n.strength, i[G.scalars3 + 2] = n.radius, i[G.scalars3 + 3] = 0, i[G.cursor + 0] = n.x, i[G.cursor + 1] = K(n.y), i[G.cursor + 2] = 0, i[G.cursor + 3] = 0, a[G.ints0 + 0] = Math.min(t.palette.length, 8);
1216
+ i[J.scalars0 + 0] = r, i[J.scalars0 + 1] = t.softmaxBeta, i[J.scalars0 + 2] = t.valueVariance, i[J.scalars0 + 3] = t.warpAmount, i[J.scalars1 + 0] = t.warpScale, i[J.scalars1 + 1] = t.warpDrift, i[J.scalars1 + 2] = t.saturation, i[J.scalars1 + 3] = t.paperGrain, i[J.scalars2 + 0] = t.alpha, i[J.scalars2 + 1] = t.nucleiDrift, i[J.scalars2 + 2] = t.paletteDrift, i[J.scalars2 + 3] = t.breathDepth, i[J.scalars3 + 0] = t.breathPeriod, i[J.scalars3 + 1] = n.strength, i[J.scalars3 + 2] = n.radius, i[J.scalars3 + 3] = t.vividness ?? 1, i[J.cursor + 0] = n.x, i[J.cursor + 1] = Y(n.y), i[J.cursor + 2] = 0, i[J.cursor + 3] = 0, a[J.ints0 + 0] = Math.min(t.palette.length, 8);
866
1217
  let s = Math.min(t.nuclei.length, 6);
867
- a[G.ints0 + 1] = s, a[G.ints0 + 2] = z[t.warpMode], a[G.ints0 + 3] = t.noiseOctaves, a[G.ints1 + 0] = I[t.medium], a[G.ints1 + 1] = L[t.huePath ?? "shorter"], a[G.ints1 + 2] = 0, a[G.ints1 + 3] = 0, M(t.palette, 8, o);
1218
+ a[J.ints0 + 1] = s, a[J.ints0 + 2] = H[t.warpMode], a[J.ints0 + 3] = t.noiseOctaves, a[J.ints1 + 0] = z[t.medium], a[J.ints1 + 1] = B[t.huePath ?? "shorter"], a[J.ints1 + 2] = 0, a[J.ints1 + 3] = 0, N(t.palette, 8, o);
868
1219
  for (let e = 0; e < 8; e++) {
869
- let t = G.palette + e * 4;
1220
+ let t = J.palette + e * 4;
870
1221
  i[t + 0] = o[e * 3 + 0], i[t + 1] = o[e * 3 + 1], i[t + 2] = o[e * 3 + 2], i[t + 3] = 0;
871
1222
  }
872
1223
  for (let e = 0; e < 6; e++) {
873
- let n = G.nuc0 + e * 4, r = G.nuc1 + e * 4, a = G.nuc2 + e * 4;
1224
+ let n = J.nuc0 + e * 4, r = J.nuc1 + e * 4, a = J.nuc2 + e * 4;
874
1225
  if (e < s) {
875
1226
  let o = t.nuclei[e];
876
- i[n + 0] = o.x, i[n + 1] = K(o.y), i[n + 2] = o.radius, i[n + 3] = o.paletteBias, i[r + 0] = o.valueBias, i[r + 1] = o.driftRadius, i[r + 2] = o.driftPhase, i[r + 3] = o.elongation ?? 1, i[a + 0] = -(o.angle ?? 0) * Math.PI / 180, i[a + 1] = 0, i[a + 2] = 0, i[a + 3] = 0;
1227
+ i[n + 0] = o.x, i[n + 1] = Y(o.y), i[n + 2] = o.radius, i[n + 3] = o.paletteBias, i[r + 0] = o.valueBias, i[r + 1] = o.driftRadius, i[r + 2] = o.driftPhase, i[r + 3] = o.elongation ?? 1, i[a + 0] = -(o.angle ?? 0) * Math.PI / 180, i[a + 1] = 0, i[a + 2] = 0, i[a + 3] = 0;
877
1228
  } else i[n + 0] = 0, i[n + 1] = 0, i[n + 2] = 0, i[n + 3] = 0, i[r + 0] = 0, i[r + 1] = 0, i[r + 2] = 0, i[r + 3] = 1, i[a + 0] = 0, i[a + 1] = 0, i[a + 2] = 0, i[a + 3] = 0;
878
1229
  }
879
- return e;
1230
+ return i[J.scalars4 + 0] = t.strokeAmount, i[J.scalars4 + 1] = t.strokeScale, i[J.scalars4 + 2] = t.strokeAnisotropy, i[J.scalars4 + 3] = t.canvasGrain, i[J.scalars5 + 0] = t.wetEdge, i[J.scalars5 + 1] = t.granulation, i[J.scalars5 + 2] = t.brokenColor, i[J.scalars5 + 3] = t.kuwaharaQ ?? nt, i[J.kuwahara + 0] = t.kuwaharaRadius ?? et, i[J.kuwahara + 1] = tt, i[J.kuwahara + 2] = 0, i[J.kuwahara + 3] = 0, e;
880
1231
  }
881
1232
  //#endregion
882
1233
  //#region src/components/custom/aurora/composables/wgpuSetup.ts
883
- var Qe = 64, $e = 8, et = 2;
884
- function tt(e) {
885
- let { canvas: t, cursor: n, getConfig: r, getReducedMotion: i } = e;
886
- return function(e, a, o) {
887
- let s = e.createShaderModule({
1234
+ var at = 64, ot = 8, st = 2;
1235
+ function ct(e) {
1236
+ let { canvas: t, cursor: n, getConfig: r, getReducedMotion: i, pointerField: a } = e, o = null;
1237
+ return function(e, s, c) {
1238
+ let l = e.createShaderModule({
888
1239
  label: "[Aurora] aurora.wgsl",
889
- code: Ye
890
- }), c = e.createBuffer({
1240
+ code: $e
1241
+ }), u = e.createBuffer({
891
1242
  label: "[Aurora] uniforms",
892
- size: 528,
893
- usage: Qe | $e
894
- }), u = e.createBindGroupLayout({
1243
+ size: 576,
1244
+ usage: at | ot
1245
+ }), d = e.createBindGroupLayout({
895
1246
  label: "[Aurora] bind-group-0",
896
1247
  entries: [{
897
1248
  binding: 0,
898
- visibility: et,
1249
+ visibility: st,
899
1250
  buffer: { type: "uniform" }
900
1251
  }]
901
- }), d = e.createRenderPipeline({
1252
+ }), p = e.createRenderPipeline({
902
1253
  label: "[Aurora] pipeline",
903
- layout: e.createPipelineLayout({ bindGroupLayouts: [u] }),
1254
+ layout: e.createPipelineLayout({ bindGroupLayouts: [d] }),
904
1255
  vertex: {
905
- module: s,
1256
+ module: l,
906
1257
  entryPoint: "vs_main"
907
1258
  },
908
1259
  fragment: {
909
- module: s,
1260
+ module: l,
910
1261
  entryPoint: "fs_main",
911
1262
  targets: [{
912
- format: o,
1263
+ format: c,
913
1264
  blend: {
914
1265
  color: {
915
1266
  srcFactor: "one",
@@ -925,22 +1276,25 @@ function tt(e) {
925
1276
  }]
926
1277
  },
927
1278
  primitive: { topology: "triangle-list" }
928
- }), f = e.createBindGroup({
1279
+ }), m = e.createBindGroup({
929
1280
  label: "[Aurora] bind-group",
930
- layout: u,
1281
+ layout: d,
931
1282
  entries: [{
932
1283
  binding: 0,
933
- resource: { buffer: c }
1284
+ resource: { buffer: u }
934
1285
  }]
935
- }), p = Xe();
936
- function m() {
937
- let e = l(), n = t.getBoundingClientRect(), r = t.parentElement?.getBoundingClientRect(), i = n.width || r?.width || 1, a = n.height || r?.height || 1, o = Math.max(1, Math.round(i * e)), s = Math.max(1, Math.round(a * e));
1286
+ }), h = rt();
1287
+ function g() {
1288
+ let e = f(), n = t.getBoundingClientRect(), r = t.parentElement?.getBoundingClientRect(), i = n.width || r?.width || 1, a = n.height || r?.height || 1, o = Math.max(1, Math.round(i * e)), s = Math.max(1, Math.round(a * e));
938
1289
  (t.width !== o || t.height !== s) && (t.width = o, t.height = s);
939
1290
  }
940
- function h(t) {
941
- U(n, +!i()), Ze(p, r(), n, t), e.queue.writeBuffer(c, 0, p.buffer);
942
- let o = a.getCurrentTexture().createView(), s = e.createCommandEncoder({ label: "[Aurora] frame" }), l = s.beginRenderPass({ colorAttachments: [{
943
- view: o,
1291
+ function _(t) {
1292
+ let c = +!i(), l = o === null ? 0 : Math.max(0, (t - o) * 1e3);
1293
+ o = t, a.tick(c === 0 ? 0 : l);
1294
+ let d = a.acceleration.value, f = a.velocity.value, g = -(d.x * f.x + d.y * f.y);
1295
+ g > 0 && c > 0 && (n.burst = Math.min(1, n.burst + g * 6)), K(n, c), it(h, r(), n, t), e.queue.writeBuffer(u, 0, h.buffer);
1296
+ let _ = s.getCurrentTexture().createView(), v = e.createCommandEncoder({ label: "[Aurora] frame" }), y = v.beginRenderPass({ colorAttachments: [{
1297
+ view: _,
944
1298
  clearValue: {
945
1299
  r: 0,
946
1300
  g: 0,
@@ -950,34 +1304,34 @@ function tt(e) {
950
1304
  loadOp: "clear",
951
1305
  storeOp: "store"
952
1306
  }] });
953
- l.setPipeline(d), l.setBindGroup(0, f), l.draw(3, 1, 0, 0), l.end(), e.queue.submit([s.finish()]);
1307
+ y.setPipeline(p), y.setBindGroup(0, m), y.draw(3, 1, 0, 0), y.end(), e.queue.submit([v.finish()]);
954
1308
  }
955
- function g() {
1309
+ function v() {
956
1310
  if (i()) return !1;
957
1311
  let e = r();
958
- return e.nucleiDrift !== 0 || e.paletteDrift !== 0 || e.breathDepth !== 0 || e.warpDrift !== 0 || e.interactivity?.light && n.burst > .001 ? !0 : W(n);
1312
+ return e.nucleiDrift !== 0 || e.paletteDrift !== 0 || e.breathDepth !== 0 || e.warpDrift !== 0 || e.interactivity?.light && n.burst > .001 ? !0 : q(n);
959
1313
  }
960
1314
  return {
961
- frame: h,
962
- shouldContinue: g,
963
- resize: m,
1315
+ frame: _,
1316
+ shouldContinue: v,
1317
+ resize: g,
964
1318
  time: (e) => i() ? 3.7 : e,
965
1319
  teardown: () => {
966
- c.destroy();
1320
+ u.destroy();
967
1321
  }
968
1322
  };
969
1323
  };
970
1324
  }
971
1325
  //#endregion
972
1326
  //#region src/components/custom/aurora/composables/runtime.ts
973
- function nt(e) {
1327
+ function lt(e) {
974
1328
  return e.mode === "capture" ? !0 : e.initStrategy === "eager";
975
1329
  }
976
- function rt(e) {
1330
+ function ut(e) {
977
1331
  return typeof e.preserveDrawingBuffer == "boolean" ? e.preserveDrawingBuffer : e.mode === "capture";
978
1332
  }
979
- function q(e, t, n = {}) {
980
- let r = rt(n), i = !n.forceWebGLUnderSoftwareRaster && A(), a = t, s = Ke(), c = null, u = i ? {
1333
+ function X(e, n, r = {}) {
1334
+ let i = ut(r), a = !r.forceWebGLUnderSoftwareRaster && A(), o = n, s = Xe(), c = t(), l = null, u = a ? {
981
1335
  arm: () => {},
982
1336
  suspend: () => {},
983
1337
  resume: () => {},
@@ -985,42 +1339,44 @@ function q(e, t, n = {}) {
985
1339
  renderAt: () => {},
986
1340
  dispose: () => {},
987
1341
  reducedMotion: !1
988
- } : o(e, {
989
- mode: n.mode === "capture" ? "capture" : "live",
1342
+ } : h(e, {
1343
+ mode: r.mode === "capture" ? "capture" : "live",
990
1344
  contextAttrs: {
991
1345
  antialias: !1,
992
1346
  alpha: !0,
993
1347
  premultipliedAlpha: !0,
994
- preserveDrawingBuffer: r
1348
+ preserveDrawingBuffer: i
995
1349
  },
996
- setupWGPU: tt({
1350
+ setupWGPU: ct({
997
1351
  canvas: e,
998
1352
  cursor: s,
999
- getConfig: () => a,
1000
- getReducedMotion: () => u.reducedMotion
1353
+ getConfig: () => o,
1354
+ getReducedMotion: () => u.reducedMotion,
1355
+ pointerField: c
1001
1356
  }),
1002
1357
  setupGL: (t) => {
1003
- let { program: n, vs: r, fs: i, uniforms: o, geometry: d } = Oe(t, he, Te);
1004
- function f() {
1005
- let r = l(), i = e.getBoundingClientRect(), a = e.parentElement?.getBoundingClientRect(), o = i.width || a?.width || 1, s = i.height || a?.height || 1, c = Math.max(1, Math.round(o * r)), u = Math.max(1, Math.round(s * r));
1006
- (e.width !== c || e.height !== u) && (e.width = c, e.height = u), t.viewport(0, 0, c, u), t.useProgram(n);
1358
+ let { program: n, vs: r, fs: i, uniforms: a, geometry: d } = Ae(t, ye, Oe);
1359
+ function p() {
1360
+ let r = f(), i = e.getBoundingClientRect(), a = e.parentElement?.getBoundingClientRect(), o = i.width || a?.width || 1, s = i.height || a?.height || 1, c = Math.max(1, Math.round(o * r)), l = Math.max(1, Math.round(s * r));
1361
+ (e.width !== c || e.height !== l) && (e.width = c, e.height = l), t.viewport(0, 0, c, l), t.useProgram(n);
1007
1362
  }
1008
- let p = He(t, n, o, s), m = Je({
1363
+ let m = Ke(t, n, a, s), h = Qe({
1009
1364
  gl: t,
1010
1365
  prog: n,
1011
- uniforms: o,
1366
+ uniforms: a,
1012
1367
  cursor: s,
1013
- getConfig: () => a,
1014
- getReducedMotion: () => u.reducedMotion
1368
+ getConfig: () => o,
1369
+ getReducedMotion: () => u.reducedMotion,
1370
+ pointerField: c
1015
1371
  });
1016
- 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) => {
1017
- a = e, p(e);
1018
- }, c(a), requestAnimationFrame(() => {
1019
- f(), requestAnimationFrame(f);
1372
+ 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), l = (e) => {
1373
+ o = e, m(e);
1374
+ }, l(o), requestAnimationFrame(() => {
1375
+ p(), requestAnimationFrame(p);
1020
1376
  }), {
1021
- frame: m.frame,
1022
- shouldContinue: m.needsAnimation,
1023
- resize: f,
1377
+ frame: h.frame,
1378
+ shouldContinue: h.needsAnimation,
1379
+ resize: p,
1024
1380
  time: (e) => u.reducedMotion ? 3.7 : e,
1025
1381
  teardown: () => {
1026
1382
  t.deleteProgram(n), t.deleteShader(r), t.deleteShader(i), t.deleteBuffer(d.buf), t.deleteVertexArray(d.vao);
@@ -1029,35 +1385,39 @@ function q(e, t, n = {}) {
1029
1385
  }
1030
1386
  });
1031
1387
  function d(e, t, n = .8) {
1032
- s.targetX = e, s.targetY = t, s.targetStrength = n, u.wake();
1388
+ s.targetX = e, s.targetY = t, s.targetStrength = n, c.setPointer(e, t), u.wake();
1033
1389
  }
1034
- function f(e, t) {
1035
- u.reducedMotion || (qe(s, e, t), u.wake());
1390
+ function p(e, t) {
1391
+ u.reducedMotion || (Ze(s, e, t), u.wake());
1036
1392
  }
1037
- function p() {
1393
+ function m() {
1038
1394
  s.targetStrength = 0, u.wake();
1039
1395
  }
1040
- function m(e) {
1396
+ function g(e) {
1041
1397
  s.radius = e, u.wake();
1042
1398
  }
1043
- function h(e) {
1399
+ function _(e) {
1044
1400
  u.wake();
1045
1401
  }
1046
- function g() {
1402
+ function v() {
1047
1403
  u.armAsync ? u.armAsync().catch((e) => {
1048
- n.onInitError?.(e instanceof Error ? e : Error(String(e)));
1404
+ r.onInitError?.(e instanceof Error ? e : Error(String(e)));
1049
1405
  }) : u.arm();
1050
1406
  }
1051
- return n.mode !== "capture" && nt(n) && g(), {
1052
- arm: () => g(),
1407
+ function y() {
1408
+ return u.armAsync ? u.armAsync() : (u.arm(), Promise.resolve());
1409
+ }
1410
+ return r.mode !== "capture" && lt(r) && v(), {
1411
+ arm: () => v(),
1412
+ armAsync: () => y(),
1053
1413
  update: (e) => {
1054
- a = e, c?.(e), u.wake();
1414
+ o = e, l?.(e), u.wake();
1055
1415
  },
1056
1416
  setCursor: d,
1057
- clearCursor: p,
1058
- setCursorRadius: m,
1059
- setReducedMotion: h,
1060
- injectCursorVelocity: f,
1417
+ clearCursor: m,
1418
+ setCursorRadius: g,
1419
+ setReducedMotion: _,
1420
+ injectCursorVelocity: p,
1061
1421
  get reducedMotion() {
1062
1422
  return u.reducedMotion;
1063
1423
  },
@@ -1069,12 +1429,12 @@ function q(e, t, n = {}) {
1069
1429
  }
1070
1430
  //#endregion
1071
1431
  //#region src/components/custom/aurora/composables/configSource.ts
1072
- function J(e) {
1432
+ function dt(e) {
1073
1433
  return typeof e == "function" ? e : e && typeof e == "object" && "value" in e ? () => e.value : () => e;
1074
1434
  }
1075
1435
  //#endregion
1076
1436
  //#region src/components/custom/aurora/composables/useAurora.ts
1077
- function it(e) {
1437
+ function ft(e) {
1078
1438
  if (typeof window > "u") return () => {};
1079
1439
  if (typeof window.requestIdleCallback == "function") {
1080
1440
  let t = window.requestIdleCallback(() => e(), { timeout: 2e3 });
@@ -1091,8 +1451,8 @@ function it(e) {
1091
1451
  r = !0, cancelAnimationFrame(t), n !== void 0 && clearTimeout(n);
1092
1452
  };
1093
1453
  }
1094
- function Y(e, t, n = {}, r = {}) {
1095
- let i = J(t), a = r.renderMode === "css", o = null, s = null, c = null, l = null, u = null, d = null, f = null, p = !1, m = E(!1), h = n.mode === "capture" || n.initStrategy === "eager";
1454
+ function pt(e, t, n = {}, r = {}) {
1455
+ let i = dt(t), a = r.renderMode === "css", o = null, s = null, c = null, l = null, u = null, d = null, f = null, p = !1, m = E(!1), h = n.mode === "capture" || n.initStrategy === "eager";
1096
1456
  function g() {
1097
1457
  f && o && o.setReducedMotion(f.matches);
1098
1458
  }
@@ -1113,7 +1473,7 @@ function Y(e, t, n = {}, r = {}) {
1113
1473
  _(e);
1114
1474
  return;
1115
1475
  }
1116
- m.value = !0, f = window.matchMedia("(prefers-reduced-motion: reduce)"), f.addEventListener("change", g), s = O(i, (e) => o?.update(e), { deep: !0 }), i().interactivity?.scroll && e.value && (l = O(ie({
1476
+ m.value = !0, f = window.matchMedia("(prefers-reduced-motion: reduce)"), f.addEventListener("change", g), s = O(i, (e) => o?.update(e), { deep: !0 }), i().interactivity?.scroll && e.value && (l = O(oe({
1117
1477
  target: e,
1118
1478
  trackExit: !0
1119
1479
  }), (e) => {
@@ -1121,11 +1481,11 @@ function Y(e, t, n = {}, r = {}) {
1121
1481
  }));
1122
1482
  }
1123
1483
  }
1124
- return T(() => {
1484
+ return pe(() => {
1125
1485
  let t = e.value;
1126
1486
  if (!t || a) return;
1127
1487
  try {
1128
- o = q(t, i(), {
1488
+ o = X(t, i(), {
1129
1489
  initStrategy: "deferred",
1130
1490
  ...n
1131
1491
  });
@@ -1137,17 +1497,17 @@ function Y(e, t, n = {}, r = {}) {
1137
1497
  v();
1138
1498
  return;
1139
1499
  }
1140
- d = re(e, {
1500
+ d = ae(e, {
1141
1501
  pause: () => o?.pause("off-screen"),
1142
1502
  resume: () => o?.resume("off-screen")
1143
1503
  }, { pauseWhenHidden: !1 });
1144
1504
  let r = d.isIntersecting;
1145
1505
  c = O(r, (e) => {
1146
- !e || p || u || (u = it(() => {
1506
+ !e || p || u || (u = ft(() => {
1147
1507
  u = null, r.value && v();
1148
1508
  }));
1149
1509
  }, { immediate: !0 });
1150
- }), w(() => {
1510
+ }), fe(() => {
1151
1511
  u?.(), c?.(), s?.(), l?.(), d?.dispose(), f?.removeEventListener("change", g), o?.dispose(), o = null;
1152
1512
  }), {
1153
1513
  setCursor: (e, t, n) => o?.setCursor(e, t, n),
@@ -1157,16 +1517,16 @@ function Y(e, t, n = {}, r = {}) {
1157
1517
  renderAt: (e) => o?.renderAt(e),
1158
1518
  pause: () => o?.pause(),
1159
1519
  resume: () => o?.resume(),
1160
- isArmed: de(m)
1520
+ isArmed: he(m)
1161
1521
  };
1162
1522
  }
1163
1523
  //#endregion
1164
1524
  //#region src/components/custom/aurora/composables/auroraFallbackGround.ts
1165
- function X(e) {
1525
+ function Z(e) {
1166
1526
  let t = Math.max(0, Math.min(1, e));
1167
1527
  return t <= .0031308 ? t * 12.92 : 1.055 * t ** (1 / 2.4) - .055;
1168
1528
  }
1169
- function at([e, t, n]) {
1529
+ function mt([e, t, n]) {
1170
1530
  let r = .76, i = Math.min(e, Math.min(t, n)), a = i < .08 ? i - 6.25 * i * i : .04, o = e - a, s = t - a, c = n - a, l = Math.max(o, Math.max(s, c)), u = (e) => Math.max(0, Math.min(1, e));
1171
1531
  if (l < r) return [
1172
1532
  u(o),
@@ -1182,10 +1542,10 @@ function at([e, t, n]) {
1182
1542
  u(c + (f - c) * m)
1183
1543
  ];
1184
1544
  }
1185
- function ot([e, t, n]) {
1545
+ function ht([e, t, n]) {
1186
1546
  return .2126 * e + .7152 * t + .0722 * n;
1187
1547
  }
1188
- function st(e, t) {
1548
+ function gt(e, t) {
1189
1549
  let n = e.length;
1190
1550
  if (n === 0) return [
1191
1551
  0,
@@ -1200,7 +1560,7 @@ function st(e, t) {
1200
1560
  s[2] + (c[2] - s[2]) * o
1201
1561
  ];
1202
1562
  }
1203
- function ct(e, t, n, r) {
1563
+ function _t(e, t, n, r) {
1204
1564
  let i = 0, a = 0, o = 0;
1205
1565
  for (let s of e) {
1206
1566
  let e = n - s.x, c = r - s.y, l = (s.angle ?? 0) * Math.PI / 180, u = Math.cos(l), d = Math.sin(l), f = u * e + d * c, p = -d * e + u * c, m = f / Math.max(s.elongation ?? 1, .01), h = p, g = m * m + h * h, _ = Math.max(s.radius, .01), v = Math.exp(-t * g / (_ * _));
@@ -1212,33 +1572,33 @@ function ct(e, t, n, r) {
1212
1572
  valueMod: a / s
1213
1573
  };
1214
1574
  }
1215
- function lt(e, t, n) {
1216
- let r = e.palette.map((e) => b(e)), { paletteId: i, valueMod: a } = ct(e.nuclei, e.softmaxBeta, t, n), o = st(r, i), s = 1 + e.valueVariance * a, c = [
1575
+ function vt(e, t, n) {
1576
+ let r = e.palette.map((e) => x(e)), { paletteId: i, valueMod: a } = _t(e.nuclei, e.softmaxBeta, t, n), o = gt(r, i), s = 1 + e.valueVariance * a, c = [
1217
1577
  o[0] * s,
1218
1578
  o[1] * s,
1219
1579
  o[2] * s
1220
1580
  ];
1221
- return c = at(c), c = [
1581
+ return c = mt(c), c = [
1222
1582
  Math.max(0, Math.min(1, c[0] * .985 + .008)),
1223
1583
  Math.max(0, Math.min(1, c[1] * .985 + .008)),
1224
1584
  Math.max(0, Math.min(1, c[2] * .985 + .008))
1225
1585
  ], [
1226
- X(c[0]),
1227
- X(c[1]),
1228
- X(c[2])
1586
+ Z(c[0]),
1587
+ Z(c[1]),
1588
+ Z(c[2])
1229
1589
  ];
1230
1590
  }
1231
- function ut([e, t, n]) {
1591
+ function yt([e, t, n]) {
1232
1592
  let r = (e) => Math.round(Math.max(0, Math.min(1, e)) * 255);
1233
1593
  return `rgb(${r(e)}, ${r(t)}, ${r(n)})`;
1234
1594
  }
1235
- function dt(e) {
1236
- return ot(e.map((e) => {
1595
+ function bt(e) {
1596
+ return ht(e.map((e) => {
1237
1597
  let t = Math.max(0, Math.min(1, e));
1238
1598
  return t <= .04045 ? t / 12.92 : ((t + .055) / 1.055) ** 2.4;
1239
1599
  }));
1240
1600
  }
1241
- function ft(e, t = {}) {
1601
+ function xt(e, t = {}) {
1242
1602
  let n = Math.max(2, Math.min(16, Math.round(t.grid ?? 8))), r = [], i = 0, a = [
1243
1603
  0,
1244
1604
  0,
@@ -1251,13 +1611,13 @@ function ft(e, t = {}) {
1251
1611
  0
1252
1612
  ];
1253
1613
  for (let t = 0; t < n; t++) for (let s = 0; s < n; s++) {
1254
- let c = (s + .5) / n, l = (t + .5) / n, u = lt(e, c, l);
1614
+ let c = (s + .5) / n, l = (t + .5) / n, u = vt(e, c, l);
1255
1615
  r.push({
1256
1616
  x: c,
1257
1617
  y: l,
1258
1618
  color: u
1259
1619
  });
1260
- let d = dt(u);
1620
+ let d = bt(u);
1261
1621
  i += d;
1262
1622
  let f = (l < .5 ? 0 : 2) + (c < .5 ? 0 : 1);
1263
1623
  a[f] += d, o[f] += 1;
@@ -1274,10 +1634,10 @@ function ft(e, t = {}) {
1274
1634
  ];
1275
1635
  for (let e of r) l[0] += e.color[0], l[1] += e.color[1], l[2] += e.color[2];
1276
1636
  l[0] /= r.length, l[1] /= r.length, l[2] /= r.length;
1277
- let u = pt(r, n);
1637
+ let u = St(r, n);
1278
1638
  if (u) return {
1279
1639
  backgroundImage: `url("${u}")`,
1280
- backgroundColor: ut(l),
1640
+ backgroundColor: yt(l),
1281
1641
  metrics: {
1282
1642
  mean: s,
1283
1643
  quadrants: c
@@ -1289,14 +1649,14 @@ function ft(e, t = {}) {
1289
1649
  let t = (e.x * 100).toFixed(2), n = (e.y * 100).toFixed(2), r = (e) => Math.round(Math.max(0, Math.min(1, e)) * 255), [i, a, o] = e.color, s = `rgba(${r(i)}, ${r(a)}, ${r(o)}, 0.7)`, c = `rgba(${r(i)}, ${r(a)}, ${r(o)}, 0)`;
1290
1650
  return `radial-gradient(circle ${d.toFixed(2)}% at ${t}% ${n}%, ${s} 0%, ${c} 100%)`;
1291
1651
  }).join(", "),
1292
- backgroundColor: ut(l),
1652
+ backgroundColor: yt(l),
1293
1653
  metrics: {
1294
1654
  mean: s,
1295
1655
  quadrants: c
1296
1656
  }
1297
1657
  };
1298
1658
  }
1299
- function pt(e, t) {
1659
+ function St(e, t) {
1300
1660
  if (typeof document > "u") return null;
1301
1661
  let n;
1302
1662
  try {
@@ -1321,20 +1681,20 @@ function pt(e, t) {
1321
1681
  }
1322
1682
  //#endregion
1323
1683
  //#region src/components/custom/aurora/Aurora.vue?vue&type=script&setup=true&lang.ts
1324
- var mt = ["data-aurora-substrate"], ht = /* @__PURE__ */ e(/* @__PURE__ */ se({
1684
+ var Ct = ["data-aurora-substrate"], wt = /* @__PURE__ */ e(/* @__PURE__ */ le({
1325
1685
  __name: "Aurora",
1326
1686
  props: {
1327
- config: { default: () => y },
1687
+ config: { default: () => b },
1328
1688
  runtimeOptions: {},
1329
1689
  onInitError: {},
1330
1690
  renderMode: { default: "auto" },
1331
1691
  opacityCeiling: { default: 1 }
1332
1692
  },
1333
1693
  setup(e, { expose: t }) {
1334
- let n = e, r = Ee(n.renderMode, { forceWebGLUnderSoftwareRaster: n.runtimeOptions?.forceWebGLUnderSoftwareRaster ?? !1 }), i = C(() => Math.max(0, Math.min(1, n.opacityCeiling))), a = E(null), o = Y(a, () => n.config, C(() => ({
1694
+ let n = e, r = j(n.renderMode, { forceWebGLUnderSoftwareRaster: n.runtimeOptions?.forceWebGLUnderSoftwareRaster ?? !1 }), i = T(() => Math.max(0, Math.min(1, n.opacityCeiling))), a = E(null), o = pt(a, () => n.config, T(() => ({
1335
1695
  ...n.runtimeOptions ?? {},
1336
1696
  ...n.onInitError ? { onInitError: n.onInitError } : {}
1337
- })).value, { renderMode: r }), s = C(() => r === "css"), c = C(() => ft(n.config)), l = C(() => s.value ? c.value.backgroundImage : N(n.config.palette)), u = C(() => s.value ? c.value.backgroundColor : "transparent");
1697
+ })).value, { renderMode: r }), s = T(() => r === "css"), c = T(() => xt(n.config)), l = T(() => s.value ? c.value.backgroundImage : P(n.config.palette)), u = T(() => s.value ? c.value.backgroundColor : "transparent");
1338
1698
  return t({
1339
1699
  config: n.config,
1340
1700
  canvasRef: a,
@@ -1346,29 +1706,29 @@ var mt = ["data-aurora-substrate"], ht = /* @__PURE__ */ e(/* @__PURE__ */ se({
1346
1706
  pause: o.pause,
1347
1707
  resume: o.resume,
1348
1708
  isArmed: o.isArmed
1349
- }), (e, t) => (ue(), ae("div", {
1709
+ }), (e, t) => (me(), se("div", {
1350
1710
  class: "aurora-root block h-full w-full overflow-hidden",
1351
1711
  "data-aurora-substrate": D(r),
1352
- style: le({ "--aurora-opacity-ceiling": i.value })
1353
- }, [oe("div", {
1712
+ style: de({ "--aurora-opacity-ceiling": i.value })
1713
+ }, [ce("div", {
1354
1714
  class: "aurora-placeholder h-full w-full",
1355
1715
  "aria-hidden": "true",
1356
- style: le({
1716
+ style: de({
1357
1717
  backgroundImage: l.value,
1358
1718
  backgroundColor: u.value
1359
1719
  })
1360
- }, null, 4), oe("canvas", {
1720
+ }, null, 4), ce("canvas", {
1361
1721
  ref_key: "canvasRef",
1362
1722
  ref: a,
1363
1723
  "aria-hidden": "true",
1364
- class: ce(["aurora-canvas block h-full w-full", { "aurora-canvas--armed": D(o).isArmed.value }])
1365
- }, null, 2)], 12, mt));
1724
+ class: ue(["aurora-canvas block h-full w-full", { "aurora-canvas--armed": D(o).isArmed.value }])
1725
+ }, null, 2)], 12, Ct));
1366
1726
  }
1367
- }), [["__scopeId", "data-v-c05f182a"]]);
1727
+ }), [["__scopeId", "data-v-3eae1d97"]]);
1368
1728
  //#endregion
1369
1729
  //#region src/components/custom/aurora/composables/useCursorInteraction.ts
1370
- function gt(e, t, n) {
1371
- let r = J(t), i = n.hitFraction ?? .35, a = null, o = null, s = null, c = null;
1730
+ function Tt(e, t, n) {
1731
+ let r = dt(t), i = n.hitFraction ?? .35, a = null, o = null, s = null, c = null;
1372
1732
  function l(e, t) {
1373
1733
  let n = t.getBoundingClientRect();
1374
1734
  return {
@@ -1455,20 +1815,20 @@ function gt(e, t, n) {
1455
1815
  }, a = n.getBoundingClientRect(), o = d((i.clientX - a.left) / Math.max(a.width, 1), (i.clientY - a.top) / Math.max(a.height, 1));
1456
1816
  o !== null && r().nuclei.length > 1 && (r().nuclei.splice(o, 1), t.preventDefault());
1457
1817
  }
1458
- return T(() => {
1818
+ return pe(() => {
1459
1819
  let t = e.value;
1460
1820
  t && (t.addEventListener("pointermove", h), t.addEventListener("pointerleave", g), t.addEventListener("pointerdown", _), t.addEventListener("pointerup", v), t.addEventListener("pointercancel", v), t.addEventListener("contextmenu", y));
1461
- }), w(() => {
1821
+ }), fe(() => {
1462
1822
  let t = e.value;
1463
1823
  t && (t.removeEventListener("pointermove", h), t.removeEventListener("pointerleave", g), t.removeEventListener("pointerdown", _), t.removeEventListener("pointerup", v), t.removeEventListener("pointercancel", v), t.removeEventListener("contextmenu", y));
1464
1824
  }), { isDragging: () => a };
1465
1825
  }
1466
1826
  //#endregion
1467
1827
  //#region src/components/custom/aurora/composables/atoms-fields.ts
1468
- function Z(e, t, n) {
1828
+ function Q(e, t, n) {
1469
1829
  return e + (t - e) * n;
1470
1830
  }
1471
- var Q = {
1831
+ var $ = {
1472
1832
  saturation: {
1473
1833
  calm: .85,
1474
1834
  vivid: 1.2
@@ -1485,7 +1845,7 @@ var Q = {
1485
1845
  calm: .15,
1486
1846
  vivid: .5
1487
1847
  }
1488
- }, _t = {
1848
+ }, Et = {
1489
1849
  still: {
1490
1850
  nucleiDrift: 0,
1491
1851
  paletteDrift: 0,
@@ -1505,8 +1865,8 @@ var Q = {
1505
1865
  breathDepth: .05
1506
1866
  }
1507
1867
  };
1508
- function vt(e, t, n) {
1509
- let r = s(n, 0, 1);
1868
+ function Dt(e, t, n) {
1869
+ let r = m(n, 0, 1);
1510
1870
  switch (t) {
1511
1871
  case "watercolor":
1512
1872
  e.wetEdge = r, e.granulation = r * .8;
@@ -1525,27 +1885,27 @@ function vt(e, t, n) {
1525
1885
  break;
1526
1886
  }
1527
1887
  }
1528
- function yt(e, t) {
1888
+ function Ot(e, t) {
1529
1889
  switch (t) {
1530
- case "watercolor": return s(e.wetEdge, 0, 1);
1890
+ case "watercolor": return m(e.wetEdge, 0, 1);
1531
1891
  case "oil":
1532
1892
  case "vangogh":
1533
1893
  case "pastel":
1534
1894
  case "oil-pastel":
1535
1895
  case "crayon":
1536
- case "kuwahara": return s(e.strokeAmount, 0, 1);
1896
+ case "kuwahara": return m(e.strokeAmount, 0, 1);
1537
1897
  }
1538
1898
  }
1539
- function bt(e) {
1899
+ function kt(e) {
1540
1900
  return e.nucleiDrift + e.paletteDrift + e.warpDrift > .001 ? "drifting" : e.breathDepth > .001 ? "breathing" : "still";
1541
1901
  }
1542
- function xt(e, t, n) {
1543
- return n === t ? 0 : s((e - t) / (n - t), 0, 1);
1902
+ function At(e, t, n) {
1903
+ return n === t ? 0 : m((e - t) / (n - t), 0, 1);
1544
1904
  }
1545
1905
  //#endregion
1546
1906
  //#region src/components/custom/aurora/composables/atoms.ts
1547
- var $ = {}, St = .61803398875;
1548
- function Ct(e, t = "composed") {
1907
+ var jt = {}, Mt = .61803398875;
1908
+ function Nt(e, t = "composed") {
1549
1909
  let n = Math.max(1, Math.min(6, Math.round(e))), r = [
1550
1910
  [.33, .33],
1551
1911
  [.67, .67],
@@ -1556,8 +1916,8 @@ function Ct(e, t = "composed") {
1556
1916
  ], i = (e) => {
1557
1917
  switch (t) {
1558
1918
  case "scattered": {
1559
- let t = e * St * Math.PI * 2, r = .12 + .3 * Math.sqrt((e + .5) / n);
1560
- return [s(.5 + r * Math.cos(t), .1, .9), s(.5 + r * Math.sin(t), .1, .9)];
1919
+ let t = e * Mt * Math.PI * 2, r = .12 + .3 * Math.sqrt((e + .5) / n);
1920
+ return [m(.5 + r * Math.cos(t), .1, .9), m(.5 + r * Math.sin(t), .1, .9)];
1561
1921
  }
1562
1922
  case "centred": {
1563
1923
  if (n === 1) return [.5, .5];
@@ -1580,47 +1940,47 @@ function Ct(e, t = "composed") {
1580
1940
  };
1581
1941
  });
1582
1942
  }
1583
- function wt(e) {
1943
+ function Pt(e) {
1584
1944
  return e < .4 ? "fbm" : e < .75 ? "hybrid" : "cellular";
1585
1945
  }
1586
- function Tt(e, t) {
1587
- let n = s(t, 0, 1);
1588
- e.warpAmount = s(Z(.2, .6, n), 0, .6), e.warpScale = s(Z(1, 2.6, n), .5, 3), e.warpMode = wt(n), e.noiseOctaves = n < .5 ? 3 : n < .85 ? 4 : 5;
1946
+ function Ft(e, t) {
1947
+ let n = m(t, 0, 1);
1948
+ e.warpAmount = m(Q(.2, .6, n), 0, .6), e.warpScale = m(Q(1, 2.6, n), .5, 3), e.warpMode = Pt(n), e.noiseOctaves = n < .5 ? 3 : n < .85 ? 4 : 5;
1589
1949
  }
1590
- function Et(e = $, t = y) {
1950
+ function It(e = jt, t = b) {
1591
1951
  let n = {
1592
1952
  ...t,
1593
1953
  palette: t.palette.map((e) => ({ ...e })),
1594
1954
  nuclei: t.nuclei.map((e) => ({ ...e })),
1595
1955
  flow: { ...t.flow }
1596
- }, r = e.colorEnergy === void 0 ? void 0 : s(e.colorEnergy, 0, 1);
1597
- if (e.seed !== void 0 && (n.palette = P(e.seed, {
1956
+ }, r = e.colorEnergy === void 0 ? void 0 : m(e.colorEnergy, 0, 1);
1957
+ if (e.seed !== void 0 && (n.palette = F(e.seed, {
1598
1958
  harmony: e.harmony ?? "analogous",
1599
1959
  stopCount: 4,
1600
- temperatureShift: r === void 0 ? 0 : Z(Q.temperatureShift.calm, Q.temperatureShift.vivid, r)
1601
- })), r !== void 0 && (n.saturation = s(Z(Q.saturation.calm, Q.saturation.vivid, r), .6, 1.3), n.valueVariance = s(Z(Q.valueVariance.calm, Q.valueVariance.vivid, r), 0, .3), n.breathDepth = s(Z(Q.breathDepth.calm, Q.breathDepth.vivid, r), 0, .15)), e.zones !== void 0 && (n.nuclei = Ct(e.zones.count, e.zones.arrangement ?? "composed")), e.noise !== void 0 && Tt(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 && vt(n, e.medium.kind, e.medium.amount)), e.motion !== void 0) {
1602
- let t = _t[e.motion];
1603
- n.nucleiDrift = s(t.nucleiDrift, 0, .05), n.paletteDrift = s(t.paletteDrift, 0, .04), n.warpDrift = s(t.warpDrift, 0, .015), n.breathDepth = s(e.motion === "still" ? 0 : Math.max(n.breathDepth, t.breathDepth), 0, .15);
1960
+ temperatureShift: r === void 0 ? 0 : Q($.temperatureShift.calm, $.temperatureShift.vivid, r)
1961
+ })), r !== void 0 && (n.saturation = m(Q($.saturation.calm, $.saturation.vivid, r), .6, 1.3), n.valueVariance = m(Q($.valueVariance.calm, $.valueVariance.vivid, r), 0, .3), n.breathDepth = m(Q($.breathDepth.calm, $.breathDepth.vivid, r), 0, .15)), e.zones !== void 0 && (n.nuclei = Nt(e.zones.count, e.zones.arrangement ?? "composed")), e.noise !== void 0 && Ft(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 && Dt(n, e.medium.kind, e.medium.amount)), e.motion !== void 0) {
1962
+ let t = Et[e.motion];
1963
+ n.nucleiDrift = m(t.nucleiDrift, 0, .05), n.paletteDrift = m(t.paletteDrift, 0, .04), n.warpDrift = m(t.warpDrift, 0, .015), n.breathDepth = m(e.motion === "still" ? 0 : Math.max(n.breathDepth, t.breathDepth), 0, .15);
1604
1964
  }
1605
1965
  return e.interactivity !== void 0 && (n.interactivity = { ...e.interactivity }), n;
1606
1966
  }
1607
- function Dt(e) {
1967
+ function Lt(e) {
1608
1968
  let t = e.medium, n = t === "smooth" ? { kind: t } : {
1609
1969
  kind: t,
1610
- amount: yt(e, t)
1970
+ amount: Ot(e, t)
1611
1971
  };
1612
1972
  return {
1613
- seed: e.palette.length > 0 ? x(e.palette[0]) : "#3a7bd5",
1973
+ seed: e.palette.length > 0 ? C(e.palette[0]) : "#3a7bd5",
1614
1974
  harmony: "analogous",
1615
- colorEnergy: xt(e.saturation, Q.saturation.calm, Q.saturation.vivid),
1975
+ colorEnergy: At(e.saturation, $.saturation.calm, $.saturation.vivid),
1616
1976
  zones: {
1617
1977
  count: Math.max(1, Math.min(6, e.nuclei.length)),
1618
1978
  arrangement: "composed"
1619
1979
  },
1620
- noise: xt(e.warpAmount, .2, .6),
1980
+ noise: At(e.warpAmount, .2, .6),
1621
1981
  medium: n,
1622
- motion: bt(e)
1982
+ motion: kt(e)
1623
1983
  };
1624
1984
  }
1625
1985
  //#endregion
1626
- export { ht as Aurora, $ as DEFAULT_ATOMS, y as DEFAULT_AURORA_CONFIG, _ as MAX_NUCLEI, v as MAX_STOPS, g as PAPER_WASH_GROUND, ft as auroraFallbackGround, Dt as configToAtoms, q as createAurora, S as cssToOklch, P as deriveAurora, M as flattenPalette, ke as hexToOklchStop, A as isSoftwareWebGLRenderer, ct as nucleiFieldStatic, Ct as nucleiPrior, x as oklchStopToHex, b as oklchToLinear, N as paletteToCssGradient, ot as relativeLuminance, Et as resolveAtoms, Ee as resolveRenderMode, lt as sampleAuroraField, Y as useAurora, gt as useCursorInteraction };
1986
+ export { wt as Aurora, jt as DEFAULT_ATOMS, b as DEFAULT_AURORA_CONFIG, ee as DEFAULT_VIVIDNESS, ne as MAX_NUCLEI, y as MAX_STOPS, v as PAPER_WASH_GROUND, te as VIVID_TARGET, xt as auroraFallbackGround, Lt as configToAtoms, X as createAurora, w as cssToOklch, F as deriveAurora, N as flattenPalette, je as hexToOklchStop, A as isSoftwareWebGLRenderer, _t as nucleiFieldStatic, Nt as nucleiPrior, C as oklchStopToHex, x as oklchToLinear, P as paletteToCssGradient, ht as relativeLuminance, It as resolveAtoms, j as resolveRenderMode, vt as sampleAuroraField, pt as useAurora, Tt as useCursorInteraction };