@mkbabb/glass-ui 7.0.0 → 9.0.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 (918) hide show
  1. package/README.md +4 -3
  2. package/dist/DialogContent-4hzLiaCQ.js +152 -0
  3. package/dist/DotRing-Blh4Mc3Q.js +27 -0
  4. package/dist/{DropdownMenuTrigger-ClIq8RJE.js → DropdownMenuTrigger-8UZsBtZ8.js} +5 -5
  5. package/dist/Surface-i1tWC-Ga.js +48 -0
  6. package/dist/aurora.d.ts +1 -1
  7. package/dist/aurora.js +321 -289
  8. package/dist/axes.d.ts +1 -1
  9. package/dist/axes.js +1 -22
  10. package/dist/badge-owUOaaba.js +63 -0
  11. package/dist/badge.d.ts +1 -1
  12. package/dist/badge.js +2 -2
  13. package/dist/blob-config.d.ts +1 -1
  14. package/dist/blob.d.ts +1 -1
  15. package/dist/blob.js +43 -43
  16. package/dist/button-yvGPb0Bx.js +110 -0
  17. package/dist/button.d.ts +1 -1
  18. package/dist/button.js +1 -1
  19. package/dist/{card-Bk96VI2R.js → card-3HFFpC7_.js} +51 -95
  20. package/dist/card.d.ts +1 -1
  21. package/dist/card.js +2 -2
  22. package/dist/carousel.d.ts +1 -1
  23. package/dist/carousel.js +187 -175
  24. package/dist/checkbox-B1qn65se.js +64 -0
  25. package/dist/checkbox.d.ts +1 -0
  26. package/dist/checkbox.js +2 -0
  27. package/dist/chip.d.ts +1 -1
  28. package/dist/chip.js +129 -2
  29. package/dist/{class-names-Cpy5eaBk.js → class-names-q-UGOkHH.js} +15 -1
  30. package/dist/{collapsible-BAP95XTt.js → collapsible-B7O6m-Ch.js} +2 -2
  31. package/dist/collapsible.d.ts +1 -1
  32. package/dist/collapsible.js +1 -1
  33. package/dist/color.d.ts +1 -1
  34. package/dist/color.glsl-DTJhdekG.js +34 -0
  35. package/dist/color.js +18 -18
  36. package/dist/color.wgsl-BTszLY39.js +398 -0
  37. package/dist/command-tEzAaVY_.js +340 -0
  38. package/dist/command.d.ts +1 -1
  39. package/dist/command.js +1 -1
  40. package/dist/component-styles.css +4 -0
  41. package/dist/components/_shared/axes.d.ts +4 -1
  42. package/dist/components/_shared/class-names.d.ts +1 -1
  43. package/dist/components/_shared/control.d.ts +10 -0
  44. package/dist/components/_shared/feedback/DotRing.vue.d.ts +3 -0
  45. package/dist/components/_shared/feedback/dot-ring.css +1 -0
  46. package/dist/components/_shared/feedback/feedback-tone.css +1 -0
  47. package/dist/components/_shared/{fieldControl.d.ts → field/control.d.ts} +1 -1
  48. package/dist/components/_shared/focus.d.ts +11 -0
  49. package/dist/components/_shared/index.d.ts +1 -2
  50. package/dist/components/_shared/interaction.d.ts +1 -1
  51. package/dist/components/_shared/menu/menu.css +1 -0
  52. package/dist/components/_shared/menu/rowClass.d.ts +2 -0
  53. package/dist/components/_shared/overlay/content.d.ts +40 -0
  54. package/dist/components/_shared/overlay/index.d.ts +12 -0
  55. package/dist/components/{dock/composables → _shared/overlay}/isTeleportedTarget.d.ts +5 -0
  56. package/dist/components/_shared/overlay/participation.d.ts +66 -0
  57. package/dist/components/_shared/overlay/placement.d.ts +22 -0
  58. package/dist/components/_shared/selection.d.ts +39 -0
  59. package/dist/components/_shared/surface/resolve.d.ts +17 -0
  60. package/dist/components/_shared/useMotionAxis.d.ts +1 -7
  61. package/dist/components/accordion/Accordion.vue.d.ts +1 -1
  62. package/dist/components/accordion/index.d.ts +4 -4
  63. package/dist/components/alert/Alert.vue.d.ts +1 -1
  64. package/dist/components/alert/AlertDescription.vue.d.ts +2 -2
  65. package/dist/components/alert/AlertTitle.vue.d.ts +2 -2
  66. package/dist/components/alert/index.d.ts +9 -9
  67. package/dist/components/aurora/Aurora.vue.d.ts +5 -4
  68. package/dist/components/aurora/composables/atoms-fields.d.ts +1 -1
  69. package/dist/components/aurora/composables/atoms.d.ts +3 -3
  70. package/dist/components/aurora/composables/auroraFallbackGround.d.ts +1 -1
  71. package/dist/components/aurora/composables/auroraImageSource.d.ts +2 -2
  72. package/dist/components/aurora/composables/color.d.ts +3 -4
  73. package/dist/components/aurora/composables/frameLoop.d.ts +9 -8
  74. package/dist/components/aurora/composables/glSetup.d.ts +5 -10
  75. package/dist/components/aurora/composables/runtime.d.ts +28 -3
  76. package/dist/{composables/glass → components/aurora/composables}/textureUpload.d.ts +0 -13
  77. package/dist/components/aurora/composables/uniformBridge.d.ts +3 -20
  78. package/dist/components/aurora/composables/uniformBridgeWGPU.d.ts +2 -2
  79. package/dist/components/aurora/composables/uniformBridgeWGPUImage.d.ts +2 -2
  80. package/dist/components/aurora/composables/useAurora.d.ts +32 -4
  81. package/dist/components/aurora/composables/useCursorInteraction.d.ts +2 -2
  82. package/dist/components/aurora/composables/wgpuSetup.d.ts +5 -5
  83. package/dist/components/aurora/constants/budget.d.ts +0 -36
  84. package/dist/components/aurora/constants/presets.d.ts +46 -7
  85. package/dist/components/aurora/constants/shaders/aurora-image.frag.d.ts +0 -2
  86. package/dist/components/aurora/constants/shaders/aurora-image.wgsl.d.ts +0 -2
  87. package/dist/components/aurora/index.d.ts +15 -14
  88. package/dist/components/avatar/AvatarImage.vue.d.ts +1 -6
  89. package/dist/components/avatar/index.d.ts +3 -3
  90. package/dist/components/badge/Badge.vue.d.ts +2 -9
  91. package/dist/components/badge/index.d.ts +12 -11
  92. package/dist/components/blob/Blob.vue.d.ts +4 -4
  93. package/dist/components/blob/composables/blobSimulation.d.ts +5 -5
  94. package/dist/components/blob/composables/buildMetaballProgram.d.ts +1 -1
  95. package/dist/components/blob/composables/resolveBlobSurface.d.ts +1 -1
  96. package/dist/components/blob/composables/satelliteKinematics.d.ts +1 -1
  97. package/dist/components/blob/composables/uniformBridgeWGPU.d.ts +4 -4
  98. package/dist/components/blob/composables/uploadBlobUniforms.d.ts +5 -5
  99. package/dist/components/blob/composables/useBlobMood.d.ts +1 -1
  100. package/dist/components/blob/composables/useBlobPointer.d.ts +0 -3
  101. package/dist/components/blob/composables/useBlobSatellites.d.ts +1 -1
  102. package/dist/components/blob/composables/useMetaballRenderer.d.ts +6 -6
  103. package/dist/components/blob/composables/wgpuSetup.d.ts +5 -5
  104. package/dist/components/blob/config.d.ts +3 -3
  105. package/dist/components/blob/constants.d.ts +1 -3
  106. package/dist/components/blob/index.d.ts +8 -8
  107. package/dist/components/blob/presets.d.ts +1 -1
  108. package/dist/components/button/Button.vue.d.ts +15 -9
  109. package/dist/components/button/index.d.ts +1 -1
  110. package/dist/components/button/styles.css +1 -1
  111. package/dist/components/card/Card.vue.d.ts +20 -23
  112. package/dist/components/card/CardHeader.vue.d.ts +8 -1
  113. package/dist/components/card/index.d.ts +6 -7
  114. package/dist/components/card/scroll.css +1 -0
  115. package/dist/components/card/styles.css +1 -1
  116. package/dist/components/carousel/Carousel.vue.d.ts +14 -19
  117. package/dist/components/carousel/CarouselContent.vue.d.ts +2 -12
  118. package/dist/components/carousel/CarouselItem.vue.d.ts +14 -3
  119. package/dist/components/carousel/CarouselPager.vue.d.ts +2 -2
  120. package/dist/components/carousel/index.d.ts +7 -6
  121. package/dist/components/carousel/projection.d.ts +23 -0
  122. package/dist/components/carousel/styles.css +1 -0
  123. package/dist/components/carousel/types.d.ts +47 -0
  124. package/dist/components/carousel/useCarousel.d.ts +4 -21
  125. package/dist/components/checkbox/Checkbox.vue.d.ts +6 -6
  126. package/dist/components/checkbox/index.d.ts +1 -1
  127. package/dist/components/chip/Chip.vue.d.ts +2 -2
  128. package/dist/components/chip/accent-tone.css +1 -0
  129. package/dist/components/chip/chipVariants.d.ts +1 -1
  130. package/dist/components/chip/index.d.ts +3 -3
  131. package/dist/components/chip/types.d.ts +2 -2
  132. package/dist/components/collapsible/CollapsibleTrigger.vue.d.ts +1 -1
  133. package/dist/components/collapsible/index.d.ts +3 -3
  134. package/dist/components/command/Command.vue.d.ts +5 -3
  135. package/dist/components/command/CommandDialog.vue.d.ts +9 -5
  136. package/dist/components/command/CommandEmpty.vue.d.ts +1 -1
  137. package/dist/components/command/CommandGroup.vue.d.ts +1 -1
  138. package/dist/components/command/CommandInput.vue.d.ts +1 -1
  139. package/dist/components/command/CommandItem.vue.d.ts +3 -3
  140. package/dist/components/command/CommandList.vue.d.ts +8 -8
  141. package/dist/components/command/CommandSeparator.vue.d.ts +1 -1
  142. package/dist/components/command/index.d.ts +11 -10
  143. package/dist/components/command/types.d.ts +8 -3
  144. package/dist/components/configurator/Configurator.vue.d.ts +26 -3
  145. package/dist/components/configurator/ConfiguratorRow.vue.d.ts +5 -3
  146. package/dist/components/configurator/index.d.ts +6 -6
  147. package/dist/components/configurator/styles.css +1 -1
  148. package/dist/components/configurator/useConfiguratorState.d.ts +1 -1
  149. package/dist/components/constellation/Constellation.vue.d.ts +3 -5
  150. package/dist/components/constellation/composables/createConstellationField.d.ts +1 -1
  151. package/dist/components/constellation/composables/useConstellation.d.ts +2 -2
  152. package/dist/components/constellation/constants.d.ts +1 -1
  153. package/dist/components/constellation/constellationField.d.ts +3 -4
  154. package/dist/components/constellation/constellationInteraction.d.ts +1 -5
  155. package/dist/components/constellation/constellationRender.d.ts +1 -3
  156. package/dist/components/constellation/constellationTypes.d.ts +0 -11
  157. package/dist/components/constellation/constellationWell.d.ts +1 -1
  158. package/dist/components/constellation/index.d.ts +8 -6
  159. package/dist/components/dark-mode-toggle/dark-mode-toggle.css +1 -1
  160. package/dist/components/dark-mode-toggle/index.d.ts +2 -2
  161. package/dist/components/data-table/DataTable.vue.d.ts +1 -1
  162. package/dist/components/data-table/composables/useDataTableResponsive.d.ts +1 -1
  163. package/dist/components/data-table/index.d.ts +2 -2
  164. package/dist/components/{number-field/NumberFieldContent.vue.d.ts → deck/DeckSlide.vue.d.ts} +12 -1
  165. package/dist/components/deck/DeckStage.vue.d.ts +36 -0
  166. package/dist/components/deck/composables/useDeck.d.ts +35 -10
  167. package/dist/components/deck/composables/useDeckCapture.d.ts +26 -0
  168. package/dist/components/deck/composables/useDeckHashSync.d.ts +8 -0
  169. package/dist/components/deck/composables/useDeckKeyboard.d.ts +16 -8
  170. package/dist/components/deck/composables/useDeckSnap.d.ts +20 -0
  171. package/dist/components/deck/composables/useDeckSwipe.d.ts +13 -0
  172. package/dist/components/deck/composables/useEdgeZones.d.ts +14 -0
  173. package/dist/components/deck/constants.d.ts +0 -3
  174. package/dist/components/deck/index.d.ts +22 -6
  175. package/dist/components/deck/slideContext.d.ts +21 -0
  176. package/dist/components/deck/styles/capture.css +1 -0
  177. package/dist/components/deck/styles/index.css +1 -0
  178. package/dist/components/deck/styles/reveal.css +1 -0
  179. package/dist/components/deck/styles/stage.css +1 -0
  180. package/dist/components/deck/styles/turn.css +1 -0
  181. package/dist/components/deck/types.d.ts +26 -0
  182. package/dist/components/deck/window.d.ts +27 -0
  183. package/dist/components/dialog/Dialog.vue.d.ts +13 -0
  184. package/dist/components/dialog/DialogContent.vue.d.ts +23 -27
  185. package/dist/components/dialog/DialogFooter.vue.d.ts +2 -2
  186. package/dist/components/dialog/DialogHeader.vue.d.ts +2 -2
  187. package/dist/components/dialog/ModalOverlay.vue.d.ts +44 -23
  188. package/dist/components/dialog/index.d.ts +7 -8
  189. package/dist/components/dialog/styles.css +1 -0
  190. package/dist/components/dock/DockControl.vue.d.ts +8 -4
  191. package/dist/components/dock/DockCrossfade.vue.d.ts +3 -3
  192. package/dist/components/dock/DockLayer.vue.d.ts +1 -1
  193. package/dist/components/dock/DockLayerGroup.vue.d.ts +13 -13
  194. package/dist/components/dock/DockSeparator.vue.d.ts +24 -20
  195. package/dist/components/dock/GlassDock.vue.d.ts +5 -4
  196. package/dist/components/dock/composables/dockContext.d.ts +28 -11
  197. package/dist/components/dock/composables/dockCrossfadeContext.d.ts +5 -5
  198. package/dist/components/dock/composables/dockMorphMeasure.d.ts +0 -1
  199. package/dist/components/dock/composables/dockSwitcherContext.d.ts +29 -0
  200. package/dist/components/dock/composables/index.d.ts +10 -10
  201. package/dist/components/dock/composables/useDockHold.d.ts +21 -6
  202. package/dist/components/dock/composables/useDockRun.d.ts +20 -0
  203. package/dist/components/dock/composables/useDockSearch.d.ts +5 -5
  204. package/dist/components/dock/composables/useDockShellProps.d.ts +21 -128
  205. package/dist/components/dock/composables/useDockState.d.ts +18 -12
  206. package/dist/components/dock/constants.d.ts +32 -3
  207. package/dist/components/dock/index.d.ts +15 -15
  208. package/dist/components/dock/styles/adaptive-legibility.css +1 -1
  209. package/dist/components/dock/styles/controls/icon-button.css +1 -1
  210. package/dist/components/dock/styles/controls/tab-button.css +1 -1
  211. package/dist/components/dock/styles/controls/touch-floor.css +1 -1
  212. package/dist/components/dock/styles/controls/triggers.css +1 -1
  213. package/dist/components/dock/styles/controls.css +1 -1
  214. package/dist/components/dock/styles/crossfade.css +1 -1
  215. package/dist/components/dock/styles/cta-seat.css +1 -1
  216. package/dist/components/dock/styles/density.css +1 -1
  217. package/dist/components/dock/styles/dock.css +1 -1
  218. package/dist/components/dock/styles/index.css +1 -1
  219. package/dist/components/dock/styles/layer-group.css +1 -1
  220. package/dist/components/dock/styles/layers.css +1 -1
  221. package/dist/components/dock/styles/morph.css +1 -1
  222. package/dist/components/dock/styles/run.css +1 -0
  223. package/dist/components/dock/styles/search.css +1 -1
  224. package/dist/components/dock/styles/shape.css +1 -1
  225. package/dist/components/dock/styles/shell-regions.css +1 -1
  226. package/dist/components/dock/styles/shell.css +1 -1
  227. package/dist/components/easing/EasingCurve.vue.d.ts +51 -0
  228. package/dist/components/easing/EasingPicker.vue.d.ts +16 -32
  229. package/dist/components/easing/constants.d.ts +15 -13
  230. package/dist/components/easing/index.d.ts +5 -3
  231. package/dist/components/easing/{composables/useEasingPicker.d.ts → usePicker.d.ts} +27 -23
  232. package/dist/components/expandable-container/ExpandableContainer.vue.d.ts +1 -1
  233. package/dist/components/expandable-container/index.d.ts +1 -1
  234. package/dist/components/fading-scroll/constants.d.ts +2 -9
  235. package/dist/components/fading-scroll/index.d.ts +2 -2
  236. package/dist/components/fourier-field/FourierField.vue.d.ts +31 -30
  237. package/dist/components/fourier-field/clock.d.ts +35 -0
  238. package/dist/components/fourier-field/constants.d.ts +79 -104
  239. package/dist/components/fourier-field/index.d.ts +4 -31
  240. package/dist/components/fourier-field/math.d.ts +69 -77
  241. package/dist/components/fourier-field/renderer/mint.d.ts +50 -0
  242. package/dist/components/fourier-field/renderer/uniforms.d.ts +84 -0
  243. package/dist/components/fourier-field/renderer/wgpu.d.ts +28 -0
  244. package/dist/components/fourier-field/shaders/compute.wgsl.d.ts +1 -0
  245. package/dist/components/fourier-field/shaders/render.wgsl.d.ts +1 -0
  246. package/dist/components/fourier-field/useFourierField.d.ts +49 -0
  247. package/dist/components/handmark/HandMark.vue.d.ts +18 -14
  248. package/dist/components/handmark/index.d.ts +5 -22
  249. package/dist/components/handmark/stroke.d.ts +92 -0
  250. package/dist/components/index.d.ts +30 -32
  251. package/dist/components/infinite-scroll/InfiniteScroll.vue.d.ts +3 -3
  252. package/dist/components/infinite-scroll/composables/index.d.ts +2 -2
  253. package/dist/components/infinite-scroll/composables/useInfiniteScroll.d.ts +1 -1
  254. package/dist/components/infinite-scroll/index.d.ts +2 -2
  255. package/dist/components/input/Input.vue.d.ts +10 -5
  256. package/dist/components/input/index.d.ts +3 -2
  257. package/dist/components/input/types.d.ts +49 -9
  258. package/dist/components/label/index.d.ts +1 -1
  259. package/dist/components/labeled-field/LabeledField.vue.d.ts +4 -4
  260. package/dist/components/labeled-field/LabeledInput.vue.d.ts +1 -1
  261. package/dist/components/labeled-field/LabeledSlider.vue.d.ts +1 -1
  262. package/dist/components/labeled-field/LabeledSwitch.vue.d.ts +1 -1
  263. package/dist/components/labeled-field/index.d.ts +5 -6
  264. package/dist/components/labeled-field/types.d.ts +5 -14
  265. package/dist/components/{dropdown-menu → menu}/DropdownMenu.vue.d.ts +2 -2
  266. package/dist/components/{dropdown-menu → menu}/DropdownMenuCheckboxItem.vue.d.ts +2 -2
  267. package/dist/components/{dropdown-menu → menu}/DropdownMenuContent.vue.d.ts +4 -7
  268. package/dist/components/{dropdown-menu → menu}/DropdownMenuRadioGroup.vue.d.ts +1 -1
  269. package/dist/components/{dropdown-menu → menu}/DropdownMenuRadioItem.vue.d.ts +1 -1
  270. package/dist/components/{dropdown-menu → menu}/DropdownMenuSubContent.vue.d.ts +7 -7
  271. package/dist/components/{dropdown-menu → menu}/DropdownMenuTrigger.vue.d.ts +1 -1
  272. package/dist/components/{dropdown-menu → menu}/index.d.ts +15 -15
  273. package/dist/components/metric/Metric.vue.d.ts +7 -5
  274. package/dist/components/metric/MetricRow.vue.d.ts +4 -12
  275. package/dist/components/metric/MetricStack.vue.d.ts +2 -2
  276. package/dist/components/metric/coalesce-metric.d.ts +30 -2
  277. package/dist/components/metric/index.d.ts +6 -5
  278. package/dist/components/metric/styles.css +1 -1
  279. package/dist/components/metric/types.d.ts +25 -13
  280. package/dist/components/music-staff/MusicStaff.vue.d.ts +22 -0
  281. package/dist/components/music-staff/glyphs.d.ts +74 -0
  282. package/dist/components/music-staff/index.d.ts +4 -0
  283. package/dist/components/music-staff/midiAdapter.d.ts +22 -0
  284. package/dist/components/music-staff/staffGeometry.d.ts +216 -0
  285. package/dist/components/music-staff/styles.css +1 -0
  286. package/dist/components/number-field/NumberField.vue.d.ts +7 -2
  287. package/dist/components/number-field/NumberFieldStep.vue.d.ts +31 -0
  288. package/dist/components/number-field/context.d.ts +4 -0
  289. package/dist/components/number-field/index.d.ts +3 -5
  290. package/dist/components/pager-dots/PagerDots.vue.d.ts +22 -10
  291. package/dist/components/pager-dots/index.d.ts +3 -2
  292. package/dist/components/pager-dots/worm.d.ts +42 -0
  293. package/dist/components/popover/Popover.vue.d.ts +23 -1
  294. package/dist/components/popover/PopoverContent.vue.d.ts +17 -14
  295. package/dist/components/popover/PopoverTrigger.vue.d.ts +1 -1
  296. package/dist/components/popover/index.d.ts +3 -3
  297. package/dist/components/popover/popoverContext.d.ts +10 -0
  298. package/dist/components/progress/Progress.vue.d.ts +5 -5
  299. package/dist/components/progress/index.d.ts +2 -2
  300. package/dist/components/progress/types.d.ts +16 -6
  301. package/dist/components/radio-group/RadioGroup.vue.d.ts +5 -7
  302. package/dist/components/radio-group/RadioGroupItem.vue.d.ts +3 -2
  303. package/dist/components/radio-group/index.d.ts +4 -4
  304. package/dist/components/scroll-progress-rim/ScrollProgressRim.vue.d.ts +2 -14
  305. package/dist/components/scroll-progress-rim/index.d.ts +2 -3
  306. package/dist/components/scroll-progress-rim/styles.css +1 -1
  307. package/dist/components/scroll-progress-rim/types.d.ts +20 -2
  308. package/dist/components/select/Select.vue.d.ts +2 -2
  309. package/dist/components/select/SelectContent.vue.d.ts +4 -9
  310. package/dist/components/select/SelectItem.vue.d.ts +1 -1
  311. package/dist/components/select/SelectScrollButton.vue.d.ts +18 -0
  312. package/dist/components/select/SelectTrigger.vue.d.ts +1 -8
  313. package/dist/components/select/SelectValue.vue.d.ts +4 -6
  314. package/dist/components/select/index.d.ts +18 -16
  315. package/dist/components/separator/Separator.vue.d.ts +1 -1
  316. package/dist/components/separator/index.d.ts +1 -1
  317. package/dist/components/sheet/SheetContent.vue.d.ts +60 -0
  318. package/dist/components/sheet/detents/projection.d.ts +42 -0
  319. package/dist/components/sheet/detents/use.d.ts +27 -0
  320. package/dist/components/sheet/index.d.ts +4 -0
  321. package/dist/components/sheet/motion.d.ts +29 -0
  322. package/dist/components/sheet/styles.css +1 -0
  323. package/dist/components/skeleton/index.d.ts +1 -1
  324. package/dist/components/slider/Slider.vue.d.ts +2 -3
  325. package/dist/components/slider/index.d.ts +2 -2
  326. package/dist/components/slider/styles.css +1 -0
  327. package/dist/components/slider/types.d.ts +13 -4
  328. package/dist/components/sortable-list/SortableItem.vue.d.ts +1 -1
  329. package/dist/components/sortable-list/SortableList.vue.d.ts +1 -2
  330. package/dist/components/sortable-list/context.d.ts +1 -1
  331. package/dist/components/sortable-list/drag.d.ts +52 -0
  332. package/dist/components/sortable-list/ghost.d.ts +40 -0
  333. package/dist/components/sortable-list/index.d.ts +4 -4
  334. package/dist/components/sortable-list/motion.d.ts +75 -0
  335. package/dist/components/sortable-list/resolve.d.ts +56 -0
  336. package/dist/components/sortable-list/{composables/types.d.ts → types.d.ts} +14 -7
  337. package/dist/components/sortable-list/{composables/useSortable.d.ts → useSortable.d.ts} +2 -4
  338. package/dist/components/status-dot/StatusDot.vue.d.ts +10 -3
  339. package/dist/components/status-dot/index.d.ts +3 -2
  340. package/dist/components/status-dot/states.d.ts +4 -0
  341. package/dist/components/surface/Surface.vue.d.ts +31 -14
  342. package/dist/components/surface/index.d.ts +2 -2
  343. package/dist/components/switch/Switch.vue.d.ts +7 -12
  344. package/dist/components/switch/index.d.ts +2 -2
  345. package/dist/components/table/index.d.ts +8 -8
  346. package/dist/components/tabs/SegmentedTabs.vue.d.ts +9 -13
  347. package/dist/components/tabs/composables/useTabDragMorph.d.ts +2 -2
  348. package/dist/components/tabs/composables/useTabResponsive.d.ts +1 -1
  349. package/dist/components/tabs/composables/useTabRovingFocus.d.ts +15 -7
  350. package/dist/components/tabs/index.d.ts +2 -2
  351. package/dist/components/tabs/styles/drag.css +1 -1
  352. package/dist/components/tabs/styles/segmented.css +1 -1
  353. package/dist/components/textarea/Textarea.vue.d.ts +10 -5
  354. package/dist/components/textarea/index.d.ts +2 -2
  355. package/dist/components/textarea/types.d.ts +34 -11
  356. package/dist/components/timeline/Timeline.vue.d.ts +41 -0
  357. package/dist/components/timeline/geometry.d.ts +53 -78
  358. package/dist/components/timeline/index.d.ts +2 -2
  359. package/dist/components/timeline/types.d.ts +42 -42
  360. package/dist/components/toast/Toast.vue.d.ts +3 -3
  361. package/dist/components/toast/index.d.ts +8 -8
  362. package/dist/components/toast/use-toast.d.ts +9 -2
  363. package/dist/components/toggle-group/ToggleGroup.vue.d.ts +29 -40
  364. package/dist/components/toggle-group/ToggleGroupItem.vue.d.ts +7 -7
  365. package/dist/components/toggle-group/context.d.ts +28 -0
  366. package/dist/components/toggle-group/index.d.ts +2 -3
  367. package/dist/components/tooltip/TooltipContent.vue.d.ts +3 -8
  368. package/dist/components/tooltip/TooltipTrigger.vue.d.ts +1 -1
  369. package/dist/components/tooltip/index.d.ts +4 -4
  370. package/dist/components/typewriter/TypewriterText.vue.d.ts +12 -32
  371. package/dist/components/typewriter/composables/useTypewriter.d.ts +1 -1
  372. package/dist/components/typewriter/index.d.ts +7 -7
  373. package/dist/components/typewriter/types.d.ts +1 -1
  374. package/dist/components/typewriter/utils/timing.d.ts +1 -1
  375. package/dist/components/typewriter/utils/typoStateMachine.d.ts +1 -1
  376. package/dist/composables/color/accent-tone-solve.d.ts +1 -1
  377. package/dist/composables/color/index.d.ts +7 -7
  378. package/dist/composables/context/index.d.ts +1 -1
  379. package/dist/composables/dark/darkModeSyncScript.d.ts +19 -0
  380. package/dist/composables/dark/index.d.ts +5 -5
  381. package/dist/composables/dom/index.d.ts +10 -10
  382. package/dist/composables/dom/useDragVelocity.d.ts +7 -0
  383. package/dist/composables/glass/backdropLuminanceSample.d.ts +20 -22
  384. package/dist/composables/glass/backdropSampleMath.d.ts +10 -0
  385. package/dist/composables/glass/canvas2d/index.d.ts +3 -3
  386. package/dist/composables/glass/canvas2d/useCanvas2D.d.ts +2 -10
  387. package/dist/composables/glass/index.d.ts +5 -7
  388. package/dist/composables/glass/useGlassBackdropLuminance.d.ts +3 -4
  389. package/dist/composables/glass/useSpecularTracking.d.ts +1 -1
  390. package/dist/composables/glass/webgl/createCanvasLifecycle.d.ts +13 -22
  391. package/dist/composables/glass/webgl/useWebGLCanvas.d.ts +10 -13
  392. package/dist/composables/glass/webgl/visibility.d.ts +1 -1
  393. package/dist/composables/glass/webgpu/useGpuSubstrate.d.ts +10 -13
  394. package/dist/composables/glass/webgpu/useWebGPUCanvas.d.ts +3 -4
  395. package/dist/composables/glass/webgpu/webgpuCanvasTypes.d.ts +9 -11
  396. package/dist/composables/glass/webgpu/webgpuDevice.d.ts +0 -17
  397. package/dist/composables/keyboard/index.d.ts +1 -1
  398. package/dist/composables/motion/core/constants.d.ts +7 -0
  399. package/dist/composables/motion/core/index.d.ts +21 -21
  400. package/dist/composables/motion/core/motionTempo.d.ts +0 -2
  401. package/dist/composables/motion/dissolve/dissolveGrammar.d.ts +88 -0
  402. package/dist/composables/motion/engage/engageEnvelopes.d.ts +45 -0
  403. package/dist/composables/motion/engage/engageLadder.d.ts +86 -0
  404. package/dist/composables/motion/index.d.ts +14 -14
  405. package/dist/composables/motion/morph/eyeglass.d.ts +141 -0
  406. package/dist/composables/motion/morph/useDockCtaReceive.d.ts +6 -6
  407. package/dist/composables/motion/morph/useElementMorph.d.ts +4 -4
  408. package/dist/composables/motion/morph/useLeadTrail.d.ts +30 -28
  409. package/dist/composables/motion/morph/useSelectionGroup.d.ts +61 -23
  410. package/dist/composables/motion/morph/useSelectionIndicator.d.ts +52 -22
  411. package/dist/composables/motion/number/useAnimatedNumberMap.d.ts +1 -1
  412. package/dist/composables/motion/pointer/pointerFieldMappings.d.ts +1 -42
  413. package/dist/composables/motion/pointer/useRoutePointer.d.ts +1 -1
  414. package/dist/composables/motion/reveal/useLiquidReveal.d.ts +7 -6
  415. package/dist/composables/motion/route/routeGrammar.d.ts +60 -0
  416. package/dist/composables/motion/route/useRouteTransition.d.ts +45 -0
  417. package/dist/composables/motion/scroll/useScrollProgress.d.ts +7 -14
  418. package/dist/composables/motion/scroll/useScrollScene.d.ts +5 -4
  419. package/dist/composables/motion/spring/springPresets.d.ts +13 -27
  420. package/dist/composables/motion/spring/springProjection.d.ts +3 -4
  421. package/dist/composables/motion/spring/useLiquidPress.d.ts +2 -2
  422. package/dist/composables/motion/spring/useSpring.d.ts +3 -3
  423. package/dist/composables/motion/spring/useSpringMount.d.ts +6 -3
  424. package/dist/composables/motion/spring/useSpringPress.d.ts +6 -6
  425. package/dist/composables/reactive/index.d.ts +2 -2
  426. package/dist/composables/search/index.d.ts +5 -0
  427. package/dist/{components/search/composables/fuzzySearchIndex.d.ts → composables/search/match.d.ts} +9 -4
  428. package/dist/{components/search/composables → composables/search}/useFuzzySearch.d.ts +1 -1
  429. package/dist/composables/sidebar/index.d.ts +10 -10
  430. package/dist/composables/sidebar/useClickDelegate.d.ts +1 -1
  431. package/dist/composables/sidebar/useLazyLoader.d.ts +1 -1
  432. package/dist/composables/sidebar/useScrollTo.d.ts +1 -1
  433. package/dist/composables/sidebar/useScrollTracker.d.ts +1 -1
  434. package/dist/composables/sidebar/useSidebarState.d.ts +1 -1
  435. package/dist/composables/sidebar/useTreeIndex.d.ts +1 -1
  436. package/dist/configurator-BHJymdrF.js +339 -0
  437. package/dist/configurator.d.ts +1 -1
  438. package/dist/configurator.js +1 -1
  439. package/dist/{constants-nBz23tJE.js → constants-Dk96HZHu.js} +2 -2
  440. package/dist/constellation.d.ts +1 -1
  441. package/dist/constellation.js +137 -136
  442. package/dist/content-CWO-XL7o.js +18 -0
  443. package/dist/control-DJp15L4U.js +7 -0
  444. package/dist/{field-control.css_vue_type_style_index_0_src_true_lang-CeLay9Tk.js → control.css_vue_type_style_index_0_src_true_lang-BIdufaDG.js} +1 -1
  445. package/dist/{createCanvasLifecycle-DbXRwjU_.js → createCanvasLifecycle-hJHobVTn.js} +2 -5
  446. package/dist/{dark-z_P5QwqI.js → dark-CLlmo7dD.js} +2 -2
  447. package/dist/dark-mode-toggle.d.ts +1 -1
  448. package/dist/dark-mode-toggle.js +3 -3
  449. package/dist/dark.d.ts +1 -1
  450. package/dist/dark.js +1 -1
  451. package/dist/data-table-_RrOEdiw.js +493 -0
  452. package/dist/data-table.d.ts +1 -1
  453. package/dist/data-table.js +1 -1
  454. package/dist/deck-Ci6YjB5e.js +453 -0
  455. package/dist/deck.d.ts +1 -1
  456. package/dist/deck.js +2 -94
  457. package/dist/dialog-CbRnTRtg.js +78 -0
  458. package/dist/dialog.d.ts +1 -1
  459. package/dist/dialog.js +3 -2
  460. package/dist/dock.d.ts +1 -1
  461. package/dist/dock.js +873 -620
  462. package/dist/dom.d.ts +1 -1
  463. package/dist/dom.js +141 -37
  464. package/dist/easing.d.ts +1 -1
  465. package/dist/easing.js +686 -515
  466. package/dist/expandable-container-BY1hQXi4.js +142 -0
  467. package/dist/expandable-container.d.ts +1 -1
  468. package/dist/expandable-container.js +2 -140
  469. package/dist/{fading-scroll-DhxXIhm2.js → fading-scroll-CTeW3KNS.js} +30 -30
  470. package/dist/fading-scroll.d.ts +1 -1
  471. package/dist/fading-scroll.js +1 -1
  472. package/dist/focus-Duf7EpUu.js +13 -0
  473. package/dist/fourier-field.d.ts +1 -1
  474. package/dist/fourier-field.js +641 -855
  475. package/dist/fourier-math.d.ts +1 -1
  476. package/dist/fourier-math.js +6 -6
  477. package/dist/glass-ui.css +1 -1
  478. package/dist/glass-ui.js +133 -469
  479. package/dist/handmark.d.ts +1 -1
  480. package/dist/handmark.js +330 -718
  481. package/dist/index.d.ts +41 -42
  482. package/dist/infinite-scroll.d.ts +1 -1
  483. package/dist/infinite-scroll.js +56 -43
  484. package/dist/input-Bls2A4Tt.js +54 -0
  485. package/dist/input.d.ts +1 -0
  486. package/dist/input.js +2 -0
  487. package/dist/keyboard.d.ts +1 -1
  488. package/dist/label-BQEZhnK_.js +53 -0
  489. package/dist/label.d.ts +1 -1
  490. package/dist/label.js +1 -1
  491. package/dist/labeled-field.d.ts +1 -1
  492. package/dist/labeled-field.js +64 -148
  493. package/dist/menu-TDMNtZjG.js +501 -0
  494. package/dist/menu.d.ts +1 -0
  495. package/dist/{dropdown-menu.js → menu.js} +2 -2
  496. package/dist/metric.d.ts +1 -1
  497. package/dist/metric.js +79 -132
  498. package/dist/motion-D4uYE0UM.js +61 -0
  499. package/dist/motion-core.d.ts +1 -1
  500. package/dist/motion-core.js +185 -52
  501. package/dist/motion.d.ts +1 -1
  502. package/dist/motion.js +160 -178
  503. package/dist/music-staff.d.ts +1 -0
  504. package/dist/music-staff.js +915 -0
  505. package/dist/number-field-NFAwQ0HK.js +111 -0
  506. package/dist/number-field.d.ts +1 -1
  507. package/dist/number-field.js +2 -2
  508. package/dist/pager-dots.d.ts +1 -1
  509. package/dist/pager-dots.js +362 -2
  510. package/dist/participation-Di4UHbzk.js +42 -0
  511. package/dist/{pointerFieldMappings-DP5tla5z.js → pointerFieldMappings-DXwlU5uj.js} +7 -23
  512. package/dist/popover-C04Rrdcz.js +181 -0
  513. package/dist/popover.d.ts +1 -1
  514. package/dist/popover.js +1 -1
  515. package/dist/progress-DF8dWZIX.js +74 -0
  516. package/dist/progress.d.ts +1 -1
  517. package/dist/progress.js +1 -1
  518. package/dist/radio-group-BNV7PN1O.js +85 -0
  519. package/dist/radio-group.d.ts +1 -0
  520. package/dist/radio-group.js +2 -0
  521. package/dist/reactive.d.ts +1 -1
  522. package/dist/resolve-DdrNvBby.js +6 -0
  523. package/dist/rowClass-DYOXwxhO.js +10 -0
  524. package/dist/scroll-progress-rim.d.ts +1 -1
  525. package/dist/scroll-progress-rim.js +48 -42
  526. package/dist/select-COox45Kv.js +290 -0
  527. package/dist/select.d.ts +1 -1
  528. package/dist/select.js +2 -2
  529. package/dist/separator-CN11t406.js +73 -0
  530. package/dist/separator.d.ts +1 -1
  531. package/dist/separator.js +1 -1
  532. package/dist/sheet-BEFvnO_u.js +340 -0
  533. package/dist/sheet.d.ts +1 -0
  534. package/dist/sheet.js +3 -0
  535. package/dist/sidebar.d.ts +1 -1
  536. package/dist/slider-5ChjjWSR.js +227 -0
  537. package/dist/slider.d.ts +1 -1
  538. package/dist/slider.js +1 -1
  539. package/dist/sortable-list.d.ts +1 -1
  540. package/dist/sortable-list.js +411 -286
  541. package/dist/springPresets-Cbe0Voqg.js +52 -0
  542. package/dist/status-dot-BpnqOP5N.js +56 -0
  543. package/dist/status-dot.d.ts +1 -1
  544. package/dist/status-dot.js +2 -42
  545. package/dist/styles/accessibility.css +1 -1
  546. package/dist/styles/animations.css +1 -1
  547. package/dist/styles/components.css +1 -1
  548. package/dist/styles/fonts.css +1 -1
  549. package/dist/styles/glass/a11y-fallback.css +1 -1
  550. package/dist/styles/glass/control-bit.css +1 -0
  551. package/dist/styles/glass/control-edge.css +1 -0
  552. package/dist/styles/glass/control-surfaces.css +1 -1
  553. package/dist/styles/glass/deep.css +1 -1
  554. package/dist/styles/glass/defined.css +1 -1
  555. package/dist/styles/glass/dissolve.css +1 -0
  556. package/dist/styles/glass/focus-veil.css +1 -0
  557. package/dist/styles/glass/glass-atom.css +1 -1
  558. package/dist/styles/glass/glass-capsule.css +1 -1
  559. package/dist/styles/glass/glass-chip.css +1 -1
  560. package/dist/styles/glass/grasp.css +1 -0
  561. package/dist/styles/glass/ladder-undershadow.css +1 -1
  562. package/dist/styles/glass/ladder.css +1 -1
  563. package/dist/styles/glass/liquid-enter.css +1 -1
  564. package/dist/styles/glass/liquid-fill.css +1 -1
  565. package/dist/styles/glass/mark.css +1 -0
  566. package/dist/styles/glass/material.css +1 -1
  567. package/dist/styles/glass/overlay-plate.css +1 -0
  568. package/dist/styles/glass/reveal.css +1 -1
  569. package/dist/styles/glass/rim.css +1 -1
  570. package/dist/styles/glass/squircle.css +1 -1
  571. package/dist/styles/glass/surface-axis.css +1 -1
  572. package/dist/styles/glass/surfaces-pager.css +1 -1
  573. package/dist/styles/glass/surfaces.css +1 -1
  574. package/dist/styles/glass/track-flow.css +1 -0
  575. package/dist/styles/glass/value-marks.css +1 -0
  576. package/dist/styles/glass/veil.css +1 -0
  577. package/dist/styles/glass-specular-track.css +1 -1
  578. package/dist/styles/glass.css +1 -1
  579. package/dist/styles/index.css +1 -1
  580. package/dist/styles/paper.css +1 -1
  581. package/dist/styles/scroll-choreography.css +1 -1
  582. package/dist/styles/scroll-chrome.css +1 -1
  583. package/dist/styles/scroll-driven.css +1 -1
  584. package/dist/styles/theme/bridges.css +1 -1
  585. package/dist/styles/theme/dark.css +1 -1
  586. package/dist/styles/theme/literals.css +1 -1
  587. package/dist/styles/theme/radius.css +1 -1
  588. package/dist/styles/theme.css +1 -1
  589. package/dist/styles/tokens/color-radius.css +1 -1
  590. package/dist/styles/tokens/dark-arm-glass.css +1 -1
  591. package/dist/styles/tokens/dark-arm.css +1 -1
  592. package/dist/styles/tokens/glass-deep.css +1 -1
  593. package/dist/styles/tokens/glass-fx.css +1 -1
  594. package/dist/styles/tokens/glass.css +1 -1
  595. package/dist/styles/tokens/light-dark.css +1 -1
  596. package/dist/styles/tokens/manifest.d.ts +4 -4
  597. package/dist/styles/tokens/motion-registers.css +1 -1
  598. package/dist/styles/tokens/offsets.css +1 -1
  599. package/dist/styles/tokens/on-glass-fg.css +1 -1
  600. package/dist/styles/tokens/property-regs-specular.css +1 -1
  601. package/dist/styles/tokens/property-regs.css +1 -1
  602. package/dist/styles/tokens/scale-paper.css +1 -1
  603. package/dist/styles/tokens/scheme-motion.css +1 -1
  604. package/dist/styles/tokens/scheme-spring.css +1 -1
  605. package/dist/styles/tokens/scroll-tokens.css +1 -1
  606. package/dist/styles/tokens/shadow.css +1 -1
  607. package/dist/styles/tokens/sizing-config.css +1 -1
  608. package/dist/styles/tokens/sizing.css +1 -1
  609. package/dist/styles/tokens.css +1 -1
  610. package/dist/styles/tokens.d.ts +2 -2
  611. package/dist/styles/track-well.css +1 -0
  612. package/dist/styles/transitions.css +1 -1
  613. package/dist/styles/typography/scale.css +1 -1
  614. package/dist/styles/typography/semantic.css +1 -1
  615. package/dist/styles/typography/utilities.css +1 -1
  616. package/dist/styles/typography.css +1 -1
  617. package/dist/styles/utilities/a11y-overrides.css +1 -1
  618. package/dist/styles/utilities/base-misc.css +1 -1
  619. package/dist/styles/utilities/base.css +1 -1
  620. package/dist/styles/utilities/btn.css +1 -1
  621. package/dist/styles/utilities/components.css +1 -1
  622. package/dist/styles/utilities/metal.css +1 -1
  623. package/dist/styles/utilities/responsive.css +1 -1
  624. package/dist/styles/utilities.css +1 -1
  625. package/dist/styles/view-transition.css +1 -1
  626. package/dist/styles/viz-reveal.css +1 -1
  627. package/dist/{supportsCssTimeline-C2zyWpl8.js → supportsCssTimeline-Di9gzleG.js} +5 -4
  628. package/dist/surface.d.ts +1 -1
  629. package/dist/surface.js +1 -1
  630. package/dist/switch-lLkbREB-.js +49 -0
  631. package/dist/switch.d.ts +1 -1
  632. package/dist/switch.js +1 -1
  633. package/dist/tabs-DSJMd1Et.js +268 -0
  634. package/dist/tabs.d.ts +1 -1
  635. package/dist/tabs.js +2 -311
  636. package/dist/textarea.d.ts +1 -0
  637. package/dist/textarea.js +58 -0
  638. package/dist/timeline.d.ts +1 -1
  639. package/dist/timeline.js +238 -528
  640. package/dist/{toast-CH5mnsYT.js → toast-9c6Kp58L.js} +52 -50
  641. package/dist/toast.d.ts +1 -1
  642. package/dist/toast.js +1 -1
  643. package/dist/toggle-group-DUcmKnDw.js +125 -0
  644. package/dist/toggle-group.d.ts +1 -1
  645. package/dist/toggle-group.js +1 -1
  646. package/dist/tokens.d.ts +1 -1
  647. package/dist/tokens.js +12 -10
  648. package/dist/tooltip-BKk2qVDn.js +142 -0
  649. package/dist/tooltip.d.ts +1 -1
  650. package/dist/tooltip.js +1 -1
  651. package/dist/typewriter.d.ts +1 -1
  652. package/dist/typewriter.js +10 -32
  653. package/dist/useAccentTone-DFAh1osB.js +47 -0
  654. package/dist/{resolveCanvasColor-DS_6DBxA.js → useCanvas2D-CQRElMyE.js} +2 -13
  655. package/dist/{useDockCtaReceive-DkrOTM0q.js → useDockCtaReceive-BSSgD6mu.js} +2 -2
  656. package/dist/{useDragMorph-BogSV_nv.js → useDragMorph-B363RUbF.js} +59 -59
  657. package/dist/{useDragVelocity-6ONX0sr0.js → useDragVelocity-C5BkKiNf.js} +28 -24
  658. package/dist/useIntersectionPause-CzKR3EyP.js +57 -0
  659. package/dist/useLeadTrail-DQWdpFN1.js +83 -0
  660. package/dist/{useLiquidPress-BOxuDkKa.js → useLiquidPress-h1scrPtP.js} +1 -1
  661. package/dist/useScrollChrome-BJbWV32F.js +162 -0
  662. package/dist/useScrollProgress-Bq-KPaKM.js +41 -0
  663. package/dist/{useScrollTrigger-BDdqptlc.js → useScrollTrigger-Abe5dEeO.js} +25 -26
  664. package/dist/useSelectionGroup-zsEwitF6.js +291 -0
  665. package/dist/{useSpringMount-uQ8QL9Ff.js → useSpringMount-OA-0mTNv.js} +5 -6
  666. package/dist/vReveal-B5WYGofz.js +44 -0
  667. package/dist/{valueDomain-SbVzoHMH.js → valueDomain-DXRzfc8l.js} +1 -1
  668. package/package.json +52 -109
  669. package/dist/FeedbackMark-DZq5001h.js +0 -23
  670. package/dist/Input-9BlLluik.js +0 -77
  671. package/dist/PagerDots-BEq0MgtH.js +0 -311
  672. package/dist/Surface-kFQeYrYv.js +0 -82
  673. package/dist/animated-digit.d.ts +0 -1
  674. package/dist/animated-digit.js +0 -44
  675. package/dist/badge-u65NClWn.js +0 -62
  676. package/dist/button-Bu9F4uU6.js +0 -98
  677. package/dist/canvas.d.ts +0 -1
  678. package/dist/canvas.js +0 -2
  679. package/dist/chip-DFZQr6rV.js +0 -129
  680. package/dist/color.wgsl-Ct5o9dpf.js +0 -411
  681. package/dist/command-SFqDQ65h.js +0 -320
  682. package/dist/compile-D7VYG3gi.js +0 -19
  683. package/dist/completion-seal.d.ts +0 -1
  684. package/dist/completion-seal.js +0 -132
  685. package/dist/components/_shared/FeedbackMark.vue.d.ts +0 -11
  686. package/dist/components/_shared/control-size.d.ts +0 -2
  687. package/dist/components/_shared/disclosure.css +0 -1
  688. package/dist/components/_shared/feedback-tone.css +0 -1
  689. package/dist/components/_shared/feedback.d.ts +0 -7
  690. package/dist/components/_shared/field-control.css +0 -1
  691. package/dist/components/_shared/field-surfaces.css +0 -1
  692. package/dist/components/_shared/floating.d.ts +0 -14
  693. package/dist/components/_shared/menu.css +0 -1
  694. package/dist/components/_shared/menuRowClass.d.ts +0 -2
  695. package/dist/components/_shared/resolveSurfaceClass.d.ts +0 -3
  696. package/dist/components/animated-digit/AnimatedDigit.vue.d.ts +0 -55
  697. package/dist/components/animated-digit/index.d.ts +0 -2
  698. package/dist/components/avatar/styles.css +0 -1
  699. package/dist/components/card/CardAction.vue.d.ts +0 -17
  700. package/dist/components/card/card-scroll.css +0 -1
  701. package/dist/components/carousel/interface.d.ts +0 -22
  702. package/dist/components/checkbox/styles.css +0 -1
  703. package/dist/components/combobox/Combobox.vue.d.ts +0 -22
  704. package/dist/components/combobox/ComboboxAnchor.vue.d.ts +0 -14
  705. package/dist/components/combobox/ComboboxEmpty.vue.d.ts +0 -14
  706. package/dist/components/combobox/ComboboxGroup.vue.d.ts +0 -14
  707. package/dist/components/combobox/ComboboxInput.vue.d.ts +0 -18
  708. package/dist/components/combobox/ComboboxItem.vue.d.ts +0 -18
  709. package/dist/components/combobox/ComboboxItemIndicator.vue.d.ts +0 -14
  710. package/dist/components/combobox/ComboboxList.vue.d.ts +0 -29
  711. package/dist/components/combobox/ComboboxTrigger.vue.d.ts +0 -16
  712. package/dist/components/combobox/index.d.ts +0 -10
  713. package/dist/components/combobox/types.d.ts +0 -87
  714. package/dist/components/command/dialogContext.d.ts +0 -6
  715. package/dist/components/command/styles.css +0 -1
  716. package/dist/components/completion-seal/CompletionSeal.vue.d.ts +0 -28
  717. package/dist/components/completion-seal/composables/useCompletionSeal.d.ts +0 -37
  718. package/dist/components/completion-seal/constants.d.ts +0 -66
  719. package/dist/components/completion-seal/index.d.ts +0 -4
  720. package/dist/components/completion-seal/styles.css +0 -1
  721. package/dist/components/data-table/styles.css +0 -1
  722. package/dist/components/deck/DeckPager.vue.d.ts +0 -29
  723. package/dist/components/dialog/DialogClose.vue.d.ts +0 -16
  724. package/dist/components/dialog/DialogTrigger.vue.d.ts +0 -16
  725. package/dist/components/dialog/dialogStageContext.d.ts +0 -7
  726. package/dist/components/dialog/placement.css +0 -1
  727. package/dist/components/dialog/sheet-motion.d.ts +0 -17
  728. package/dist/components/dock/composables/useDockOverflowFit.d.ts +0 -20
  729. package/dist/components/dock/composables/useDockTouchGate.d.ts +0 -42
  730. package/dist/components/dock/styles/overflow.css +0 -1
  731. package/dist/components/drawer/Drawer.vue.d.ts +0 -82
  732. package/dist/components/drawer/DrawerContent.vue.d.ts +0 -39
  733. package/dist/components/drawer/DrawerDescription.vue.d.ts +0 -17
  734. package/dist/components/drawer/DrawerFooter.vue.d.ts +0 -17
  735. package/dist/components/drawer/DrawerHeader.vue.d.ts +0 -17
  736. package/dist/components/drawer/DrawerOverlay.vue.d.ts +0 -8
  737. package/dist/components/drawer/DrawerTitle.vue.d.ts +0 -17
  738. package/dist/components/drawer/composables/drawerSnapContext.d.ts +0 -34
  739. package/dist/components/drawer/composables/useDrawerSnap.d.ts +0 -26
  740. package/dist/components/drawer/constants.d.ts +0 -32
  741. package/dist/components/drawer/index.d.ts +0 -33
  742. package/dist/components/drawer/styles.css +0 -1
  743. package/dist/components/dropdown-menu/styles.css +0 -1
  744. package/dist/components/easing/EasingConfigurator.vue.d.ts +0 -33
  745. package/dist/components/expandable-container/styles.css +0 -1
  746. package/dist/components/fourier-field/composables/fourierFieldGLSetup.d.ts +0 -24
  747. package/dist/components/fourier-field/composables/fourierFieldWGPUSetup.d.ts +0 -31
  748. package/dist/components/fourier-field/composables/uniformBridgeWGPU.d.ts +0 -41
  749. package/dist/components/fourier-field/composables/useFourierField.d.ts +0 -56
  750. package/dist/components/fourier-field/presets.d.ts +0 -54
  751. package/dist/components/fourier-field/shaders/fourier-field.compute.wgsl.d.ts +0 -1
  752. package/dist/components/fourier-field/shaders/fourier-field.glsl.d.ts +0 -4
  753. package/dist/components/fourier-field/shaders/fourier-field.render.wgsl.d.ts +0 -1
  754. package/dist/components/fourier-field/shaders/fourier-field.ribbon.d.ts +0 -51
  755. package/dist/components/handmark/brush.d.ts +0 -269
  756. package/dist/components/handmark/composables/useHandMark.d.ts +0 -99
  757. package/dist/components/handmark/constants.d.ts +0 -69
  758. package/dist/components/handmark/freehand.d.ts +0 -69
  759. package/dist/components/handmark/geometry.d.ts +0 -65
  760. package/dist/components/handmark/ink.d.ts +0 -11
  761. package/dist/components/handmark/noise.d.ts +0 -30
  762. package/dist/components/handmark/texture.d.ts +0 -39
  763. package/dist/components/handmark/types.d.ts +0 -70
  764. package/dist/components/header-ribbon/HeaderRibbon.vue.d.ts +0 -13
  765. package/dist/components/header-ribbon/index.d.ts +0 -2
  766. package/dist/components/header-ribbon/styles.css +0 -1
  767. package/dist/components/header-ribbon/types.d.ts +0 -10
  768. package/dist/components/instrument-chassis/InstrumentChassis.vue.d.ts +0 -23
  769. package/dist/components/instrument-chassis/index.d.ts +0 -2
  770. package/dist/components/instrument-chassis/styles.css +0 -1
  771. package/dist/components/instrument-chassis/types.d.ts +0 -13
  772. package/dist/components/labeled-field/LabeledSelect.vue.d.ts +0 -20
  773. package/dist/components/liquid-grid/LiquidGrid.vue.d.ts +0 -46
  774. package/dist/components/liquid-grid/composables/liquidGrid.d.ts +0 -113
  775. package/dist/components/liquid-grid/composables/liquidGridGLSetup.d.ts +0 -18
  776. package/dist/components/liquid-grid/composables/liquidGridWGPUSetup.d.ts +0 -21
  777. package/dist/components/liquid-grid/composables/uniformBridgeWGPU.d.ts +0 -16
  778. package/dist/components/liquid-grid/composables/useLiquidGrid.d.ts +0 -32
  779. package/dist/components/liquid-grid/constants.d.ts +0 -92
  780. package/dist/components/liquid-grid/index.d.ts +0 -5
  781. package/dist/components/liquid-grid/shaders/liquid-grid.glsl.d.ts +0 -4
  782. package/dist/components/liquid-grid/shaders/liquid-grid.wgsl.d.ts +0 -1
  783. package/dist/components/metric/MetricCell.vue.d.ts +0 -26
  784. package/dist/components/number-field/NumberFieldDecrement.vue.d.ts +0 -18
  785. package/dist/components/number-field/NumberFieldIncrement.vue.d.ts +0 -18
  786. package/dist/components/number-field/styles.css +0 -1
  787. package/dist/components/pager-dots/composables/usePagerWorm.d.ts +0 -26
  788. package/dist/components/pager-dots/constants.d.ts +0 -14
  789. package/dist/components/pager-dots/pagerWindow.d.ts +0 -9
  790. package/dist/components/paper-backdrop/PaperBackdrop.vue.d.ts +0 -8
  791. package/dist/components/paper-backdrop/index.d.ts +0 -2
  792. package/dist/components/pulse/Pulse.vue.d.ts +0 -14
  793. package/dist/components/pulse/index.d.ts +0 -2
  794. package/dist/components/radio-group/styles.css +0 -1
  795. package/dist/components/search/FuzzySearch.vue.d.ts +0 -24
  796. package/dist/components/search/SearchBar.vue.d.ts +0 -38
  797. package/dist/components/search/composables/index.d.ts +0 -5
  798. package/dist/components/search/index.d.ts +0 -9
  799. package/dist/components/search/searchVariants.d.ts +0 -17
  800. package/dist/components/select/SelectScrollDownButton.vue.d.ts +0 -17
  801. package/dist/components/select/SelectScrollUpButton.vue.d.ts +0 -17
  802. package/dist/components/sortable-list/composables/dragController.d.ts +0 -29
  803. package/dist/components/sortable-list/composables/dropResolver.d.ts +0 -34
  804. package/dist/components/sortable-list/composables/ghostRenderer.d.ts +0 -44
  805. package/dist/components/sortable-list/composables/index.d.ts +0 -1
  806. package/dist/components/sortable-list/composables/touchGate.d.ts +0 -26
  807. package/dist/components/sortable-list/composables/transitionTiming.d.ts +0 -50
  808. package/dist/components/switch/styles.css +0 -1
  809. package/dist/components/tabs/constants.d.ts +0 -1
  810. package/dist/components/tags-input/TagsInput.vue.d.ts +0 -41
  811. package/dist/components/tags-input/TagsInputInput.vue.d.ts +0 -10
  812. package/dist/components/tags-input/TagsInputItem.vue.d.ts +0 -19
  813. package/dist/components/tags-input/TagsInputItemDelete.vue.d.ts +0 -17
  814. package/dist/components/tags-input/TagsInputItemText.vue.d.ts +0 -7
  815. package/dist/components/tags-input/context.d.ts +0 -5
  816. package/dist/components/tags-input/index.d.ts +0 -5
  817. package/dist/components/tags-input/styles.css +0 -1
  818. package/dist/components/timeline/ContinuousMarkers.vue.d.ts +0 -47
  819. package/dist/components/timeline/ContinuousRail.vue.d.ts +0 -23
  820. package/dist/components/timeline/ContinuousTimeline.vue.d.ts +0 -117
  821. package/dist/components/timeline/GlassTimeline.vue.d.ts +0 -113
  822. package/dist/components/timeline/ScrubberTimeline.vue.d.ts +0 -45
  823. package/dist/components/timeline/SegmentedTimeline.vue.d.ts +0 -45
  824. package/dist/components/toggle-group/styles.css +0 -1
  825. package/dist/components/toggle-group/toggleGroupContext.d.ts +0 -9
  826. package/dist/components/typewriter/composables/index.d.ts +0 -1
  827. package/dist/components/watercolor-dot/WatercolorDot.vue.d.ts +0 -61
  828. package/dist/components/watercolor-dot/index.d.ts +0 -3
  829. package/dist/components/watercolor-dot/prng.d.ts +0 -5
  830. package/dist/components/watercolor-dot/useWatercolorBlob.d.ts +0 -28
  831. package/dist/composables/glass/ambientHueHistogram.d.ts +0 -27
  832. package/dist/composables/glass/useSpecularPointer.d.ts +0 -52
  833. package/dist/composables/glass/wave/index.d.ts +0 -4
  834. package/dist/composables/glass/wave/waveField.d.ts +0 -136
  835. package/dist/composables/glass/wave/waveField.glsl.d.ts +0 -1
  836. package/dist/composables/glass/wave/waveField.wgsl.d.ts +0 -1
  837. package/dist/composables/glass/webgpu/index.d.ts +0 -3
  838. package/dist/composables/index.d.ts +0 -9
  839. package/dist/composables/motion/number/useCountup.d.ts +0 -30
  840. package/dist/composables/motion/number/useNumericTransition.d.ts +0 -56
  841. package/dist/composables/motion/reveal/bloomUpField.d.ts +0 -29
  842. package/dist/composables/motion/reveal/useBloomUp.d.ts +0 -33
  843. package/dist/composables/motion/reveal/useStaggerReveal.d.ts +0 -28
  844. package/dist/composables/motion/reveal/useTextHighlight.d.ts +0 -41
  845. package/dist/composables/motion/scroll/useScrollPin.d.ts +0 -49
  846. package/dist/configurator-M5OaIlJd.js +0 -311
  847. package/dist/data-table-BygKg6ZA.js +0 -491
  848. package/dist/dialog-TNRDkcE4.js +0 -385
  849. package/dist/dockContext-BUTlGRNJ.js +0 -13
  850. package/dist/drawer.d.ts +0 -1
  851. package/dist/drawer.js +0 -496
  852. package/dist/dropdown-menu-BlbnvMaZ.js +0 -507
  853. package/dist/dropdown-menu.d.ts +0 -1
  854. package/dist/floating-dB5TD88k.js +0 -7
  855. package/dist/flow.wgsl-Cd2GUkPx.js +0 -4
  856. package/dist/fonts/README.md +0 -120
  857. package/dist/fonts/fira-code/OFL.txt +0 -93
  858. package/dist/fonts/plus-jakarta-sans/OFL.txt +0 -93
  859. package/dist/forms.d.ts +0 -5
  860. package/dist/forms.js +0 -403
  861. package/dist/header-ribbon.d.ts +0 -1
  862. package/dist/header-ribbon.js +0 -43
  863. package/dist/instrument-chassis.d.ts +0 -1
  864. package/dist/instrument-chassis.js +0 -54
  865. package/dist/label-DnMqH7jr.js +0 -55
  866. package/dist/liquid-grid.d.ts +0 -1
  867. package/dist/liquid-grid.js +0 -1065
  868. package/dist/menuRowClass-Nh7CtMON.js +0 -12
  869. package/dist/number-field-Boe09SvX.js +0 -154
  870. package/dist/paper-backdrop.d.ts +0 -1
  871. package/dist/paper-backdrop.js +0 -23
  872. package/dist/popover-BQGYXZyO.js +0 -244
  873. package/dist/progress-DClbhRAT.js +0 -88
  874. package/dist/pulse.d.ts +0 -1
  875. package/dist/pulse.js +0 -42
  876. package/dist/resolveSurfaceClass-DCZ4PZcy.js +0 -6
  877. package/dist/search.d.ts +0 -1
  878. package/dist/search.js +0 -352
  879. package/dist/select-BcBAyLXA.js +0 -336
  880. package/dist/separator-CgZInrX-.js +0 -75
  881. package/dist/slider-DzqeQmMu.js +0 -162
  882. package/dist/springPresets-rCii5KqX.js +0 -52
  883. package/dist/styles/draw-in.css +0 -1
  884. package/dist/styles/glass/accent-tone.css +0 -1
  885. package/dist/styles/glass/grain-overlay.css +0 -1
  886. package/dist/styles/glass/material-roles.css +0 -1
  887. package/dist/styles/glass-refract.css +0 -1
  888. package/dist/styles/tokens/manifest.ts +0 -56
  889. package/dist/styles/tokens.ts +0 -54
  890. package/dist/switch-DZjQuFLa.js +0 -54
  891. package/dist/toggle-group-O6jBo7AE.js +0 -150
  892. package/dist/tooltip-Drx5qnw_.js +0 -148
  893. package/dist/useAccentTone-DyInfHXE.js +0 -23
  894. package/dist/useAnimatedNumber-WPtMz0AN.js +0 -40
  895. package/dist/useDocumentVisibility-Dh_TGXsU.js +0 -21
  896. package/dist/useFuzzySearch-CUnZknpm.js +0 -177
  897. package/dist/useIntersectionPause-B5mAU6XY.js +0 -39
  898. package/dist/useLeadTrail-AW1-zZcd.js +0 -74
  899. package/dist/useRAFLoop-B3YlWK4M.js +0 -98
  900. package/dist/useScrollProgress-CSFYXIZT.js +0 -43
  901. package/dist/useSelectionGroup-B5XplBbo.js +0 -132
  902. package/dist/useTabRovingFocus-YnWh-Ytr.js +0 -169
  903. package/dist/useTextHighlight-mfwNIC1f.js +0 -112
  904. package/dist/useTouchGate-B4mzQcHJ.js +0 -84
  905. package/dist/useUserInvalidAria-0GNrddMX.js +0 -25
  906. package/dist/watercolor-dot.d.ts +0 -1
  907. package/dist/watercolor-dot.js +0 -149
  908. /package/dist/components/_shared/{disclosure-context.d.ts → disclosure/disclosure-context.d.ts} +0 -0
  909. /package/dist/components/_shared/{valueDomain.d.ts → field/valueDomain.d.ts} +0 -0
  910. /package/dist/components/{dropdown-menu → menu}/DropdownMenuGroup.vue.d.ts +0 -0
  911. /package/dist/components/{dropdown-menu → menu}/DropdownMenuItem.vue.d.ts +0 -0
  912. /package/dist/components/{dropdown-menu → menu}/DropdownMenuLabel.vue.d.ts +0 -0
  913. /package/dist/components/{dropdown-menu → menu}/DropdownMenuSeparator.vue.d.ts +0 -0
  914. /package/dist/components/{dropdown-menu → menu}/DropdownMenuShortcut.vue.d.ts +0 -0
  915. /package/dist/components/{dropdown-menu → menu}/DropdownMenuSub.vue.d.ts +0 -0
  916. /package/dist/components/{dropdown-menu → menu}/DropdownMenuSubTrigger.vue.d.ts +0 -0
  917. /package/dist/components/{dropdown-menu/useMenuTrigger.d.ts → menu/context.d.ts} +0 -0
  918. /package/dist/{components/search/composables → composables/search}/types.d.ts +0 -0
package/dist/aurora.js CHANGED
@@ -1,28 +1,27 @@
1
1
  import { t as e } from "./_plugin-vue_export-helper-B3ysoDQm.js";
2
2
  import { t } from "./usePointerVelocityField-DsIf7yyq.js";
3
- import { d as n, s as r } from "./pointerFieldMappings-DP5tla5z.js";
4
- import { t as i } from "./useScrollProgress-CSFYXIZT.js";
5
- import { t as a } from "./useIntersectionPause-B5mAU6XY.js";
3
+ import { a as n, c as r } from "./pointerFieldMappings-DXwlU5uj.js";
4
+ import { t as i } from "./useScrollProgress-Bq-KPaKM.js";
5
+ import { t as a } from "./useIntersectionPause-CzKR3EyP.js";
6
6
  import { t as o } from "./value-DMhh2R94.js";
7
7
  import { cssToOklch as s, deriveHue as c, gamutMapStop as l, oklchStopToHex as u, oklchToLinear as d, warmCatchLight as f } from "./color.js";
8
- import { a as p, c as m, d as h, f as g, i as _, l as v, n as y, o as b, p as x, r as S, s as C, t as w, u as ee } from "./color.wgsl-Ct5o9dpf.js";
9
- import { a as T, n as E, o as te } from "./rendererStatus-BxUlBLVy.js";
10
- import { n as ne, t as D } from "./compile-D7VYG3gi.js";
11
- import { n as re, t as ie } from "./flow.wgsl-Cd2GUkPx.js";
12
- import { computed as O, createElementBlock as ae, createElementVNode as k, defineComponent as oe, getCurrentInstance as se, normalizeClass as ce, normalizeStyle as le, onBeforeUnmount as ue, onMounted as de, openBlock as fe, readonly as pe, ref as me, shallowRef as he, unref as A, watch as j } from "vue";
13
- import { interpolateHue as ge } from "@mkbabb/value.js/color";
14
- //#region src/composables/glass/textureUpload.ts
15
- var _e = {
8
+ import { a as p, c as m, i as h, l as g, n as _, o as v, r as y, s as b, t as x } from "./color.glsl-DTJhdekG.js";
9
+ import { a as S, i as C, n as w, o as T, r as E, t as D } from "./color.wgsl-BTszLY39.js";
10
+ import { a as O, n as ee, o as te } from "./rendererStatus-BxUlBLVy.js";
11
+ import { computed as k, createElementBlock as ne, createElementVNode as A, defineComponent as re, getCurrentInstance as ie, normalizeClass as ae, normalizeStyle as oe, onBeforeUnmount as se, onMounted as ce, openBlock as le, readonly as j, ref as M, shallowRef as ue, unref as N, watch as P } from "vue";
12
+ import { interpolateHue as de } from "@mkbabb/value.js/color";
13
+ //#region src/components/aurora/composables/textureUpload.ts
14
+ var fe = {
16
15
  premultiplyAlpha: "none",
17
16
  colorSpaceConversion: "none"
18
17
  };
19
- function M(e) {
20
- return createImageBitmap(e, _e);
18
+ function pe(e) {
19
+ return createImageBitmap(e, fe);
21
20
  }
22
- async function ve(e) {
23
- return typeof e == "string" ? M(await (await fetch(e)).blob()) : e instanceof Blob ? M(e) : e;
21
+ async function me(e) {
22
+ return typeof e == "string" ? pe(await (await fetch(e)).blob()) : e instanceof Blob ? pe(e) : e;
24
23
  }
25
- function N() {
24
+ function he() {
26
25
  let e = new ImageData(1, 1);
27
26
  return e.data.set([
28
27
  128,
@@ -31,7 +30,7 @@ function N() {
31
30
  255
32
31
  ]), e;
33
32
  }
34
- function ye(e) {
33
+ function ge(e) {
35
34
  return e instanceof HTMLImageElement ? {
36
35
  width: e.naturalWidth,
37
36
  height: e.naturalHeight
@@ -40,7 +39,7 @@ function ye(e) {
40
39
  height: e.height
41
40
  };
42
41
  }
43
- function be(e, t, n = {}) {
42
+ function _e(e, t, n = {}) {
44
43
  let { linearFilter: r = !0, clampToEdge: i = !0 } = n, a = e.createTexture();
45
44
  e.bindTexture(e.TEXTURE_2D, a), e.pixelStorei(e.UNPACK_PREMULTIPLY_ALPHA_WEBGL, !1), e.pixelStorei(e.UNPACK_COLORSPACE_CONVERSION_WEBGL, e.NONE), e.pixelStorei(e.UNPACK_FLIP_Y_WEBGL, !1), e.texImage2D(e.TEXTURE_2D, 0, e.RGBA, e.RGBA, e.UNSIGNED_BYTE, t);
46
45
  let o = r ? e.LINEAR : e.NEAREST;
@@ -48,8 +47,8 @@ function be(e, t, n = {}) {
48
47
  let s = i ? e.CLAMP_TO_EDGE : e.REPEAT;
49
48
  return e.texParameteri(e.TEXTURE_2D, e.TEXTURE_WRAP_S, s), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_WRAP_T, s), e.pixelStorei(e.UNPACK_COLORSPACE_CONVERSION_WEBGL, e.BROWSER_DEFAULT_WEBGL), a;
50
49
  }
51
- function P(e, t) {
52
- let { width: n, height: r } = ye(t), i = e.createTexture({
50
+ function ve(e, t) {
51
+ let { width: n, height: r } = ge(t), i = e.createTexture({
53
52
  label: "[texture-upload] image",
54
53
  size: [
55
54
  n,
@@ -77,7 +76,7 @@ function P(e, t) {
77
76
  }
78
77
  //#endregion
79
78
  //#region src/components/aurora/composables/auroraImageSource.ts
80
- function xe(e) {
79
+ function ye(e) {
81
80
  let t = null, n = null, r;
82
81
  return {
83
82
  getDecodedImage: () => t,
@@ -85,7 +84,7 @@ function xe(e) {
85
84
  n = e, e && t && e(t);
86
85
  },
87
86
  ensureDecoded(i) {
88
- i.source !== "image" || i.src == null || i.src !== r && (r = i.src, ve(i.src).then((r) => {
87
+ i.source !== "image" || i.src == null || i.src !== r && (r = i.src, me(i.src).then((r) => {
89
88
  t = r, n?.(r), e.wake();
90
89
  }).catch((t) => {
91
90
  e.onError?.(t instanceof Error ? t : Error(String(t)));
@@ -93,13 +92,13 @@ function xe(e) {
93
92
  }
94
93
  };
95
94
  }
96
- function Se(e, t, n) {
95
+ function be(e, t, n) {
97
96
  let r = null;
98
97
  e.activeTexture(e.TEXTURE0);
99
98
  let i = (n) => {
100
- e.useProgram(t), e.activeTexture(e.TEXTURE0), r && e.deleteTexture(r), r = be(e, n);
99
+ e.useProgram(t), e.activeTexture(e.TEXTURE0), r && e.deleteTexture(r), r = _e(e, n);
101
100
  };
102
- return i(n.getDecodedImage() ?? N()), n.setImageUploader(i), () => {
101
+ return i(n.getDecodedImage() ?? he()), n.setImageUploader(i), () => {
103
102
  r && e.deleteTexture(r), n.setImageUploader(null);
104
103
  };
105
104
  }
@@ -108,18 +107,18 @@ function Se(e, t, n) {
108
107
  function F() {
109
108
  let e;
110
109
  try {
111
- e = h();
110
+ e = T();
112
111
  } catch {
113
112
  return !1;
114
113
  }
115
114
  return e === null ? !1 : e.includes("swiftshader") || e.includes("llvmpipe") || e.includes("software") || e.includes("basic render") || e.includes("microsoft basic");
116
115
  }
117
- function I(e, t = {}) {
116
+ function xe(e, t = {}) {
118
117
  return e !== "css" && !t.forceWebGLUnderSoftwareRaster && F() ? "css" : e === "auto" ? "webgl" : e;
119
118
  }
120
119
  //#endregion
121
120
  //#region src/components/aurora/constants/shaders/aurora.vert.ts
122
- var Ce = "#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}", we = "// ── Palette LUT (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. 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", Te = "// ── Flow field ────────────────────────────────────────────────────────────\n// Structure-tensor prototype (body defined post-sampleBase in the mediums block).\n// Returns vec4(dir.x, dir.y, coherence, packedGrad) — the minor-eigenvector tangent + A + the\n// metal-gradient .w lane in the widened vec3→vec4 return. The forward declaration must match\n// the vec4 definition or WebKit's compiler rejects the mismatched signature (L1b).\nvec4 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. 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 return dir;\n}\n", Ee = "// 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", De = "// ── 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.\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// 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// The internal streak perturbs hue and 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);\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.\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. 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. 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// 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 from the 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 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 // vec4 (the metal-finish widen) — .xy tangent + .z coherence; the .w gradient lane\n // is unused here. A vec3 local would be a type-mismatch WebKit rejects (L1c).\n vec4 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 // 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", Oe = "// ── Van Gogh atomic-dab body ─────────────────────────────────────────────\n// Van-Gogh does NOT route 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// runs 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// This 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 current profile lengthens\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 — fine divisionist flecks. 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 ─────────────────────────\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 at 0.92 in the\n // The discrete-dab profile'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 a 1.04 punch on TOP overshoots the\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 // not a widened band.\n result = saturate3(result, 1.0);\n return result;\n}\n\n", ke = "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 — finer 240 → 340. Oil's β 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 round-dab speckle.\n 1.0 // groundFloor — oil keeps the full base (no ground darken; fills everywhere)\n );\n\n // The Van Gogh medium does NOT route through this oil\n // StrokeProfile cascade. It is a DEDICATED atomic-dab body (mediumVangogh / vangoghDab\n // below) — a profile-driven cascade (four dense layers of long-thin\n // tensor-oriented strokes) merges into the \"marbled flow-bands\" read and runs 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 // 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 — kept low 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", Ae = "\n// ── Gradient pack ──────────────────────────────────\n// structureTensorField re-plumbs its already-computed luma gradient (Gx,Gy) out through\n// the vec4 return value's .w lane so the metal medium pays ZERO extra taps (a second Sobel is\n// 8 sampleBase() taps). 12 bits per bounded component ([-4,4]) into one f32, exact under\n// highp; the .xy/.z crayon/kuwahara callers are transparent to the widen.\nfloat packGrad(float gx, float gy) {\n float qx = floor(clamp(gx * 0.125 + 0.5, 0.0, 1.0) * 4095.0);\n float qy = floor(clamp(gy * 0.125 + 0.5, 0.0, 1.0) * 4095.0);\n return qx + qy * 4096.0;\n}\nvec2 unpackGrad(float packed) {\n float qy = floor(packed * (1.0 / 4096.0));\n float qx = packed - qy * 4096.0;\n return (vec2(qx, qy) * (1.0 / 4095.0) - 0.5) * 8.0;\n}\n", je = "#define MEDIUM_METAL 8\n#define MEDIUM_METALGRAD 9\n\n// ── Metal — two-term anisotropic BRDF over the field relief (uMedium==8/9) ──\n// Metal is a MUTUALLY-EXCLUSIVE medium (NOT a finish axis) — the field re-lights as\n// warm folded metal: the luma HEIGHT field (the packed structure-tensor gradient →\n// the surface normal) catches an anisotropic Blinn streak (brushed metal, the streak\n// runs ALONG the coherent edge-tangent) plus a sharp crest, both raked by a cursor-\n// synthesized light that reads on BOTH backends (uCursor is in-struct — a phantom\n// uLightDir read would be flat on the WGSL primary). The catch is the ACHROMATIC-WARM\n// anchor (AURORA_CATCH_LIGHT_ANCHOR ≈ [1.0,0.97,0.90], never a cold chrome-blue). The\n// body keeps the field HUE (technicolor valley base); the height rides the relief.\n// ZERO new taps — structureTensorField already computed the gradient (unpacked from .w).\nconst vec3 METAL_CATCH_WARM = vec3(1.0, 0.97, 0.90); // AURORA_CATCH_LIGHT_ANCHOR\nconst float METAL_HEIGHT_SCALE = 2.2; // gradient → normal tilt (× uMetalHeightScale)\nconst float METAL_LIGHT_Z = 0.55; // synth-light z (toward viewer)\nconst vec2 METAL_IDLE_RAKE = vec2(0.32, 0.34); // upper-right idle rake bias\nconst float METAL_SHININESS_ANISO = 6.0; // anisotropic streak exponent (brushed)\nconst float METAL_SHININESS_CREST = 40.0; // crest specular exponent (sharp)\nconst float METAL_COHERENCE_FLOOR = 0.22; // streak gates on edge coherence A\nconst float METAL_BODY_FLOOR = 0.42; // diffuse valley floor (dark side reads)\nconst float METAL_GRADIENT_FLATTEN = 0.55; // metal-gradient pre-flatten toward luma\nconst float METAL_SPARKLE_DENSITY = 240.0; // metal-gradient per-cell flake grid\nconst float METAL_SPARKLE_THRESH = 0.72; // only some cells flake\nconst float METAL_SPARKLE_AMP = 0.6; // flake glint amplitude\n\n// The shared metal BRDF core — takes the ALREADY-computed structureTensorField vec4 so\n// the caller pays ZERO extra taps (metal-gradient reuses the same stf). WebKit-fenced:\n// every pow base is clamped to [0,1], the exponents are compile-time constants.\nvec3 metalShade(vec3 baseCol, vec2 p, vec4 stf) {\n vec2 tangent = stf.xy;\n float A = stf.z;\n vec2 grad = unpackGrad(stf.w);\n // Height-field normal: N = normalize(vec3(grad * scale, 1)). The luma relief tilts\n // the surface; the flat field reads near vec3(0,0,1) (facing the viewer).\n vec3 N = normalize(vec3(grad * (METAL_HEIGHT_SCALE * uMetalHeightScale), 1.0));\n vec3 V = vec3(0.0, 0.0, 1.0);\n // Cursor-synthesized rake light (in-struct on BOTH backends — never uLightDir). The\n // idle bias keeps a still field lit from the upper-right; a moving cursor rakes it.\n vec3 L = normalize(vec3((uCursor - p) + METAL_IDLE_RAKE, METAL_LIGHT_Z));\n vec3 H = normalize(L + V);\n // Anisotropic streak — brushed metal: the highlight runs ALONG the edge-tangent, so\n // it is a function of the H-component PERPENDICULAR to the tangent (sinTH).\n float tH = dot(normalize(tangent), H.xy);\n float sinTH = sqrt(clamp(1.0 - tH * tH, 0.0, 1.0));\n float streak = pow(clamp(sinTH, 0.0, 1.0), METAL_SHININESS_ANISO);\n float crest = pow(clamp(dot(N, H), 0.0, 1.0), METAL_SHININESS_CREST);\n float spec = streak * crest;\n // Coherence gate — the anisotropic streak reads only where the field has a real edge\n // (a flat zone has no brush direction; A≈0 → no streak).\n spec *= smoothstep(0.0, METAL_COHERENCE_FLOOR, A);\n // Technicolor valley base — the body keeps the FIELD hue; the diffuse term rides the\n // height relief so the ridges brighten and the pits fall toward the floor.\n float diff = clamp(dot(N, L), 0.0, 1.0);\n vec3 body = baseCol * (METAL_BODY_FLOOR + (1.0 - METAL_BODY_FLOOR) * diff);\n // The catch is the achromatic-warm anchor, scaled by the polish knob.\n return body + METAL_CATCH_WARM * (spec * clamp(uMetalPolish, 0.0, 4.0));\n}\n\nvec3 mediumMetal(vec3 col, vec2 p, float t) {\n vec4 stf = structureTensorField(p, t, flowField(p, t));\n return metalShade(col, p, stf);\n}\n\n// metal-gradient (uMedium==9) — the SAME BRDF over a pre-flattened/gradient base + a\n// twinkle-in-place flake sparkle (position FIXED per cell, PHASE-animated, gated on\n// facing — flake glint, never a boil).\nvec3 mediumMetalGradient(vec3 col, vec2 p, float t) {\n vec4 stf = structureTensorField(p, t, flowField(p, t));\n // Pre-flatten toward luma (the smoother gradient-metallic base) — same BRDF, calmer body.\n // NB: flat is a GLSL reserved interpolation qualifier — WebKit's compiler REJECTS it as an\n // identifier (ANGLE tolerates it); named flatCol so the WebKit aurora shader compiles (L1a).\n vec3 flatCol = mix(col, vec3(dot(col, W_LUMA)), METAL_GRADIENT_FLATTEN);\n vec3 metal = metalShade(flatCol, p, stf);\n // The height-field normal facing (recomputed from the same packed gradient — ZERO taps).\n vec2 grad = unpackGrad(stf.w);\n float facing = normalize(vec3(grad * (METAL_HEIGHT_SCALE * uMetalHeightScale), 1.0)).z;\n // Twinkle-in-place: a fixed per-cell flake seed, phase-animated glint gated on facing.\n vec2 cell = floor(p * METAL_SPARKLE_DENSITY);\n float seed = hash21(cell);\n float tw = pow(clamp(sin(t + seed * 6.2831853), 0.0, 1.0), 40.0);\n float flakeGate = step(METAL_SPARKLE_THRESH, seed);\n metal += METAL_CATCH_WARM * (tw * flakeGate * clamp(facing, 0.0, 1.0) * METAL_SPARKLE_AMP);\n return metal;\n}\n", Me = "// ── 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" + Ae + "\n// ── Structure tensor / edge-tangent flow ───────────────────────────────────\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// coherence A = (λ1-λ2)/(λ1+λ2); flat zones relax toward fallbackDir by (1-A). The\n// single-pass small-tap WebGL2 form. Returns vec4(tangent.xy, A, packGrad(Gx,Gy)) —\n// The .w lane carries the metal medium's gradient; the .xy/.z callers are unchanged.\nvec4 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 vec4(dir, A, packGrad(Gx, Gy));\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", Ne = "// ── Medium id constants (mirror uniformBridge.ts MEDIUM_ID — the uMedium ladder) ──\n// The StrokeProfile selector 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// 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, 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 ────────────────────────\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, such as van-Gogh or\n// oil-pastel, 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; // 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.\n float impastoFloor;// 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; // 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; // 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" + ke + "\n// The single parameterized four-layer stroke cascade. 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 // The −5/3 radii respacing was measured and reverted, 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. The soft\n // anisotropic-Kuwahara finish therefore remains 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 paints 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. 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 // 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 // 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 // Preserve 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 — anisotropic generalized painterly finish (uMedium==7) ───────\n// The SOTA edge-preserving painterly smoothing: the generalized/anisotropic Kuwahara of\n// Kyprianidis 2010, the SOFT polynomial-weighted variant (NOT the pre-2010 hard argmin,\n// which BANDS the flat field into an 8-spoke pinwheel — the §4.2 anti-regression). Aurora\n// is a PROCEDURAL field (no input texture), so the operator runs over sampleBase() directly:\n// an elliptical kernel oriented along the structure tensor (elongated along the edge-tangent,\n// narrowed by the anisotropy), divided into 8 overlapping sectors via smooth weights; the\n// output is the variance-weighted blend of the sector means (1/(1+var^q) — low-variance\n// sectors dominate, the SOFT criterion → flat oil-paint patches with crisp zone boundaries).\n// Single-pass small-radius WebGL2 form: 4 rings × 8 angular taps = 32 procedural samples\n// (cost-bounded; the offscreen-park, PRM freeze the substrate owns is untouched).\nvec3 mediumKuwahara(vec3 col, vec2 p, float t) {\n // Orient the kernel along the structure-tensor edge-tangent + read the coherence A\n // (the field widened vec3→vec4 for metal's gradient; Kuwahara reads only .xy/.z).\n vec4 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" + je + Oe + "// The oil-pastel medium (uMedium==6) is a distinct stroke-deposition body,\n// separate from the dry-crayon tooth-multiply (mediumCrayon). 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}", L = "\n", Pe = `#version 300 es
121
+ var Se = "#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}", Ce = "\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", we = "// ── Palette LUT (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. 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", Te = "// ── Flow field ────────────────────────────────────────────────────────────\n// Structure-tensor prototype (body defined post-sampleBase in the mediums block).\n// Returns vec4(dir.x, dir.y, coherence, packedGrad) — the minor-eigenvector tangent + A + the\n// metal-gradient .w lane in the widened vec3→vec4 return. The forward declaration must match\n// the vec4 definition or WebKit's compiler rejects the mismatched signature (L1b).\nvec4 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. 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 return dir;\n}\n", Ee = "// 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", De = "// ── 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.\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// 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// The internal streak perturbs hue and 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);\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.\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. 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. 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// 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 from the 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 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 // vec4 (the metal-finish widen) — .xy tangent + .z coherence; the .w gradient lane\n // is unused here. A vec3 local would be a type-mismatch WebKit rejects (L1c).\n vec4 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 // 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", Oe = "// ── Van Gogh atomic-dab body ─────────────────────────────────────────────\n// Van-Gogh does NOT route 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// runs 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// This 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 current profile lengthens\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 — fine divisionist flecks. 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 ─────────────────────────\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 at 0.92 in the\n // The discrete-dab profile'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 a 1.04 punch on TOP overshoots the\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 // not a widened band.\n result = saturate3(result, 1.0);\n return result;\n}\n\n", ke = "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 — finer 240 → 340. Oil's β 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 round-dab speckle.\n 1.0 // groundFloor — oil keeps the full base (no ground darken; fills everywhere)\n );\n\n // The Van Gogh medium does NOT route through this oil\n // StrokeProfile cascade. It is a DEDICATED atomic-dab body (mediumVangogh / vangoghDab\n // below) — a profile-driven cascade (four dense layers of long-thin\n // tensor-oriented strokes) merges into the \"marbled flow-bands\" read and runs 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 // 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 — kept low 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", Ae = "\n// ── Gradient pack ──────────────────────────────────\n// structureTensorField re-plumbs its already-computed luma gradient (Gx,Gy) out through\n// the vec4 return value's .w lane so the metal medium pays ZERO extra taps (a second Sobel is\n// 8 sampleBase() taps). 12 bits per bounded component ([-4,4]) into one f32, exact under\n// highp; the .xy/.z crayon/kuwahara callers are transparent to the widen.\nfloat packGrad(float gx, float gy) {\n float qx = floor(clamp(gx * 0.125 + 0.5, 0.0, 1.0) * 4095.0);\n float qy = floor(clamp(gy * 0.125 + 0.5, 0.0, 1.0) * 4095.0);\n return qx + qy * 4096.0;\n}\nvec2 unpackGrad(float packed) {\n float qy = floor(packed * (1.0 / 4096.0));\n float qx = packed - qy * 4096.0;\n return (vec2(qx, qy) * (1.0 / 4095.0) - 0.5) * 8.0;\n}\n", je = "#define MEDIUM_METAL 8\n#define MEDIUM_METALGRAD 9\n\n// ── Metal — two-term anisotropic BRDF over the field relief (uMedium==8/9) ──\n// Metal is a MUTUALLY-EXCLUSIVE medium (NOT a finish axis) — the field re-lights as\n// warm folded metal: the luma HEIGHT field (the packed structure-tensor gradient →\n// the surface normal) catches an anisotropic Blinn streak (brushed metal, the streak\n// runs ALONG the coherent edge-tangent) plus a sharp crest, both raked by a cursor-\n// synthesized light that reads on BOTH backends (uCursor is in-struct — a phantom\n// uLightDir read would be flat on the WGSL primary). The catch is the ACHROMATIC-WARM\n// anchor (AURORA_CATCH_LIGHT_ANCHOR ≈ [1.0,0.97,0.90], never a cold chrome-blue). The\n// body keeps the field HUE (technicolor valley base); the height rides the relief.\n// ZERO new taps — structureTensorField already computed the gradient (unpacked from .w).\nconst vec3 METAL_CATCH_WARM = vec3(1.0, 0.97, 0.90); // AURORA_CATCH_LIGHT_ANCHOR\nconst float METAL_HEIGHT_SCALE = 2.2; // gradient → normal tilt (× uMetalHeightScale)\nconst float METAL_LIGHT_Z = 0.55; // synth-light z (toward viewer)\nconst vec2 METAL_IDLE_RAKE = vec2(0.32, 0.34); // upper-right idle rake bias\nconst float METAL_SHININESS_ANISO = 6.0; // anisotropic streak exponent (brushed)\nconst float METAL_SHININESS_CREST = 40.0; // crest specular exponent (sharp)\nconst float METAL_COHERENCE_FLOOR = 0.22; // streak gates on edge coherence A\nconst float METAL_BODY_FLOOR = 0.42; // diffuse valley floor (dark side reads)\nconst float METAL_GRADIENT_FLATTEN = 0.55; // metal-gradient pre-flatten toward luma\nconst float METAL_SPARKLE_DENSITY = 240.0; // metal-gradient per-cell flake grid\nconst float METAL_SPARKLE_THRESH = 0.72; // only some cells flake\nconst float METAL_SPARKLE_AMP = 0.6; // flake glint amplitude\n\n// The shared metal BRDF core — takes the ALREADY-computed structureTensorField vec4 so\n// the caller pays ZERO extra taps (metal-gradient reuses the same stf). WebKit-fenced:\n// every pow base is clamped to [0,1], the exponents are compile-time constants.\nvec3 metalShade(vec3 baseCol, vec2 p, vec4 stf) {\n vec2 tangent = stf.xy;\n float A = stf.z;\n vec2 grad = unpackGrad(stf.w);\n // Height-field normal: N = normalize(vec3(grad * scale, 1)). The luma relief tilts\n // the surface; the flat field reads near vec3(0,0,1) (facing the viewer).\n vec3 N = normalize(vec3(grad * (METAL_HEIGHT_SCALE * uMetalHeightScale), 1.0));\n vec3 V = vec3(0.0, 0.0, 1.0);\n // Cursor-synthesized rake light (in-struct on BOTH backends — never uLightDir). The\n // idle bias keeps a still field lit from the upper-right; a moving cursor rakes it.\n vec3 L = normalize(vec3((uCursor - p) + METAL_IDLE_RAKE, METAL_LIGHT_Z));\n vec3 H = normalize(L + V);\n // Anisotropic streak — brushed metal: the highlight runs ALONG the edge-tangent, so\n // it is a function of the H-component PERPENDICULAR to the tangent (sinTH).\n float tH = dot(normalize(tangent), H.xy);\n float sinTH = sqrt(clamp(1.0 - tH * tH, 0.0, 1.0));\n float streak = pow(clamp(sinTH, 0.0, 1.0), METAL_SHININESS_ANISO);\n float crest = pow(clamp(dot(N, H), 0.0, 1.0), METAL_SHININESS_CREST);\n float spec = streak * crest;\n // Coherence gate — the anisotropic streak reads only where the field has a real edge\n // (a flat zone has no brush direction; A≈0 → no streak).\n spec *= smoothstep(0.0, METAL_COHERENCE_FLOOR, A);\n // Technicolor valley base — the body keeps the FIELD hue; the diffuse term rides the\n // height relief so the ridges brighten and the pits fall toward the floor.\n float diff = clamp(dot(N, L), 0.0, 1.0);\n vec3 body = baseCol * (METAL_BODY_FLOOR + (1.0 - METAL_BODY_FLOOR) * diff);\n // The catch is the achromatic-warm anchor, scaled by the polish knob.\n return body + METAL_CATCH_WARM * (spec * clamp(uMetalPolish, 0.0, 4.0));\n}\n\nvec3 mediumMetal(vec3 col, vec2 p, float t) {\n vec4 stf = structureTensorField(p, t, flowField(p, t));\n return metalShade(col, p, stf);\n}\n\n// metal-gradient (uMedium==9) — the SAME BRDF over a pre-flattened/gradient base + a\n// twinkle-in-place flake sparkle (position FIXED per cell, PHASE-animated, gated on\n// facing — flake glint, never a boil).\nvec3 mediumMetalGradient(vec3 col, vec2 p, float t) {\n vec4 stf = structureTensorField(p, t, flowField(p, t));\n // Pre-flatten toward luma (the smoother gradient-metallic base) — same BRDF, calmer body.\n // NB: flat is a GLSL reserved interpolation qualifier — WebKit's compiler REJECTS it as an\n // identifier (ANGLE tolerates it); named flatCol so the WebKit aurora shader compiles (L1a).\n vec3 flatCol = mix(col, vec3(dot(col, W_LUMA)), METAL_GRADIENT_FLATTEN);\n vec3 metal = metalShade(flatCol, p, stf);\n // The height-field normal facing (recomputed from the same packed gradient — ZERO taps).\n vec2 grad = unpackGrad(stf.w);\n float facing = normalize(vec3(grad * (METAL_HEIGHT_SCALE * uMetalHeightScale), 1.0)).z;\n // Twinkle-in-place: a fixed per-cell flake seed, phase-animated glint gated on facing.\n vec2 cell = floor(p * METAL_SPARKLE_DENSITY);\n float seed = hash21(cell);\n float tw = pow(clamp(sin(t + seed * 6.2831853), 0.0, 1.0), 40.0);\n float flakeGate = step(METAL_SPARKLE_THRESH, seed);\n metal += METAL_CATCH_WARM * (tw * flakeGate * clamp(facing, 0.0, 1.0) * METAL_SPARKLE_AMP);\n return metal;\n}\n", Me = "// ── 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" + Ae + "\n// ── Structure tensor / edge-tangent flow ───────────────────────────────────\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// coherence A = (λ1-λ2)/(λ1+λ2); flat zones relax toward fallbackDir by (1-A). The\n// single-pass small-tap WebGL2 form. Returns vec4(tangent.xy, A, packGrad(Gx,Gy)) —\n// The .w lane carries the metal medium's gradient; the .xy/.z callers are unchanged.\nvec4 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 vec4(dir, A, packGrad(Gx, Gy));\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", Ne = "// ── Medium id constants (mirror uniformBridge.ts MEDIUM_ID — the uMedium ladder) ──\n// The StrokeProfile selector 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// 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, 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 ────────────────────────\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, such as van-Gogh or\n// oil-pastel, 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; // 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.\n float impastoFloor;// 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; // 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; // 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" + ke + "\n// The single parameterized four-layer stroke cascade. 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 // The −5/3 radii respacing was measured and reverted, 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. The soft\n // anisotropic-Kuwahara finish therefore remains 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 paints 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. 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 // 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 // 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 // Preserve 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 — anisotropic generalized painterly finish (uMedium==7) ───────\n// The SOTA edge-preserving painterly smoothing: the generalized/anisotropic Kuwahara of\n// Kyprianidis 2010, the SOFT polynomial-weighted variant (NOT the pre-2010 hard argmin,\n// which BANDS the flat field into an 8-spoke pinwheel — the §4.2 anti-regression). Aurora\n// is a PROCEDURAL field (no input texture), so the operator runs over sampleBase() directly:\n// an elliptical kernel oriented along the structure tensor (elongated along the edge-tangent,\n// narrowed by the anisotropy), divided into 8 overlapping sectors via smooth weights; the\n// output is the variance-weighted blend of the sector means (1/(1+var^q) — low-variance\n// sectors dominate, the SOFT criterion → flat oil-paint patches with crisp zone boundaries).\n// Single-pass small-radius WebGL2 form: 4 rings × 8 angular taps = 32 procedural samples\n// (cost-bounded; the offscreen-park, PRM freeze the substrate owns is untouched).\nvec3 mediumKuwahara(vec3 col, vec2 p, float t) {\n // Orient the kernel along the structure-tensor edge-tangent + read the coherence A\n // (the field widened vec3→vec4 for metal's gradient; Kuwahara reads only .xy/.z).\n vec4 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" + je + Oe + "// The oil-pastel medium (uMedium==6) is a distinct stroke-deposition body,\n// separate from the dry-crayon tooth-multiply (mediumCrayon). 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}", I = "\n", Pe = `#version 300 es
123
122
  precision highp float;
124
123
 
125
124
  in vec2 vUv;
@@ -259,7 +258,7 @@ float vnoise(vec2 p) {
259
258
  // The rotated-octave FBM rotation constant is spliced from the shared chunk
260
259
  // as the sole FBM_ROT. The loop below stays aurora-local (2.02 lacunarity +
261
260
  // uniform-driven octaves, per §3a — only the rotation constant converges).
262
- ${p}
261
+ ${x}
263
262
 
264
263
  float fbm(vec2 p) {
265
264
  float v = 0.0;
@@ -280,7 +279,7 @@ float fbm(vec2 p) {
280
279
  // lacunarity loop the rest of the warp uses). The curl operator is consumed ONLY on
281
280
  // the OPT-IN warpMode == "curl" branch (uWarpMode == 3) — the default warp paths are
282
281
  // byte-untouched (no curl call), so every existing aurora gate stays byte-equivalent.
283
- ${re}
282
+ ${Ce}
284
283
 
285
284
  // The scalar fbm potential ψ for curlFBM — aurora's own fbm. The splice above
286
285
  // forward-declares potentialFBM; this defines it (ES 3.00 allows the prototype).
@@ -294,18 +293,18 @@ float potentialFBM(vec2 p) { return fbm(p); }
294
293
  // fbm above (the cost tiering is preserved: this is an additive basis,
295
294
  // not a blanket upgrade). The aurora-local hash21/vnoise/fbm loop stays (legitimately
296
295
  // divergent per the chunk's §3a).
297
- ${v}
296
+ ${p}
298
297
 
299
298
  // The sRGB OETF (linearToSrgb) is spliced from the shared procedural-color chunk,
300
299
  // the sole OETF source; the local copy is deleted here. Aurora's linear pipeline
301
300
  // closes the color-space seam with this transfer before fragColor.
302
- ${b}
301
+ ${_}
303
302
 
304
303
  // The four Ottosson OKLab/OKLCh matrices + their space fns, spliced from the
305
304
  // SAME shared chunk the goo-blob uses (zero new payload, 1e-6-verified). PI is
306
305
  // defined first because the chunk's oklabToOklch folds the hue into [0, 2pi).
307
306
  const float PI = 3.141592653589793;
308
- ${C}
307
+ ${y}
309
308
 
310
309
  // ── OKLCh palette ramp (single-sourced twin) ───────────────────────────────
311
310
  // The muddy-midtone kill: distant-hue stops interpolated by a plain linear mix()
@@ -317,7 +316,7 @@ ${C}
317
316
  // the smoothstep-eased samplePaletteRamp dispatcher) is SPLICED from the shared
318
317
  // procedural-color chunk (the single ramp source). composition.glsl.ts's
319
318
  // samplePalette calls samplePaletteRamp.
320
- ${m}
319
+ ${h}
321
320
 
322
321
  // Interleaved Gradient Noise (Jimenez) — a 1-LSB triangular dither applied in
323
322
  // DISPLAY space AFTER the OETF, so 8-bit mid-tone banding on the soft gradient is
@@ -393,7 +392,7 @@ vec2 domainWarp(vec2 p, float t) {
393
392
  return warped;
394
393
  }
395
394
 
396
- ` + we + L + Te + "\n// ── Color utils (OKLCh; 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 — 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). Van-Gogh and oil-pastel consume\n// this seam for per-stroke pigment jitter.\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 — 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// The chroma floor operates 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. The 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" + Ee + L + Me + L + De + L + Ne + "\n\n// ── Main ──────────────────────────────────────────────────────────────────\nvoid main() {\n // Normalized 0..1 coordinates for nuclei, domain warp, and medium sampling.\n vec2 uv = vUv;\n vec2 pN = uv;\n\n float t = uTime;\n\n // Warp in pN space\n vec2 p_warp = domainWarp(pN, t);\n\n // Composition\n float paletteId; float valueMod;\n nucleiField(p_warp, t, paletteId, valueMod);\n vec3 col = samplePalette(paletteId);\n col *= 1.0 + uValueVariance * valueMod;\n\n // Breath — slow global luminance wobble\n float breath = sin(t * 6.2831 / max(uBreathPeriod, 1.0));\n col *= 1.0 + uBreathDepth * breath * 0.5;\n\n // Medium dispatch: each is a first-class body with 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).\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. 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 // The mutually exclusive metal mediums are opt-in at uMedium==8/9;\n // the smooth default + every existing medium are byte-unchanged by these arms).\n else if (uMedium == 8) col = mediumMetal(col, pN, t);\n else if (uMedium == 9) col = mediumMetalGradient(col, pN, t);\n\n // Cursor-local luminance lean. Position, radius, and the CPU-folded strength are the\n // same four packed values on WebGL2 and WebGPU; absent interactivity writes strength 0.\n vec2 cursorDelta = pN - uCursor;\n float cursorRadius = max(uCursorRadius, 0.01);\n float cursorLean = exp(-dot(cursorDelta, cursorDelta) / (cursorRadius * cursorRadius * 0.5));\n col *= 1.0 + 0.12 * cursorLean * uCursorStrength;\n\n // Saturation trim\n col = saturate3(col, uSaturation);\n\n // The chroma floor is warm-anchored and hue-preserving. It 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", Fe = `#version 300 es
395
+ ` + we + I + Te + "\n// ── Color utils (OKLCh; 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 — 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). Van-Gogh and oil-pastel consume\n// this seam for per-stroke pigment jitter.\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 — 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// The chroma floor operates 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. The 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" + Ee + I + Me + I + De + I + Ne + "\n\n// ── Main ──────────────────────────────────────────────────────────────────\nvoid main() {\n // Normalized 0..1 coordinates for nuclei, domain warp, and medium sampling.\n vec2 uv = vUv;\n vec2 pN = uv;\n\n float t = uTime;\n\n // Warp in pN space\n vec2 p_warp = domainWarp(pN, t);\n\n // Composition\n float paletteId; float valueMod;\n nucleiField(p_warp, t, paletteId, valueMod);\n vec3 col = samplePalette(paletteId);\n col *= 1.0 + uValueVariance * valueMod;\n\n // Breath — slow global luminance wobble\n float breath = sin(t * 6.2831 / max(uBreathPeriod, 1.0));\n col *= 1.0 + uBreathDepth * breath * 0.5;\n\n // Medium dispatch: each is a first-class body with 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).\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. 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 // The mutually exclusive metal mediums are opt-in at uMedium==8/9;\n // the smooth default + every existing medium are byte-unchanged by these arms).\n else if (uMedium == 8) col = mediumMetal(col, pN, t);\n else if (uMedium == 9) col = mediumMetalGradient(col, pN, t);\n\n // Cursor-local luminance lean. Position, radius, and the CPU-folded strength are the\n // same four packed values on WebGL2 and WebGPU; absent interactivity writes strength 0.\n vec2 cursorDelta = pN - uCursor;\n float cursorRadius = max(uCursorRadius, 0.01);\n float cursorLean = exp(-dot(cursorDelta, cursorDelta) / (cursorRadius * cursorRadius * 0.5));\n col *= 1.0 + 0.12 * cursorLean * uCursorStrength;\n\n // Saturation trim\n col = saturate3(col, uSaturation);\n\n // The chroma floor is warm-anchored and hue-preserving. It 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", Fe = `#version 300 es
397
396
  precision highp float;
398
397
 
399
398
  in vec2 vUv;
@@ -458,7 +457,7 @@ float vnoise(vec2 p) {
458
457
  float d = hash21(i + vec2(1.0, 1.0));
459
458
  return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
460
459
  }
461
- ${p}
460
+ ${x}
462
461
  float fbm(vec2 p) {
463
462
  float v = 0.0;
464
463
  float a = 0.5;
@@ -475,8 +474,8 @@ float ign(vec2 p) {
475
474
  return fract(52.9829189 * fract(dot(p, vec2(0.06711056, 0.00583715))));
476
475
  }
477
476
 
478
- ${b}
479
- ${C}
477
+ ${_}
478
+ ${y}
480
479
 
481
480
  // linear-sRGB → raw OKLab (the vividnessFloor helper — mirrors aurora.frag's linOklab).
482
481
  vec3 linOklab(vec3 lin) {
@@ -634,59 +633,16 @@ void main() {
634
633
  // Premultiply on display-space color (matches the palette path: OETF, then premul).
635
634
  fragColor = vec4(col * uAlpha, uAlpha);
636
635
  }
637
- `, R = .001, Ie = 1e-4;
638
- function z(e, t, i) {
639
- let a = n(e);
640
- if (i > 0) {
641
- let t = e.acceleration.value, n = e.velocity.value, r = -(t.x * n.x + t.y * n.y);
642
- r > 0 && (a.burst = Math.min(1, a.burst + r * 3));
643
- }
644
- let o = r(a, t);
645
- return {
646
- x: o.cx,
647
- y: o.cy,
648
- strength: o.strength,
649
- radius: o.radius
650
- };
651
- }
652
- function B(e) {
653
- let t = e.attractorVelocity.value;
654
- return e.engagement.value > R || e.speed.value > R || e.burst.value > R || Math.hypot(t.x, t.y) > Ie;
655
- }
656
- function Le(e) {
657
- let { gl: t, prog: n, uniforms: r, pointerField: i, getCursorScalars: a, getConfig: o, getReducedMotion: s, getRenderTime: c } = e, l = (e) => 1 - e, u = null, d = {
658
- x: .5,
659
- y: .5,
660
- strength: 0,
661
- radius: .25
662
- };
663
- function f() {
664
- return +!s();
665
- }
666
- function p(e) {
667
- let i = c(e);
668
- if (t.useProgram(n), t.uniform2f(r.uCursor, d.x, l(d.y)), t.uniform1f(r.uCursorStrength, d.strength), t.uniform1f(r.uCursorRadius, d.radius), t.uniform1f(r.uTime, i), o().interactivity?.light) {
669
- let e = f(), n = i * .25, a = Math.cos(n) * .5, o = Math.sin(n) * .5, s = (d.x - .5) * 2, c = (.5 - d.y) * 2, l = d.strength * e;
670
- a = a * (1 - l) + s * l, o = o * (1 - l) + c * l, t.uniform3f(r.uLightDir, a * e + (1 - e) * -.5, o * e + (1 - e) * .6, .62);
671
- }
672
- t.clear(t.COLOR_BUFFER_BIT), t.drawArrays(t.TRIANGLES, 0, 3);
673
- }
674
- function m() {
675
- if (s()) return !1;
676
- let e = o();
677
- return e.nucleiDrift !== 0 || e.paletteDrift !== 0 || e.breathDepth !== 0 || e.warpDrift !== 0 || B(i);
678
- }
679
- return {
680
- frame: (e) => {
681
- let t = f(), n = u === null ? 0 : Math.max(0, (e - u) * 1e3);
682
- u = e, i.tick(t === 0 ? 0 : n), d = z(i, a(), t), p(e);
683
- },
684
- needsAnimation: m
685
- };
636
+ `, L = {
637
+ nucleiDrift: 5e-4,
638
+ paletteDrift: 4e-4,
639
+ breathDepth: .0015,
640
+ warpDrift: 15e-5
641
+ };
642
+ function R(e) {
643
+ return Math.abs(e.nucleiDrift) >= L.nucleiDrift || Math.abs(e.paletteDrift) >= L.paletteDrift || Math.abs(e.breathDepth) >= L.breathDepth || Math.abs(e.warpDrift) >= L.warpDrift;
686
644
  }
687
- //#endregion
688
- //#region src/components/aurora/constants/presets.ts
689
- var Re = 8, ze = 8, Be = 1, Ve = .115, He = .004, Ue = .06, V = {
645
+ var Ie = 8, Le = 8, Re = 1, ze = .115, Be = .004, Ve = .06, z = {
690
646
  palette: [
691
647
  {
692
648
  L: .64,
@@ -754,7 +710,7 @@ var Re = 8, ze = 8, Be = 1, Ve = .115, He = .004, Ue = .06, V = {
754
710
  paperGrain: .008,
755
711
  alpha: 1,
756
712
  vividness: 1
757
- }, We = {
713
+ }, He = {
758
714
  medium: "crayon",
759
715
  strokeOrient: "tensor",
760
716
  granulation: .3,
@@ -767,10 +723,57 @@ var Re = 8, ze = 8, Be = 1, Ve = .115, He = .004, Ue = .06, V = {
767
723
  wetEdge: 0,
768
724
  saturation: .92,
769
725
  paperGrain: .008
770
- };
726
+ }, B = .001, Ue = 1e-4;
727
+ function V(e, t, i) {
728
+ let a = r(e);
729
+ if (i > 0) {
730
+ let t = e.acceleration.value, n = e.velocity.value, r = -(t.x * n.x + t.y * n.y);
731
+ r > 0 && (a.burst = Math.min(1, a.burst + r * 3));
732
+ }
733
+ let o = n(a, t);
734
+ return {
735
+ x: o.cx,
736
+ y: o.cy,
737
+ strength: o.strength,
738
+ radius: o.radius
739
+ };
740
+ }
741
+ function H(e) {
742
+ let t = e.attractorVelocity.value;
743
+ return e.engagement.value > B || e.speed.value > B || e.burst.value > B || Math.hypot(t.x, t.y) > Ue;
744
+ }
745
+ function We(e) {
746
+ let { gl: t, prog: n, uniforms: r, pointerField: i, getCursorScalars: a, getConfig: o, getReducedMotion: s, getRenderTime: c } = e, l = (e) => 1 - e, u = null, d = {
747
+ x: .5,
748
+ y: .5,
749
+ strength: 0,
750
+ radius: .25
751
+ };
752
+ function f() {
753
+ return +!s();
754
+ }
755
+ function p(e) {
756
+ let i = c(e);
757
+ if (t.useProgram(n), t.uniform2f(r.uCursor, d.x, l(d.y)), t.uniform1f(r.uCursorStrength, d.strength), t.uniform1f(r.uCursorRadius, d.radius), t.uniform1f(r.uTime, i), o().interactivity?.light) {
758
+ let e = f(), n = i * .25, a = Math.cos(n) * .5, o = Math.sin(n) * .5, s = (d.x - .5) * 2, c = (.5 - d.y) * 2, l = d.strength * e;
759
+ a = a * (1 - l) + s * l, o = o * (1 - l) + c * l, t.uniform3f(r.uLightDir, a * e + (1 - e) * -.5, o * e + (1 - e) * .6, .62);
760
+ }
761
+ t.clear(t.COLOR_BUFFER_BIT), t.drawArrays(t.TRIANGLES, 0, 3);
762
+ }
763
+ function m() {
764
+ return s() ? !1 : R(o()) ? !0 : H(i);
765
+ }
766
+ return {
767
+ frame: (e) => {
768
+ let t = f(), n = u === null ? 0 : Math.max(0, (e - u) * 1e3);
769
+ u = e, i.tick(t === 0 ? 0 : n), d = V(i, a(), t), p(e);
770
+ },
771
+ needsAnimation: m
772
+ };
773
+ }
771
774
  //#endregion
772
775
  //#region src/components/aurora/composables/color.ts
773
- function H(e, t = 8, n) {
776
+ function U(e, t = 8, n) {
774
777
  let r = n ?? new Float32Array(t * 3), i = Math.min(e.length, t);
775
778
  for (let t = 0; t < i; t++) {
776
779
  let [n, i, a] = d(e[t]);
@@ -845,25 +848,25 @@ var et = 70, tt = 250, nt = 22;
845
848
  function rt(e, t, n) {
846
849
  let r = (t - .5) * 2;
847
850
  if (r === 0 || n === 0) return e;
848
- let i = (o("applyTemperature", ge(e, r >= 0 ? et : tt, 1, "shorter")) - e + 540) % 360 - 180;
851
+ let i = (o("applyTemperature", de(e, r >= 0 ? et : tt, 1, "shorter")) - e + 540) % 360 - 180;
849
852
  return ((e + Math.sign(i) * Math.min(Math.abs(r) * n * nt, Math.abs(i))) % 360 + 360) % 360;
850
853
  }
851
- var U = (e) => (e % 360 + 360) % 360;
854
+ var W = (e) => (e % 360 + 360) % 360;
852
855
  function it(e, t, n) {
853
- let r = U(e), i = U(t), a = U(n);
856
+ let r = W(e), i = W(t), a = W(n);
854
857
  return i <= a ? r >= i && r <= a : r >= i || r <= a;
855
858
  }
856
859
  function at(e, t) {
857
- let n = Math.abs(U(e) - U(t)) % 360;
860
+ let n = Math.abs(W(e) - W(t)) % 360;
858
861
  return n > 180 ? 360 - n : n;
859
862
  }
860
863
  function ot(e, t) {
861
- let n = .5, r = U(e);
864
+ let n = .5, r = W(e);
862
865
  for (let e = 0; e < t.length + 2; e++) {
863
866
  let e = !1;
864
867
  for (let [i, a] of t) {
865
868
  if (!it(r, i, a)) continue;
866
- let t = U(i - n), o = U(a + n);
869
+ let t = W(i - n), o = W(a + n);
867
870
  r = at(r, t) <= at(r, o) ? t : o, e = !0;
868
871
  }
869
872
  if (!e) return r;
@@ -912,13 +915,13 @@ var st = {
912
915
  oil: 0,
913
916
  knife: 1,
914
917
  chunky: 3
915
- }, W = {
918
+ }, G = {
916
919
  L: .985,
917
920
  C: .0125,
918
921
  h: 77.5
919
922
  };
920
923
  function mt(e) {
921
- return e === void 0 ? f(W.L, W.C, W.h) : Array.isArray(e) ? e : f(e.L, e.C, e.h);
924
+ return e === void 0 ? f(G.L, G.C, G.h) : Array.isArray(e) ? e : f(e.L, e.C, e.h);
922
925
  }
923
926
  function ht(e) {
924
927
  return ct[e.medium];
@@ -942,7 +945,7 @@ function vt(e, t, n, r = !1) {
942
945
  angle: /* @__PURE__ */ new Float32Array(8)
943
946
  };
944
947
  return function(o) {
945
- e.useProgram(t), H(o.palette, 8, a.palette), e.uniform3fv(n.uPalette, a.palette), e.uniform1i(n.uStopCount, Math.min(o.palette.length, 8));
948
+ e.useProgram(t), U(o.palette, 8, a.palette), e.uniform3fv(n.uPalette, a.palette), e.uniform1i(n.uStopCount, Math.min(o.palette.length, 8));
946
949
  let s = Math.min(o.nuclei.length, 8);
947
950
  e.uniform1i(n.uNucleiCount, s);
948
951
  for (let e = 0; e < s; e++) {
@@ -968,9 +971,9 @@ function vt(e, t, n, r = !1) {
968
971
  }
969
972
  //#endregion
970
973
  //#region src/components/aurora/composables/glSetup.ts
971
- var G = "[Aurora]", yt = /* @__PURE__ */ "uTime.uPalette.uStopCount.uNucleiCount.uNucleiPos.uNucleiRadius.uNucleiPaletteBias.uNucleiValueBias.uNucleiDriftRadius.uNucleiDriftPhase.uNucleiElong.uNucleiAngle.uSoftmaxBeta.uValueVariance.uWarpAmount.uWarpScale.uWarpDrift.uWarpMode.uNoiseOctaves.uMedium.uHuePath.uFlowPattern.uFlowFocal.uFlowAngle.uFlowCurl.uCursor.uCursorStrength.uCursorRadius.uStrokeAmount.uStrokeScale.uStrokeAnisotropy.uStrokeLayers.uStrokeMode.uStrokeOrient.uLightDir.uLightColor.uWetEdge.uGranulation.uImpasto.uBrokenColor.uCanvasGrain.uMetalPolish.uMetalHeightScale.uNucleiDrift.uPaletteDrift.uBreathDepth.uBreathPeriod.uSaturation.uPaperGrain.uAlpha.uVividness.uImage.uBlurMin.uBlurMax.uImageAspect".split(".");
974
+ var K = "[Aurora]", yt = /* @__PURE__ */ "uTime.uPalette.uStopCount.uNucleiCount.uNucleiPos.uNucleiRadius.uNucleiPaletteBias.uNucleiValueBias.uNucleiDriftRadius.uNucleiDriftPhase.uNucleiElong.uNucleiAngle.uSoftmaxBeta.uValueVariance.uWarpAmount.uWarpScale.uWarpDrift.uWarpMode.uNoiseOctaves.uMedium.uHuePath.uFlowPattern.uFlowFocal.uFlowAngle.uFlowCurl.uCursor.uCursorStrength.uCursorRadius.uStrokeAmount.uStrokeScale.uStrokeAnisotropy.uStrokeLayers.uStrokeMode.uStrokeOrient.uLightDir.uLightColor.uWetEdge.uGranulation.uImpasto.uBrokenColor.uCanvasGrain.uMetalPolish.uMetalHeightScale.uNucleiDrift.uPaletteDrift.uBreathDepth.uBreathPeriod.uSaturation.uPaperGrain.uAlpha.uVividness.uImage.uBlurMin.uBlurMax.uImageAspect".split(".");
972
975
  function bt(e, t, n) {
973
- let r = D(e, e.VERTEX_SHADER, t, G), i = D(e, e.FRAGMENT_SHADER, n, G), a = ne(e, r, i, G);
976
+ let r = v(e, e.VERTEX_SHADER, t, K), i = v(e, e.FRAGMENT_SHADER, n, K), a = b(e, r, i, K);
974
977
  e.useProgram(a);
975
978
  let o = e.createVertexArray();
976
979
  e.bindVertexArray(o);
@@ -1000,11 +1003,11 @@ function bt(e, t, n) {
1000
1003
  }
1001
1004
  function xt(e) {
1002
1005
  return (t) => {
1003
- let n = e.getConfig().source === "image", { program: r, vs: i, fs: a, uniforms: o, geometry: s } = bt(t, Ce, n ? Fe : Pe), c = n ? Se(t, r, e.imageCoordinator) : null, l = (e) => {
1004
- t.viewport(0, 0, e?.w ?? t.canvas.width, e?.h ?? t.canvas.height), t.useProgram(r);
1006
+ let n = e.getConfig().source === "image", { program: r, vs: i, fs: a, uniforms: o, geometry: s } = bt(t, Se, n ? Fe : Pe), c = n ? be(t, r, e.imageCoordinator) : null, l = (e) => {
1007
+ t.viewport(0, 0, e.w, e.h), t.useProgram(r);
1005
1008
  }, u = vt(t, r, o, e.opaquePresentation), d = (e) => u(e);
1006
1009
  e.registerConfigUploader(d), d(e.getConfig());
1007
- let f = Le({
1010
+ let f = We({
1008
1011
  gl: t,
1009
1012
  prog: r,
1010
1013
  uniforms: o,
@@ -1101,7 +1104,7 @@ fn vnoise(p: vec2<f32>) -> f32 {
1101
1104
  return mix(mix(a, b, uu.x), mix(c, d, uu.x), uu.y);
1102
1105
  }
1103
1106
 
1104
- ${w}
1107
+ ${D}
1105
1108
 
1106
1109
  fn fbm(p0: vec2<f32>) -> f32 {
1107
1110
  var v = 0.0;
@@ -1118,11 +1121,23 @@ fn fbm(p0: vec2<f32>) -> f32 {
1118
1121
  }
1119
1122
 
1120
1123
  fn potentialFBM(p: vec2f) -> f32 { return fbm(p); }
1121
- ${ie}
1122
1124
 
1123
- ${y}
1124
- ${S}
1125
- ${_}
1125
+ const CURL_EPS: f32 = 0.012;
1126
+
1127
+ // ∇×ψ in 2D — the divergence-free flow direction. Central finite differences of the
1128
+ // scalar potential give the partials; the cross-pairing (∂y, −∂x) is the 2D curl.
1129
+ fn curlFBM(p: vec2f) -> vec2f {
1130
+ let dx = potentialFBM(p + vec2f(CURL_EPS, 0.0)) - potentialFBM(p - vec2f(CURL_EPS, 0.0));
1131
+ let dy = potentialFBM(p + vec2f(0.0, CURL_EPS)) - potentialFBM(p - vec2f(0.0, CURL_EPS));
1132
+ // ψ-gradient is (dx, dy)/(2·eps); the curl rotates it 90°: (∂ψ/∂y, −∂ψ/∂x).
1133
+ let g = vec2f(dx, dy) / (2.0 * CURL_EPS);
1134
+ return vec2f(g.y, -g.x);
1135
+ }
1136
+
1137
+
1138
+ ${w}
1139
+ ${E}
1140
+ ${C}
1126
1141
 
1127
1142
  // Interleaved Gradient Noise (Jimenez) — display-space 1-LSB dither.
1128
1143
  fn ign(p: vec2<f32>) -> f32 {
@@ -1803,7 +1818,7 @@ fn vnoise(p: vec2<f32>) -> f32 {
1803
1818
  let d = hash21(i + vec2<f32>(1.0, 1.0));
1804
1819
  return mix(mix(a, b, uu.x), mix(c, d, uu.x), uu.y);
1805
1820
  }
1806
- ${w}
1821
+ ${D}
1807
1822
  fn fbm(p0: vec2<f32>) -> f32 {
1808
1823
  var v = 0.0;
1809
1824
  var a = 0.5;
@@ -1821,8 +1836,8 @@ fn ign(p: vec2<f32>) -> f32 {
1821
1836
  return fract(52.9829189 * fract(dot(p, vec2<f32>(0.06711056, 0.00583715))));
1822
1837
  }
1823
1838
 
1824
- ${y}
1825
- ${S}
1839
+ ${w}
1840
+ ${E}
1826
1841
 
1827
1842
  fn linOklab(lin: vec3<f32>) -> vec3<f32> {
1828
1843
  let lms = LINEAR_SRGB_TO_LMS * lin;
@@ -2003,7 +2018,7 @@ fn fs_main(in: VSOut) -> @location(0) vec4<f32> {
2003
2018
 
2004
2019
  return vec4<f32>(col * alpha, alpha);
2005
2020
  }
2006
- `, K = {
2021
+ `, q = {
2007
2022
  scalars0: 0,
2008
2023
  scalars1: 4,
2009
2024
  scalars2: 8,
@@ -2030,23 +2045,23 @@ function Ot() {
2030
2045
  }
2031
2046
  function kt(e, t, n, r, i = t.alpha) {
2032
2047
  let { f32: a, i32: o, paletteScratch: s } = e;
2033
- a[K.scalars0 + 0] = r, a[K.scalars0 + 1] = t.softmaxBeta, a[K.scalars0 + 2] = t.valueVariance, a[K.scalars0 + 3] = t.warpAmount, a[K.scalars1 + 0] = t.warpScale, a[K.scalars1 + 1] = t.warpDrift, a[K.scalars1 + 2] = t.saturation, a[K.scalars1 + 3] = t.paperGrain, a[K.scalars2 + 0] = i, a[K.scalars2 + 1] = t.nucleiDrift, a[K.scalars2 + 2] = t.paletteDrift, a[K.scalars2 + 3] = t.breathDepth, a[K.scalars3 + 0] = t.breathPeriod, a[K.scalars3 + 1] = n.strength, a[K.scalars3 + 2] = n.radius, a[K.scalars3 + 3] = t.vividness ?? 1, a[K.cursor + 0] = n.x, a[K.cursor + 1] = Dt(n.y), a[K.cursor + 2] = t.metalPolish ?? 1, a[K.cursor + 3] = t.metalHeightScale ?? 1, o[K.ints0 + 0] = Math.min(t.palette.length, 8);
2048
+ a[q.scalars0 + 0] = r, a[q.scalars0 + 1] = t.softmaxBeta, a[q.scalars0 + 2] = t.valueVariance, a[q.scalars0 + 3] = t.warpAmount, a[q.scalars1 + 0] = t.warpScale, a[q.scalars1 + 1] = t.warpDrift, a[q.scalars1 + 2] = t.saturation, a[q.scalars1 + 3] = t.paperGrain, a[q.scalars2 + 0] = i, a[q.scalars2 + 1] = t.nucleiDrift, a[q.scalars2 + 2] = t.paletteDrift, a[q.scalars2 + 3] = t.breathDepth, a[q.scalars3 + 0] = t.breathPeriod, a[q.scalars3 + 1] = n.strength, a[q.scalars3 + 2] = n.radius, a[q.scalars3 + 3] = t.vividness ?? 1, a[q.cursor + 0] = n.x, a[q.cursor + 1] = Dt(n.y), a[q.cursor + 2] = t.metalPolish ?? 1, a[q.cursor + 3] = t.metalHeightScale ?? 1, o[q.ints0 + 0] = Math.min(t.palette.length, 8);
2034
2049
  let c = Math.min(t.nuclei.length, 8);
2035
- o[K.ints0 + 1] = c, o[K.ints0 + 2] = ft[t.warpMode], o[K.ints0 + 3] = t.noiseOctaves, o[K.ints1 + 0] = ct[t.medium], o[K.ints1 + 1] = lt[t.huePath ?? "shorter"], o[K.ints1 + 2] = 0, o[K.ints1 + 3] = 0, H(t.palette, 8, s);
2050
+ o[q.ints0 + 1] = c, o[q.ints0 + 2] = ft[t.warpMode], o[q.ints0 + 3] = t.noiseOctaves, o[q.ints1 + 0] = ct[t.medium], o[q.ints1 + 1] = lt[t.huePath ?? "shorter"], o[q.ints1 + 2] = 0, o[q.ints1 + 3] = 0, U(t.palette, 8, s);
2036
2051
  for (let e = 0; e < 8; e++) {
2037
- let t = K.palette + e * 4;
2052
+ let t = q.palette + e * 4;
2038
2053
  a[t + 0] = s[e * 3 + 0], a[t + 1] = s[e * 3 + 1], a[t + 2] = s[e * 3 + 2], a[t + 3] = 0;
2039
2054
  }
2040
2055
  for (let e = 0; e < 8; e++) {
2041
- let n = K.nuc0 + e * 4, r = K.nuc1 + e * 4, i = K.nuc2 + e * 4;
2056
+ let n = q.nuc0 + e * 4, r = q.nuc1 + e * 4, i = q.nuc2 + e * 4;
2042
2057
  if (e < c) {
2043
2058
  let o = t.nuclei[e];
2044
2059
  a[n + 0] = o.x, a[n + 1] = Dt(o.y), a[n + 2] = o.radius, a[n + 3] = o.paletteBias, a[r + 0] = o.valueBias, a[r + 1] = o.driftRadius, a[r + 2] = o.driftPhase, a[r + 3] = o.elongation ?? 1, a[i + 0] = -(o.angle ?? 0) * Math.PI / 180, a[i + 1] = 0, a[i + 2] = 0, a[i + 3] = 0;
2045
2060
  } else a[n + 0] = 0, a[n + 1] = 0, a[n + 2] = 0, a[n + 3] = 0, a[r + 0] = 0, a[r + 1] = 0, a[r + 2] = 0, a[r + 3] = 1, a[i + 0] = 0, a[i + 1] = 0, a[i + 2] = 0, a[i + 3] = 0;
2046
2061
  }
2047
- return a[K.scalars4 + 0] = t.strokeAmount, a[K.scalars4 + 1] = t.strokeScale, a[K.scalars4 + 2] = t.strokeAnisotropy, a[K.scalars4 + 3] = t.canvasGrain, a[K.scalars5 + 0] = t.wetEdge, a[K.scalars5 + 1] = t.granulation, a[K.scalars5 + 2] = t.brokenColor, a[K.scalars5 + 3] = t.kuwaharaQ ?? Et, a[K.kuwahara + 0] = t.kuwaharaRadius ?? wt, a[K.kuwahara + 1] = Tt, a[K.kuwahara + 2] = 0, a[K.kuwahara + 3] = 0, e;
2062
+ return a[q.scalars4 + 0] = t.strokeAmount, a[q.scalars4 + 1] = t.strokeScale, a[q.scalars4 + 2] = t.strokeAnisotropy, a[q.scalars4 + 3] = t.canvasGrain, a[q.scalars5 + 0] = t.wetEdge, a[q.scalars5 + 1] = t.granulation, a[q.scalars5 + 2] = t.brokenColor, a[q.scalars5 + 3] = t.kuwaharaQ ?? Et, a[q.kuwahara + 0] = t.kuwaharaRadius ?? wt, a[q.kuwahara + 1] = Tt, a[q.kuwahara + 2] = 0, a[q.kuwahara + 3] = 0, e;
2048
2063
  }
2049
- var q = {
2064
+ var J = {
2050
2065
  scalars0: 0,
2051
2066
  scalars1: 4,
2052
2067
  scalars2: 8,
@@ -2066,11 +2081,11 @@ function jt() {
2066
2081
  }
2067
2082
  function Mt(e, t, n, r, i, a = t.alpha) {
2068
2083
  let { f32: o, i32: s } = e;
2069
- o[q.scalars0 + 0] = r, o[q.scalars0 + 1] = t.warpAmount, o[q.scalars0 + 2] = t.warpScale, o[q.scalars0 + 3] = t.warpDrift, o[q.scalars1 + 0] = t.softmaxBeta, o[q.scalars1 + 1] = t.nucleiDrift, o[q.scalars1 + 2] = n.strength, o[q.scalars1 + 3] = n.radius, o[q.scalars2 + 0] = t.saturation, o[q.scalars2 + 1] = t.paperGrain, o[q.scalars2 + 2] = a, o[q.scalars2 + 3] = t.vividness ?? 1, o[q.image + 0] = t.imageBlur?.min ?? .004, o[q.image + 1] = t.imageBlur?.max ?? .06, o[q.image + 2] = i, o[q.image + 3] = 0, o[q.cursor + 0] = n.x, o[q.cursor + 1] = At(n.y), o[q.cursor + 2] = 0, o[q.cursor + 3] = 0;
2084
+ o[J.scalars0 + 0] = r, o[J.scalars0 + 1] = t.warpAmount, o[J.scalars0 + 2] = t.warpScale, o[J.scalars0 + 3] = t.warpDrift, o[J.scalars1 + 0] = t.softmaxBeta, o[J.scalars1 + 1] = t.nucleiDrift, o[J.scalars1 + 2] = n.strength, o[J.scalars1 + 3] = n.radius, o[J.scalars2 + 0] = t.saturation, o[J.scalars2 + 1] = t.paperGrain, o[J.scalars2 + 2] = a, o[J.scalars2 + 3] = t.vividness ?? 1, o[J.image + 0] = t.imageBlur?.min ?? .004, o[J.image + 1] = t.imageBlur?.max ?? .06, o[J.image + 2] = i, o[J.image + 3] = 0, o[J.cursor + 0] = n.x, o[J.cursor + 1] = At(n.y), o[J.cursor + 2] = 0, o[J.cursor + 3] = 0;
2070
2085
  let c = Math.min(t.nuclei.length, 8);
2071
- s[q.ints + 0] = c, s[q.ints + 1] = t.noiseOctaves, s[q.ints + 2] = 0, s[q.ints + 3] = 0;
2086
+ s[J.ints + 0] = c, s[J.ints + 1] = t.noiseOctaves, s[J.ints + 2] = 0, s[J.ints + 3] = 0;
2072
2087
  for (let e = 0; e < 8; e++) {
2073
- let n = q.nuc0 + e * 4, r = q.nuc1 + e * 4;
2088
+ let n = J.nuc0 + e * 4, r = J.nuc1 + e * 4;
2074
2089
  if (e < c) {
2075
2090
  let i = t.nuclei[e];
2076
2091
  o[n + 0] = i.x, o[n + 1] = At(i.y), o[n + 2] = i.radius, o[n + 3] = i.valueBias, o[r + 0] = i.driftRadius, o[r + 1] = i.driftPhase, o[r + 2] = i.elongation ?? 1, o[r + 3] = -(i.angle ?? 0) * Math.PI / 180;
@@ -2080,7 +2095,7 @@ function Mt(e, t, n, r, i, a = t.alpha) {
2080
2095
  }
2081
2096
  //#endregion
2082
2097
  //#region src/components/aurora/composables/wgpuSetup.ts
2083
- var J = 2, Nt = {
2098
+ var Y = 2, Nt = {
2084
2099
  color: {
2085
2100
  srcFactor: "one",
2086
2101
  dstFactor: "one-minus-src-alpha",
@@ -2117,7 +2132,7 @@ function Pt(e) {
2117
2132
  label: "[Aurora] bind-group-0",
2118
2133
  entries: [{
2119
2134
  binding: 0,
2120
- visibility: J,
2135
+ visibility: Y,
2121
2136
  buffer: { type: "uniform" }
2122
2137
  }]
2123
2138
  }), g = e.createRenderPipeline({
@@ -2148,7 +2163,7 @@ function Pt(e) {
2148
2163
  function b(t) {
2149
2164
  let c = +!a(), l = u === null ? 0 : Math.max(0, (t - u) * 1e3);
2150
2165
  u = t, s.tick(c === 0 ? 0 : l);
2151
- let f = z(s, r(), c), p = i();
2166
+ let f = V(s, r(), c), p = i();
2152
2167
  kt(v, p, f, o(t), n ? 1 : p.alpha), e.queue.writeBuffer(m, 0, v.buffer);
2153
2168
  let h = d.getCurrentTexture().createView(), y = e.createCommandEncoder({ label: "[Aurora] frame" }), b = y.beginRenderPass({ colorAttachments: [{
2154
2169
  view: h,
@@ -2164,9 +2179,7 @@ function Pt(e) {
2164
2179
  b.setPipeline(g), b.setBindGroup(0, _), b.draw(3, 1, 0, 0), b.end(), e.queue.submit([y.finish()]);
2165
2180
  }
2166
2181
  function x() {
2167
- if (a()) return !1;
2168
- let e = i();
2169
- return e.nucleiDrift !== 0 || e.paletteDrift !== 0 || e.breathDepth !== 0 || e.warpDrift !== 0 || B(s);
2182
+ return a() ? !1 : R(i()) ? !0 : H(s);
2170
2183
  }
2171
2184
  return {
2172
2185
  frame: b,
@@ -2197,12 +2210,12 @@ function Ft(e, t, n, r) {
2197
2210
  entries: [
2198
2211
  {
2199
2212
  binding: 0,
2200
- visibility: J,
2213
+ visibility: Y,
2201
2214
  buffer: { type: "uniform" }
2202
2215
  },
2203
2216
  {
2204
2217
  binding: 1,
2205
- visibility: J,
2218
+ visibility: Y,
2206
2219
  texture: {
2207
2220
  sampleType: "float",
2208
2221
  viewDimension: "2d"
@@ -2210,7 +2223,7 @@ function Ft(e, t, n, r) {
2210
2223
  },
2211
2224
  {
2212
2225
  binding: 2,
2213
- visibility: J,
2226
+ visibility: Y,
2214
2227
  sampler: { type: "filtering" }
2215
2228
  }
2216
2229
  ]
@@ -2230,7 +2243,7 @@ function Ft(e, t, n, r) {
2230
2243
  }]
2231
2244
  },
2232
2245
  primitive: { topology: "triangle-list" }
2233
- }), y = P(e, d() ?? N()).texture;
2246
+ }), y = ve(e, d() ?? he()).texture;
2234
2247
  function b() {
2235
2248
  return e.createBindGroup({
2236
2249
  label: "[Aurora] image bind-group",
@@ -2253,14 +2266,14 @@ function Ft(e, t, n, r) {
2253
2266
  }
2254
2267
  let x = b();
2255
2268
  f((t) => {
2256
- y.destroy(), y = P(e, t).texture, x = b();
2269
+ y.destroy(), y = ve(e, t).texture, x = b();
2257
2270
  });
2258
2271
  let S = jt();
2259
2272
  function C(e) {}
2260
2273
  function w(n) {
2261
2274
  let r = +!c(), d = p === null ? 0 : Math.max(0, (n - p) * 1e3);
2262
2275
  p = n, u.tick(r === 0 ? 0 : d);
2263
- let f = z(u, o(), r), m = i.height > 0 ? i.width / i.height : 1, g = s();
2276
+ let f = V(u, o(), r), m = i.height > 0 ? i.width / i.height : 1, g = s();
2264
2277
  Mt(S, g, f, l(n), m, a ? 1 : g.alpha), e.queue.writeBuffer(h, 0, S.buffer);
2265
2278
  let _ = t.getCurrentTexture().createView(), y = e.createCommandEncoder({ label: "[Aurora] image frame" }), b = y.beginRenderPass({ colorAttachments: [{
2266
2279
  view: _,
@@ -2275,14 +2288,12 @@ function Ft(e, t, n, r) {
2275
2288
  }] });
2276
2289
  b.setPipeline(v), b.setBindGroup(0, x), b.draw(3, 1, 0, 0), b.end(), e.queue.submit([y.finish()]);
2277
2290
  }
2278
- function ee() {
2279
- if (c()) return !1;
2280
- let e = s();
2281
- return e.nucleiDrift !== 0 || e.warpDrift !== 0 || e.paletteDrift !== 0 || B(u);
2291
+ function T() {
2292
+ return c() ? !1 : R(s()) ? !0 : H(u);
2282
2293
  }
2283
2294
  return {
2284
2295
  frame: w,
2285
- shouldContinue: ee,
2296
+ shouldContinue: T,
2286
2297
  resize: C,
2287
2298
  teardown: () => {
2288
2299
  f(null), y.destroy(), h.destroy();
@@ -2306,8 +2317,8 @@ function Rt(e = "live") {
2306
2317
  };
2307
2318
  }
2308
2319
  var zt = (e) => Math.min(1, Math.max(0, e));
2309
- function Y(e) {
2310
- return e.interactivity?.swirl === !0 || e.medium !== "smooth" && e.interactivity?.light === !0;
2320
+ function X(e) {
2321
+ return e.interactivity?.swirl !== !1 || e.medium !== "smooth" && e.interactivity?.light === !0;
2311
2322
  }
2312
2323
  var Bt = 3.7;
2313
2324
  function Vt(e, t, n, r) {
@@ -2325,23 +2336,23 @@ function Ht(e, n, r = {}) {
2325
2336
  leadGain: .08,
2326
2337
  halfLifeMs: 110
2327
2338
  }), f = .8, p = .25, m = () => {
2328
- let e = l.interactivity, t = Y(l);
2339
+ let e = l.interactivity, t = X(l);
2329
2340
  return {
2330
2341
  strength: t ? f : 0,
2331
2342
  radius: p,
2332
2343
  amplitude: t ? Math.min(1, Math.max(0, e?.amplitude ?? .5)) : 0
2333
2344
  };
2334
- }, h = null, g = xe({
2335
- wake: () => b.wake(),
2345
+ }, h = null, _ = ye({
2346
+ wake: () => C.wake(),
2336
2347
  onError: r.onInitError
2337
- }), _ = c ? /* @__PURE__ */ Error("[Aurora] GPU animation is unavailable under software rasterization") : null, v = !1;
2338
- function y() {
2339
- !_ || v || (v = !0, r.onInitError?.(_));
2348
+ }), v = c ? /* @__PURE__ */ Error("[Aurora] GPU animation is unavailable under software rasterization") : null, y = r.forceBackend === "webgl2", b = !1;
2349
+ function x() {
2350
+ !v || b || (b = !0, r.onInitError?.(v));
2340
2351
  }
2341
- let b = c ? {
2352
+ let C = c ? {
2342
2353
  backend: "webgl2",
2343
- arm: y,
2344
- armAsync: () => (y(), Promise.reject(_)),
2354
+ arm: x,
2355
+ armAsync: () => (x(), Promise.reject(v)),
2345
2356
  presize: () => {},
2346
2357
  suspend: () => {},
2347
2358
  resume: () => {},
@@ -2349,10 +2360,10 @@ function Ht(e, n, r = {}) {
2349
2360
  renderAt: () => {},
2350
2361
  dispose: () => {},
2351
2362
  reducedMotion: !1
2352
- } : ee(e, {
2363
+ } : S(e, {
2353
2364
  mode: r.mode === "capture" ? "capture" : "live",
2354
2365
  alphaMode: a.alphaMode,
2355
- dprPolicy: x,
2366
+ dprPolicy: g,
2356
2367
  revealBloom: r.revealBloom ?? !1,
2357
2368
  contextAttrs: {
2358
2369
  antialias: !1,
@@ -2360,25 +2371,25 @@ function Ht(e, n, r = {}) {
2360
2371
  premultipliedAlpha: a.contextAlpha,
2361
2372
  preserveDrawingBuffer: i
2362
2373
  },
2363
- setupWGPU: Pt({
2374
+ setupWGPU: y ? void 0 : Pt({
2364
2375
  canvas: e,
2365
2376
  opaquePresentation: a.opaque,
2366
2377
  getCursorScalars: m,
2367
2378
  getConfig: () => l,
2368
- getReducedMotion: () => b.reducedMotion,
2369
- getRenderTime: (e) => Vt(l, e, u, b.reducedMotion),
2379
+ getReducedMotion: () => C.reducedMotion,
2380
+ getRenderTime: (e) => Vt(l, e, u, C.reducedMotion),
2370
2381
  pointerField: d,
2371
- getDecodedImage: () => g.getDecodedImage(),
2372
- registerImageUploader: (e) => g.setImageUploader(e)
2382
+ getDecodedImage: () => _.getDecodedImage(),
2383
+ registerImageUploader: (e) => _.setImageUploader(e)
2373
2384
  }),
2374
2385
  setupGL: xt({
2375
2386
  opaquePresentation: a.opaque,
2376
2387
  getConfig: () => l,
2377
- getReducedMotion: () => b.reducedMotion,
2378
- getRenderTime: (e) => Vt(l, e, u, b.reducedMotion),
2388
+ getReducedMotion: () => C.reducedMotion,
2389
+ getRenderTime: (e) => Vt(l, e, u, C.reducedMotion),
2379
2390
  getCursorScalars: m,
2380
2391
  pointerField: d,
2381
- imageCoordinator: g,
2392
+ imageCoordinator: _,
2382
2393
  registerConfigUploader: (e) => {
2383
2394
  h = e;
2384
2395
  }
@@ -2386,44 +2397,44 @@ function Ht(e, n, r = {}) {
2386
2397
  onInitError: r.onInitError ? (e) => r.onInitError?.(e instanceof Error ? e : Error(String(e))) : void 0,
2387
2398
  onStatus: r.onRendererStatus
2388
2399
  });
2389
- _ && r.onRendererStatus?.(te("webgl2", "Software renderer", _));
2390
- function S(e, t, n = .8) {
2391
- Y(l) && (f = n, d.setActive(!0), d.setPointer(e, t), b.wake());
2400
+ v && r.onRendererStatus?.(te("webgl2", "Software renderer", v));
2401
+ function w(e, t, n = .8) {
2402
+ X(l) && (f = n, d.setActive(!0), d.setPointer(e, t), C.wake());
2392
2403
  }
2393
- function C() {
2394
- d.setActive(!1), b.wake();
2404
+ function T() {
2405
+ d.setActive(!1), C.wake();
2395
2406
  }
2396
- function w(e) {
2397
- p = e, b.wake();
2407
+ function E(e) {
2408
+ p = e, C.wake();
2398
2409
  }
2399
- function T(e) {
2410
+ function D(e) {
2400
2411
  let t = Math.min(1, Math.max(0, e));
2401
- t !== u && (u = t, l.interactivity?.scroll === !0 && !b.reducedMotion && b.wake());
2412
+ t !== u && (u = t, l.interactivity?.scroll === !0 && !C.reducedMotion && C.wake());
2402
2413
  }
2403
- function E() {
2404
- b.armAsync().catch(() => void 0);
2414
+ function O() {
2415
+ C.armAsync().catch(() => void 0);
2405
2416
  }
2406
- function ne() {
2407
- return b.armAsync();
2417
+ function ee() {
2418
+ return C.armAsync();
2408
2419
  }
2409
- return g.ensureDecoded(l), r.mode !== "capture" && It(r) && E(), {
2410
- arm: () => E(),
2411
- armAsync: () => ne(),
2420
+ return _.ensureDecoded(l), r.mode !== "capture" && It(r) && O(), {
2421
+ arm: () => O(),
2422
+ armAsync: () => ee(),
2412
2423
  update: (e) => {
2413
- l = e, s(e.alpha), Y(e) || d.setActive(!1), h?.(e), g.ensureDecoded(e), b.wake();
2424
+ l = e, s(e.alpha), X(e) || d.setActive(!1), h?.(e), _.ensureDecoded(e), C.wake();
2414
2425
  },
2415
- setCursor: S,
2416
- clearCursor: C,
2417
- setCursorRadius: w,
2418
- setScrollProgress: T,
2426
+ setCursor: w,
2427
+ clearCursor: T,
2428
+ setCursorRadius: E,
2429
+ setScrollProgress: D,
2419
2430
  get reducedMotion() {
2420
- return b.reducedMotion;
2431
+ return C.reducedMotion;
2421
2432
  },
2422
- pause: (e = "manual") => b.suspend(e),
2423
- resume: (e = "manual") => b.resume(e),
2424
- renderAt: (e) => b.renderAt(e),
2433
+ pause: (e = "manual") => C.suspend(e),
2434
+ resume: (e = "manual") => C.resume(e),
2435
+ renderAt: (e) => C.renderAt(e),
2425
2436
  dispose: () => {
2426
- b.dispose(), a.opaque && (e.style.opacity = o);
2437
+ C.dispose(), a.opaque && (e.style.opacity = o);
2427
2438
  }
2428
2439
  };
2429
2440
  }
@@ -2434,7 +2445,8 @@ function Ut(e) {
2434
2445
  }
2435
2446
  //#endregion
2436
2447
  //#region src/components/aurora/composables/useAurora.ts
2437
- function Wt(e) {
2448
+ var Wt = 500;
2449
+ function Gt(e) {
2438
2450
  if (typeof window > "u") return () => {};
2439
2451
  if (typeof window.requestIdleCallback == "function") {
2440
2452
  let t = window.requestIdleCallback(() => e(), { timeout: 2e3 });
@@ -2451,32 +2463,48 @@ function Wt(e) {
2451
2463
  r = !0, cancelAnimationFrame(t), n !== void 0 && clearTimeout(n);
2452
2464
  };
2453
2465
  }
2454
- function Gt(e, t, n = {}, r = {}) {
2455
- let o = Ut(t), s = r.renderMode === "css", c = null, l = null, u = null, d = null, f = null, p = null, m = !1, h = me(!1), g = he(s ? E() : T("webgl2")), _ = i({
2466
+ function Kt(e, t, n = {}, r = {}) {
2467
+ let o = Ut(t), s = r.renderMode === "css", c = null, l = null, u = null, d = null, f = null, p = null, m = !1, h = M(!1), g = M(!1), _ = 0, v = 0, y = !1;
2468
+ function b() {
2469
+ if (g.value = !1, typeof requestAnimationFrame != "function" || (v = performance.now() + 500, _ !== 0)) return;
2470
+ let e = (t) => {
2471
+ if (t < v) {
2472
+ _ = requestAnimationFrame(e);
2473
+ return;
2474
+ }
2475
+ _ = 0, g.value = !0;
2476
+ };
2477
+ _ = requestAnimationFrame(e);
2478
+ }
2479
+ function x() {
2480
+ _ !== 0 && cancelAnimationFrame(_), _ = 0, g.value = !1;
2481
+ }
2482
+ let S = ue(s ? ee() : O("webgl2")), C = i({
2456
2483
  target: e,
2457
2484
  trackExit: !0,
2458
- reactive: !0,
2459
- enabled: O(() => !s && o().interactivity?.scroll === !0)
2485
+ enabled: k(() => !s && o().interactivity?.scroll === !0)
2460
2486
  });
2461
- d = j(_, (e) => c?.setScrollProgress(e), { flush: "sync" });
2462
- let v = n.mode === "capture" || n.initStrategy === "eager";
2463
- function y(e) {
2487
+ d = P(C, (e) => c?.setScrollProgress(e), { flush: "sync" });
2488
+ let w = n.mode === "capture" || n.initStrategy === "eager";
2489
+ function T(e) {
2464
2490
  let t = e instanceof Error ? e : Error(String(e));
2465
- g.value = te(g.value.engine, g.value.adapter, t), n.onInitError?.(t);
2491
+ S.value = te(S.value.engine, S.value.adapter, t), n.onInitError?.(t);
2466
2492
  }
2467
- async function b() {
2493
+ async function E() {
2468
2494
  if (m || !c) return;
2469
2495
  m = !0;
2470
2496
  let e = c;
2471
2497
  try {
2472
2498
  await e.armAsync();
2473
2499
  } catch (e) {
2474
- g.value.phase !== "error" && y(e);
2500
+ S.value.phase !== "error" && T(e);
2475
2501
  return;
2476
2502
  }
2477
- c !== e || g.value.engine === "css" || (h.value = !0, e.update(o()), e.setScrollProgress(_.value), l = j(o, (t) => e.update(t), { deep: !0 }));
2503
+ c !== e || S.value.engine === "css" || (h.value = !0, e.update(o()), e.setScrollProgress(C.value), b(), y = !0, l = P(o, (t) => {
2504
+ e.update(t), b();
2505
+ }, { deep: !0 }));
2478
2506
  }
2479
- return de(() => {
2507
+ return ce(() => {
2480
2508
  let t = e.value;
2481
2509
  if (!t || s) return;
2482
2510
  try {
@@ -2484,15 +2512,16 @@ function Gt(e, t, n = {}, r = {}) {
2484
2512
  initStrategy: "deferred",
2485
2513
  ...n,
2486
2514
  onRendererStatus: (e) => {
2487
- g.value = e, h.value = e.phase === "ready", n.onRendererStatus?.(e);
2515
+ let t = e.phase === "ready", r = t && !h.value && y;
2516
+ S.value = e, h.value = t, t ? r && b() : x(), n.onRendererStatus?.(e);
2488
2517
  }
2489
2518
  });
2490
2519
  } catch (e) {
2491
- y(e);
2520
+ T(e);
2492
2521
  return;
2493
2522
  }
2494
- if (v) {
2495
- b();
2523
+ if (w) {
2524
+ E();
2496
2525
  return;
2497
2526
  }
2498
2527
  p = a(e, {
@@ -2500,13 +2529,13 @@ function Gt(e, t, n = {}, r = {}) {
2500
2529
  resume: () => c?.resume("off-screen-io")
2501
2530
  }, { pauseWhenHidden: !1 });
2502
2531
  let r = p.isIntersecting;
2503
- u = j(r, (e) => {
2504
- !e || m || f || (f = Wt(() => {
2505
- f = null, r.value && b();
2532
+ u = P(r, (e) => {
2533
+ !e || m || f || (f = Gt(() => {
2534
+ f = null, r.value && E();
2506
2535
  }));
2507
2536
  }, { immediate: !0 });
2508
- }), ue(() => {
2509
- f?.(), u?.(), l?.(), d?.(), p?.dispose(), c?.dispose(), c = null;
2537
+ }), se(() => {
2538
+ x(), f?.(), u?.(), l?.(), d?.(), p?.dispose(), c?.dispose(), c = null;
2510
2539
  }), {
2511
2540
  setCursor: (e, t, n) => c?.setCursor(e, t, n),
2512
2541
  clearCursor: () => c?.clearCursor(),
@@ -2514,17 +2543,18 @@ function Gt(e, t, n = {}, r = {}) {
2514
2543
  renderAt: (e) => c?.renderAt(e),
2515
2544
  pause: () => c?.pause(),
2516
2545
  resume: () => c?.resume(),
2517
- isArmed: pe(h),
2518
- rendererStatus: pe(g)
2546
+ isArmed: j(h),
2547
+ isSettled: j(g),
2548
+ rendererStatus: j(S)
2519
2549
  };
2520
2550
  }
2521
2551
  //#endregion
2522
2552
  //#region src/components/aurora/composables/auroraFallbackGround.ts
2523
- function X(e) {
2553
+ function Z(e) {
2524
2554
  let t = Math.max(0, Math.min(1, e));
2525
2555
  return t <= .0031308 ? t * 12.92 : 1.055 * t ** (1 / 2.4) - .055;
2526
2556
  }
2527
- function Kt([e, t, n]) {
2557
+ function qt([e, t, n]) {
2528
2558
  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));
2529
2559
  if (l < r) return [
2530
2560
  u(o),
@@ -2540,10 +2570,10 @@ function Kt([e, t, n]) {
2540
2570
  u(c + (f - c) * m)
2541
2571
  ];
2542
2572
  }
2543
- function qt([e, t, n]) {
2573
+ function Jt([e, t, n]) {
2544
2574
  return .2126 * e + .7152 * t + .0722 * n;
2545
2575
  }
2546
- function Jt(e, t) {
2576
+ function Yt(e, t) {
2547
2577
  let n = e.length;
2548
2578
  if (n === 0) return [
2549
2579
  0,
@@ -2558,7 +2588,7 @@ function Jt(e, t) {
2558
2588
  s[2] + (c[2] - s[2]) * o
2559
2589
  ];
2560
2590
  }
2561
- function Z(e, t, n, r) {
2591
+ function Xt(e, t, n, r) {
2562
2592
  let i = 0, a = 0, o = 0;
2563
2593
  for (let s of e) {
2564
2594
  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 / (_ * _));
@@ -2570,33 +2600,33 @@ function Z(e, t, n, r) {
2570
2600
  valueMod: a / s
2571
2601
  };
2572
2602
  }
2573
- function Yt(e, t, n) {
2574
- let r = e.palette.map((e) => d(e)), { paletteId: i, valueMod: a } = Z(e.nuclei, e.softmaxBeta, t, n), o = Jt(r, i), s = 1 + e.valueVariance * a, c = [
2603
+ function Zt(e, t, n) {
2604
+ let r = e.palette.map((e) => d(e)), { paletteId: i, valueMod: a } = Xt(e.nuclei, e.softmaxBeta, t, n), o = Yt(r, i), s = 1 + e.valueVariance * a, c = [
2575
2605
  o[0] * s,
2576
2606
  o[1] * s,
2577
2607
  o[2] * s
2578
2608
  ];
2579
- return c = Kt(c), c = [
2609
+ return c = qt(c), c = [
2580
2610
  Math.max(0, Math.min(1, c[0] * .985 + .008)),
2581
2611
  Math.max(0, Math.min(1, c[1] * .985 + .008)),
2582
2612
  Math.max(0, Math.min(1, c[2] * .985 + .008))
2583
2613
  ], [
2584
- X(c[0]),
2585
- X(c[1]),
2586
- X(c[2])
2614
+ Z(c[0]),
2615
+ Z(c[1]),
2616
+ Z(c[2])
2587
2617
  ];
2588
2618
  }
2589
- function Xt([e, t, n]) {
2619
+ function Qt([e, t, n]) {
2590
2620
  let r = (e) => Math.round(Math.max(0, Math.min(1, e)) * 255);
2591
2621
  return `rgb(${r(e)}, ${r(t)}, ${r(n)})`;
2592
2622
  }
2593
- function Zt(e) {
2594
- return qt(e.map((e) => {
2623
+ function $t(e) {
2624
+ return Jt(e.map((e) => {
2595
2625
  let t = Math.max(0, Math.min(1, e));
2596
2626
  return t <= .04045 ? t / 12.92 : ((t + .055) / 1.055) ** 2.4;
2597
2627
  }));
2598
2628
  }
2599
- function Qt(e, t = {}) {
2629
+ function en(e, t = {}) {
2600
2630
  let n = Math.max(2, Math.min(16, Math.round(t.grid ?? 8))), r = [], i = 0, a = [
2601
2631
  0,
2602
2632
  0,
@@ -2609,13 +2639,13 @@ function Qt(e, t = {}) {
2609
2639
  0
2610
2640
  ];
2611
2641
  for (let t = 0; t < n; t++) for (let s = 0; s < n; s++) {
2612
- let c = (s + .5) / n, l = (t + .5) / n, u = Yt(e, c, l);
2642
+ let c = (s + .5) / n, l = (t + .5) / n, u = Zt(e, c, l);
2613
2643
  r.push({
2614
2644
  x: c,
2615
2645
  y: l,
2616
2646
  color: u
2617
2647
  });
2618
- let d = Zt(u);
2648
+ let d = $t(u);
2619
2649
  i += d;
2620
2650
  let f = (l < .5 ? 0 : 2) + (c < .5 ? 0 : 1);
2621
2651
  a[f] += d, o[f] += 1;
@@ -2632,10 +2662,10 @@ function Qt(e, t = {}) {
2632
2662
  ];
2633
2663
  for (let e of r) l[0] += e.color[0], l[1] += e.color[1], l[2] += e.color[2];
2634
2664
  l[0] /= r.length, l[1] /= r.length, l[2] /= r.length;
2635
- let u = $t(r, n);
2665
+ let u = tn(r, n);
2636
2666
  if (u) return {
2637
2667
  backgroundImage: `url("${u}")`,
2638
- backgroundColor: Xt(l),
2668
+ backgroundColor: Qt(l),
2639
2669
  metrics: {
2640
2670
  mean: s,
2641
2671
  quadrants: c
@@ -2647,14 +2677,14 @@ function Qt(e, t = {}) {
2647
2677
  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)`;
2648
2678
  return `radial-gradient(circle ${d.toFixed(2)}% at ${t}% ${n}%, ${s} 0%, ${c} 100%)`;
2649
2679
  }).join(", "),
2650
- backgroundColor: Xt(l),
2680
+ backgroundColor: Qt(l),
2651
2681
  metrics: {
2652
2682
  mean: s,
2653
2683
  quadrants: c
2654
2684
  }
2655
2685
  };
2656
2686
  }
2657
- function $t(e, t) {
2687
+ function tn(e, t) {
2658
2688
  if (typeof document > "u") return null;
2659
2689
  let n;
2660
2690
  try {
@@ -2679,10 +2709,10 @@ function $t(e, t) {
2679
2709
  }
2680
2710
  //#endregion
2681
2711
  //#region src/components/aurora/Aurora.vue?vue&type=script&setup=true&lang.ts
2682
- var en = ["data-aurora-substrate"], tn = /*#__PURE__*/ e(/* @__PURE__ */ oe({
2712
+ var nn = ["data-aurora-substrate", "data-aurora-settled"], rn = /*#__PURE__*/ e(/* @__PURE__ */ re({
2683
2713
  __name: "Aurora",
2684
2714
  props: {
2685
- config: { default: () => V },
2715
+ config: { default: () => z },
2686
2716
  runtimeOptions: {},
2687
2717
  onInitError: {},
2688
2718
  renderMode: { default: "auto" },
@@ -2690,15 +2720,15 @@ var en = ["data-aurora-substrate"], tn = /*#__PURE__*/ e(/* @__PURE__ */ oe({
2690
2720
  },
2691
2721
  emits: ["rendererStatus"],
2692
2722
  setup(e, { expose: t, emit: n }) {
2693
- let r = n, i = e, a = I(i.renderMode, { forceWebGLUnderSoftwareRaster: i.runtimeOptions?.forceWebGLUnderSoftwareRaster ?? !1 }), o = O(() => Math.max(0, Math.min(1, i.opacityCeiling))), s = me(null), c = se(), l = c?.appContext.config.errorHandler, u = l ? (e) => l(e, c?.proxy ?? null, "Aurora initialization") : void 0, d = Gt(s, () => i.config, {
2723
+ let r = n, i = e, a = xe(i.renderMode, { forceWebGLUnderSoftwareRaster: i.runtimeOptions?.forceWebGLUnderSoftwareRaster ?? !1 }), o = k(() => Math.max(0, Math.min(1, i.opacityCeiling))), s = M(null), c = ie(), l = c?.appContext.config.errorHandler, u = l ? (e) => l(e, c?.proxy ?? null, "Aurora initialization") : void 0, d = Kt(s, () => i.config, {
2694
2724
  ...i.runtimeOptions ?? {},
2695
2725
  onInitError: i.onInitError ?? i.runtimeOptions?.onInitError ?? u
2696
2726
  }, { renderMode: a });
2697
- j(d.rendererStatus, (e) => r("rendererStatus", e), {
2727
+ P(d.rendererStatus, (e) => r("rendererStatus", e), {
2698
2728
  immediate: !0,
2699
2729
  flush: "sync"
2700
2730
  });
2701
- let f = O(() => Qt(i.config)), p = O(() => f.value.backgroundImage), m = O(() => f.value.backgroundColor);
2731
+ let f = k(() => en(i.config)), p = k(() => f.value.backgroundImage), m = k(() => f.value.backgroundColor);
2702
2732
  return t({
2703
2733
  config: i.config,
2704
2734
  canvasRef: s,
@@ -2709,29 +2739,31 @@ var en = ["data-aurora-substrate"], tn = /*#__PURE__*/ e(/* @__PURE__ */ oe({
2709
2739
  pause: d.pause,
2710
2740
  resume: d.resume,
2711
2741
  isArmed: d.isArmed,
2742
+ isSettled: d.isSettled,
2712
2743
  rendererStatus: d.rendererStatus
2713
- }), (e, t) => (fe(), ae("div", {
2744
+ }), (e, t) => (le(), ne("div", {
2714
2745
  class: "aurora-root block h-full w-full overflow-hidden",
2715
- "data-aurora-substrate": A(a),
2716
- style: le({ opacity: o.value })
2717
- }, [k("div", {
2746
+ "data-aurora-substrate": N(a),
2747
+ "data-aurora-settled": N(d).isSettled.value ? "" : void 0,
2748
+ style: oe({ opacity: o.value })
2749
+ }, [A("div", {
2718
2750
  class: "aurora-placeholder h-full min-h-0 w-full",
2719
2751
  "aria-hidden": "true",
2720
- style: le({
2752
+ style: oe({
2721
2753
  backgroundImage: p.value,
2722
2754
  backgroundColor: m.value
2723
2755
  })
2724
- }, null, 4), k("div", { class: ce(["aurora-canvas-layer h-full min-h-0 w-full", { "aurora-canvas-layer--armed": A(d).isArmed.value }]) }, [k("canvas", {
2756
+ }, null, 4), A("div", { class: ae(["aurora-canvas-layer h-full min-h-0 w-full", { "aurora-canvas-layer--armed": N(d).isArmed.value }]) }, [A("canvas", {
2725
2757
  ref_key: "canvasRef",
2726
2758
  ref: s,
2727
2759
  "aria-hidden": "true",
2728
- class: ce(["aurora-canvas block h-full w-full", { "aurora-canvas--armed": A(d).isArmed.value }])
2729
- }, null, 2)], 2)], 12, en));
2760
+ class: ae(["aurora-canvas block h-full w-full", { "aurora-canvas--armed": N(d).isArmed.value }])
2761
+ }, null, 2)], 2)], 12, nn));
2730
2762
  }
2731
- }), [["__scopeId", "data-v-aebed9bd"]]);
2763
+ }), [["__scopeId", "data-v-f054ede9"]]);
2732
2764
  //#endregion
2733
2765
  //#region src/components/aurora/composables/useCursorInteraction.ts
2734
- function nn(e, t, n) {
2766
+ function an(e, t, n) {
2735
2767
  let r = Ut(t), i = n.hitFraction ?? .35, a = null, o = null;
2736
2768
  function s(e, t) {
2737
2769
  let n = t.getBoundingClientRect();
@@ -2819,10 +2851,10 @@ function nn(e, t, n) {
2819
2851
  }, a = n.getBoundingClientRect(), o = l((i.clientX - a.left) / Math.max(a.width, 1), (i.clientY - a.top) / Math.max(a.height, 1));
2820
2852
  o !== null && r().nuclei.length > 1 && (r().nuclei.splice(o, 1), t.preventDefault());
2821
2853
  }
2822
- return de(() => {
2854
+ return ce(() => {
2823
2855
  let t = e.value;
2824
2856
  t && (t.addEventListener("pointermove", p), t.addEventListener("pointerleave", m), t.addEventListener("pointerdown", h), t.addEventListener("pointerup", g), t.addEventListener("pointercancel", g), t.addEventListener("contextmenu", _));
2825
- }), ue(() => {
2857
+ }), se(() => {
2826
2858
  let t = e.value;
2827
2859
  t && (t.removeEventListener("pointermove", p), t.removeEventListener("pointerleave", m), t.removeEventListener("pointerdown", h), t.removeEventListener("pointerup", g), t.removeEventListener("pointercancel", g), t.removeEventListener("contextmenu", _));
2828
2860
  }), { isDragging: () => a };
@@ -2853,7 +2885,7 @@ var $ = {
2853
2885
  calm: 16,
2854
2886
  vivid: 44
2855
2887
  }
2856
- }, rn = {
2888
+ }, on = {
2857
2889
  still: {
2858
2890
  nucleiDrift: 0,
2859
2891
  paletteDrift: 0,
@@ -2872,8 +2904,8 @@ var $ = {
2872
2904
  warpDrift: .008,
2873
2905
  breathDepth: .05
2874
2906
  }
2875
- }, an = .61803398875;
2876
- function on(e, t = "composed") {
2907
+ }, sn = .61803398875;
2908
+ function cn(e, t = "composed") {
2877
2909
  let n = Math.max(1, Math.min(8, Math.round(e))), r = [
2878
2910
  [.33, .33],
2879
2911
  [.67, .67],
@@ -2885,8 +2917,8 @@ function on(e, t = "composed") {
2885
2917
  [.67, .5]
2886
2918
  ], i = (e) => {
2887
2919
  if (t === "scattered") {
2888
- let t = e * an * Math.PI * 2, r = .12 + .3 * Math.sqrt((e + .5) / n);
2889
- return [g(.5 + r * Math.cos(t), .1, .9), g(.5 + r * Math.sin(t), .1, .9)];
2920
+ let t = e * sn * Math.PI * 2, r = .12 + .3 * Math.sqrt((e + .5) / n);
2921
+ return [m(.5 + r * Math.cos(t), .1, .9), m(.5 + r * Math.sin(t), .1, .9)];
2890
2922
  }
2891
2923
  if (t === "centred") {
2892
2924
  if (n === 1) return [.5, .5];
@@ -2908,8 +2940,8 @@ function on(e, t = "composed") {
2908
2940
  };
2909
2941
  });
2910
2942
  }
2911
- var sn = .03;
2912
- function cn(e, t, n) {
2943
+ var ln = .03;
2944
+ function un(e, t, n) {
2913
2945
  let r = e.length;
2914
2946
  if (r === 0 || t <= 0) return;
2915
2947
  let i = e.reduce((e, t) => e + t.C, 0) / r, a = Math.min(.22, i * 1.5), o = (e, t, n) => l({
@@ -2918,23 +2950,23 @@ function cn(e, t, n) {
2918
2950
  h: n
2919
2951
  }).C;
2920
2952
  if (e.forEach((e, n) => {
2921
- let r = n % 2 == 0 ? a : sn;
2922
- e.C = o(e.L, g(Q(e.C, r, t), 0, .4), e.h);
2953
+ let r = n % 2 == 0 ? a : ln;
2954
+ e.C = o(e.L, m(Q(e.C, r, t), 0, .4), e.h);
2923
2955
  }), !n) return;
2924
2956
  let s = 0;
2925
2957
  for (let t = 1; t < r; t++) e[t].L < e[s].L && (s = t);
2926
2958
  let c = e[s];
2927
- c.C = o(c.L, sn, c.h);
2959
+ c.C = o(c.L, ln, c.h);
2928
2960
  }
2929
- function ln(e) {
2961
+ function dn(e) {
2930
2962
  return e < .4 ? "fbm" : e < .75 ? "hybrid" : "cellular";
2931
2963
  }
2932
- function un(e, t) {
2933
- let n = g(t, 0, 1);
2934
- e.warpAmount = g(Q(.2, .6, n), 0, .6), e.warpScale = g(Q(1, 2.6, n), .5, 3), e.warpMode = ln(n), e.noiseOctaves = n < .5 ? 3 : n < .85 ? 4 : 5;
2964
+ function fn(e, t) {
2965
+ let n = m(t, 0, 1);
2966
+ e.warpAmount = m(Q(.2, .6, n), 0, .6), e.warpScale = m(Q(1, 2.6, n), .5, 3), e.warpMode = dn(n), e.noiseOctaves = n < .5 ? 3 : n < .85 ? 4 : 5;
2935
2967
  }
2936
- function dn(e, t, n) {
2937
- let r = g(n, 0, 1);
2968
+ function pn(e, t, n) {
2969
+ let r = m(n, 0, 1);
2938
2970
  switch (t) {
2939
2971
  case "watercolor":
2940
2972
  e.wetEdge = r, e.granulation = r * .8;
@@ -2957,37 +2989,37 @@ function dn(e, t, n) {
2957
2989
  break;
2958
2990
  }
2959
2991
  }
2960
- function fn(e, t) {
2992
+ function mn(e, t) {
2961
2993
  switch (t) {
2962
- case "watercolor": return g(e.wetEdge, 0, 1);
2994
+ case "watercolor": return m(e.wetEdge, 0, 1);
2963
2995
  case "oil":
2964
2996
  case "vangogh":
2965
2997
  case "pastel":
2966
2998
  case "oil-pastel":
2967
2999
  case "crayon":
2968
- case "kuwahara": return g(e.strokeAmount, 0, 1);
3000
+ case "kuwahara": return m(e.strokeAmount, 0, 1);
2969
3001
  case "metal":
2970
- case "metal-gradient": return g((e.metalPolish ?? 1) / 4, 0, 1);
3002
+ case "metal-gradient": return m((e.metalPolish ?? 1) / 4, 0, 1);
2971
3003
  }
2972
3004
  }
2973
- function pn(e) {
3005
+ function hn(e) {
2974
3006
  return e.nucleiDrift + e.paletteDrift + e.warpDrift > .001 ? "drifting" : e.breathDepth > .001 ? "breathing" : "still";
2975
3007
  }
2976
- function mn(e, t, n) {
2977
- return n === t ? 0 : g((e - t) / (n - t), 0, 1);
3008
+ function gn(e, t, n) {
3009
+ return n === t ? 0 : m((e - t) / (n - t), 0, 1);
2978
3010
  }
2979
3011
  //#endregion
2980
3012
  //#region src/components/aurora/composables/atoms.ts
2981
- var hn = {};
2982
- function gn(e = hn, t = V) {
3013
+ var _n = {};
3014
+ function vn(e = _n, t = z) {
2983
3015
  let n = {
2984
3016
  ...t,
2985
3017
  palette: t.palette.map((e) => ({ ...e })),
2986
3018
  nuclei: t.nuclei.map((e) => ({ ...e })),
2987
3019
  flow: { ...t.flow }
2988
- }, r = e.colorEnergy === void 0 ? void 0 : g(e.colorEnergy, 0, 1);
3020
+ }, r = e.colorEnergy === void 0 ? void 0 : m(e.colorEnergy, 0, 1);
2989
3021
  if (e.seed !== void 0) {
2990
- let t = e.hueSpread === void 0 ? r === void 0 ? void 0 : Q($.hueSpread.calm, $.hueSpread.vivid, r) : g(e.hueSpread, 0, 180);
3022
+ let t = e.hueSpread === void 0 ? r === void 0 ? void 0 : Q($.hueSpread.calm, $.hueSpread.vivid, r) : m(e.hueSpread, 0, 180);
2991
3023
  n.palette = Xe(e.seed, {
2992
3024
  harmony: e.harmony ?? "analogous",
2993
3025
  stopCount: 4,
@@ -2995,11 +3027,11 @@ function gn(e = hn, t = V) {
2995
3027
  ...t === void 0 ? {} : { hueSpread: t },
2996
3028
  ...e.lightnessScheme === void 0 ? {} : { scheme: e.lightnessScheme },
2997
3029
  ...e.lBand === void 0 ? {} : { lBand: e.lBand }
2998
- }), e.chromaVariance !== void 0 && e.chromaVariance > 0 && cn(n.palette, g(e.chromaVariance, 0, 1), e.chromaCounterpoint ?? !1);
3030
+ }), e.chromaVariance !== void 0 && e.chromaVariance > 0 && un(n.palette, m(e.chromaVariance, 0, 1), e.chromaCounterpoint ?? !1);
2999
3031
  }
3000
- if (r !== void 0 && (n.saturation = g(Q($.saturation.calm, $.saturation.vivid, r), .6, 1.3), n.valueVariance = g(Q($.valueVariance.calm, $.valueVariance.vivid, r), 0, .3), n.breathDepth = g(Q($.breathDepth.calm, $.breathDepth.vivid, r), 0, .15)), e.zones !== void 0 && (n.nuclei = on(e.zones.count, e.zones.arrangement ?? "composed")), e.noise !== void 0 && un(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 && dn(n, e.medium.kind, e.medium.amount)), e.motion !== void 0) {
3001
- let t = rn[e.motion];
3002
- n.nucleiDrift = g(t.nucleiDrift, 0, .05), n.paletteDrift = g(t.paletteDrift, 0, .04), n.warpDrift = g(t.warpDrift, 0, .015), n.breathDepth = g(e.motion === "still" ? 0 : Math.max(n.breathDepth, t.breathDepth), 0, .15);
3032
+ if (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 = cn(e.zones.count, e.zones.arrangement ?? "composed")), e.noise !== void 0 && fn(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 && pn(n, e.medium.kind, e.medium.amount)), e.motion !== void 0) {
3033
+ let t = on[e.motion];
3034
+ 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);
3003
3035
  }
3004
3036
  if (e.interactivity !== void 0) {
3005
3037
  let t = e.interactivity, r = e.medium?.kind !== void 0 && e.medium.kind !== "smooth" ? e.interactivity.light : void 0;
@@ -3007,12 +3039,12 @@ function gn(e = hn, t = V) {
3007
3039
  ...r === void 0 ? {} : { light: r },
3008
3040
  ...t.scroll === void 0 ? {} : { scroll: t.scroll },
3009
3041
  swirl: t.swirl ?? !0,
3010
- amplitude: t.amplitude === void 0 ? .5 : g(t.amplitude, 0, 1)
3042
+ amplitude: t.amplitude === void 0 ? .5 : m(t.amplitude, 0, 1)
3011
3043
  };
3012
3044
  }
3013
3045
  return n;
3014
3046
  }
3015
- function _n(e) {
3047
+ function yn(e) {
3016
3048
  let t = e.medium, n = e.interactivity, r = n && (n.scroll !== void 0 || n.swirl !== void 0 || n.amplitude !== void 0) ? {
3017
3049
  ...n.scroll === void 0 ? {} : { scroll: n.scroll },
3018
3050
  ...n.swirl === void 0 ? {} : { swirl: n.swirl },
@@ -3020,13 +3052,13 @@ function _n(e) {
3020
3052
  } : void 0, i = {
3021
3053
  seed: e.palette.length > 0 ? u(e.palette[0]) : "#3a7bd5",
3022
3054
  harmony: "analogous",
3023
- colorEnergy: mn(e.saturation, $.saturation.calm, $.saturation.vivid),
3055
+ colorEnergy: gn(e.saturation, $.saturation.calm, $.saturation.vivid),
3024
3056
  zones: {
3025
3057
  count: Math.max(1, Math.min(8, e.nuclei.length)),
3026
3058
  arrangement: "composed"
3027
3059
  },
3028
- noise: mn(e.warpAmount, .2, .6),
3029
- motion: pn(e)
3060
+ noise: gn(e.warpAmount, .2, .6),
3061
+ motion: hn(e)
3030
3062
  };
3031
3063
  return t === "smooth" ? {
3032
3064
  ...i,
@@ -3036,7 +3068,7 @@ function _n(e) {
3036
3068
  ...i,
3037
3069
  medium: {
3038
3070
  kind: t,
3039
- amount: fn(e, t)
3071
+ amount: mn(e, t)
3040
3072
  },
3041
3073
  ...r || n?.light !== void 0 ? { interactivity: {
3042
3074
  ...r,
@@ -3045,4 +3077,4 @@ function _n(e) {
3045
3077
  };
3046
3078
  }
3047
3079
  //#endregion
3048
- export { tn as Aurora, hn as DEFAULT_ATOMS, V as DEFAULT_AURORA_CONFIG, Be as DEFAULT_VIVIDNESS, Ue as IMAGE_BLUR_MAX_DEFAULT, He as IMAGE_BLUR_MIN_DEFAULT, Re as MAX_NUCLEI, ze as MAX_STOPS, We as PAPER_WASH_GROUND, Ve as VIVID_TARGET, Qt as auroraFallbackGround, _n as configToAtoms, Ht as createAurora, s as cssToOklch, Xe as deriveAurora, H as flattenPalette, Ke as hexToOklchStop, F as isSoftwareWebGLRenderer, Z as nucleiFieldStatic, on as nucleiPrior, u as oklchStopToHex, d as oklchToLinear, Ge as paletteToCssGradient, qt as relativeLuminance, gn as resolveAtoms, I as resolveRenderMode, Yt as sampleAuroraField, Gt as useAurora, nn as useCursorInteraction };
3080
+ export { Wt as AURORA_SETTLE_MS, rn as Aurora, _n as DEFAULT_ATOMS, z as DEFAULT_AURORA_CONFIG, Re as DEFAULT_VIVIDNESS, Ve as IMAGE_BLUR_MAX_DEFAULT, Be as IMAGE_BLUR_MIN_DEFAULT, Ie as MAX_NUCLEI, Le as MAX_STOPS, He as PAPER_WASH_GROUND, ze as VIVID_TARGET, en as auroraFallbackGround, yn as configToAtoms, Ht as createAurora, s as cssToOklch, Xe as deriveAurora, U as flattenPalette, Ke as hexToOklchStop, F as isSoftwareWebGLRenderer, Xt as nucleiFieldStatic, cn as nucleiPrior, u as oklchStopToHex, d as oklchToLinear, Ge as paletteToCssGradient, Jt as relativeLuminance, vn as resolveAtoms, xe as resolveRenderMode, Zt as sampleAuroraField, Kt as useAurora, an as useCursorInteraction };