@mkbabb/glass-ui 3.7.0 → 3.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (261) hide show
  1. package/README.md +1 -6
  2. package/dist/CardAction-DuArVky-.js +157 -0
  3. package/dist/{CommandShortcut-CY0aQTz5.js → CommandShortcut-CefCj5mt.js} +7 -7
  4. package/dist/{ContextMenuSubContent-BeDz7G6f.js → ContextMenuSubContent-Bhg4f5jX.js} +3 -3
  5. package/dist/{DataTable-gtPOLZvl.js → DataTable-CUHMukiQ.js} +4 -4
  6. package/dist/DeckProgress-Dmk4_AKN.js +23 -0
  7. package/dist/{DialogContent-DG1YMR-P.js → DialogContent-BRkT1cjP.js} +3 -3
  8. package/dist/{DialogFooter-BCIaDP15.js → DialogFooter-Gprw8H_A.js} +1 -1
  9. package/dist/{HoverPopover-DXpOEw1P.js → HoverPopover-2LOIW34a.js} +1 -1
  10. package/dist/{IconTooltip-Cu86JR4e.js → IconTooltip-yefK7cJc.js} +1 -1
  11. package/dist/{MetricBadge-D-bRaQUD.js → MetricBadge-lTc002u2.js} +1 -1
  12. package/dist/{Notification-B5WHP3_1.js → Notification-BxK8LZMw.js} +1 -1
  13. package/dist/{NumberFieldContent-CLNVR1iY.js → NumberFieldContent-BbLOh8RU.js} +2 -2
  14. package/dist/{PopoverContent-F1bWnYoh.js → PopoverContent-BId6ggf_.js} +1 -1
  15. package/dist/{Progress-c4vo4ALi.js → Progress-iEuD9pxG.js} +4 -4
  16. package/dist/{ScrollingText-BEp377U5.js → ScrollingText-BPP84wKo.js} +2 -2
  17. package/dist/{SelectScrollDownButton-DAo5NmY_.js → SelectScrollDownButton-T32cVFeM.js} +4 -4
  18. package/dist/{SelectSeparator-COFMJGng.js → SelectSeparator-C4Ql0gzV.js} +1 -1
  19. package/dist/{TabsList-COqIdW7i.js → TabsList-B8CVOQPC.js} +2 -2
  20. package/dist/{Toaster-BMaeziJJ.js → Toaster-1rYWrmAm.js} +1 -1
  21. package/dist/{ToggleGroupItem-C-AIkBUw.js → ToggleGroupItem-Cev4pOVK.js} +5 -5
  22. package/dist/animated-digit.js +1 -1
  23. package/dist/api/index.d.ts +5 -3
  24. package/dist/aurora.js +339 -408
  25. package/dist/{badge-DHQrc_GQ.js → badge-BvwvT50i.js} +4 -4
  26. package/dist/badge.js +1 -1
  27. package/dist/{button-CAywcrhO.js → button-DN39cGqL.js} +13 -12
  28. package/dist/button.js +1 -1
  29. package/dist/canvas.d.ts +1 -0
  30. package/dist/canvas.js +3 -0
  31. package/dist/card.js +1 -1
  32. package/dist/carousel.js +106 -105
  33. package/dist/collapsible.js +1 -1
  34. package/dist/{color-DjJCAQ6f.js → color-8jBjWiJ6.js} +22 -14
  35. package/dist/color.js +2 -2
  36. package/dist/command.js +1 -1
  37. package/dist/compile-DAjIoEy2.js +158 -0
  38. package/dist/components/custom/aurora/composables/atoms.d.ts +90 -25
  39. package/dist/components/custom/aurora/composables/color.d.ts +0 -12
  40. package/dist/components/custom/aurora/composables/uniformBridge.d.ts +34 -23
  41. package/dist/components/custom/aurora/constants/presets.d.ts +10 -7
  42. package/dist/components/custom/aurora/constants/renderMode.d.ts +22 -0
  43. package/dist/components/custom/aurora/constants/shaders/aurora.wgsl.d.ts +1 -1
  44. package/dist/components/custom/aurora/constants/shaders/brush.glsl.d.ts +1 -1
  45. package/dist/components/custom/aurora/constants/shaders/composition.glsl.d.ts +1 -1
  46. package/dist/components/custom/aurora/constants/shaders/mediums.glsl.d.ts +1 -1
  47. package/dist/components/custom/aurora/index.d.ts +3 -3
  48. package/dist/components/custom/configurator/ConfiguratorLayer.vue.d.ts +1 -1
  49. package/dist/components/custom/constellation/Constellation.vue.d.ts +40 -5
  50. package/dist/components/custom/constellation/constellationField.d.ts +110 -5
  51. package/dist/components/custom/constellation/index.d.ts +2 -2
  52. package/dist/components/custom/dock/DockSeparator.vue.d.ts +3 -0
  53. package/dist/components/custom/dock/GlassDock.vue.d.ts +57 -67
  54. package/dist/components/custom/dock/composables/dockContext.d.ts +7 -0
  55. package/dist/components/custom/dock/composables/dockMorphContext.d.ts +57 -0
  56. package/dist/components/custom/dock/composables/index.d.ts +3 -0
  57. package/dist/components/custom/dock/composables/useDockHold.d.ts +61 -0
  58. package/dist/components/custom/dock/composables/useLayerTransition.d.ts +13 -59
  59. package/dist/components/custom/dock/index.d.ts +4 -1
  60. package/dist/components/custom/goo-blob/GooBlob.vue.d.ts +13 -0
  61. package/dist/components/custom/goo-blob/composables/useBlobMood.d.ts +23 -5
  62. package/dist/components/custom/goo-blob/composables/useBlobPointer.d.ts +2 -0
  63. package/dist/components/custom/goo-blob/composables/useBlobSatellites.d.ts +2 -0
  64. package/dist/components/custom/goo-blob/composables/useMetaballRenderer.d.ts +20 -5
  65. package/dist/components/custom/goo-blob/index.d.ts +1 -1
  66. package/dist/components/custom/goo-blob/shaders/sdf-body.glsl.d.ts +1 -1
  67. package/dist/components/custom/goo-blob/shaders/watercolor-edges.glsl.d.ts +2 -2
  68. package/dist/components/custom/goo-blob/types.d.ts +8 -0
  69. package/dist/components/custom/tabs/SegmentedTabs.vue.d.ts +92 -0
  70. package/dist/components/custom/tabs/composables/useTabIndicator.d.ts +49 -0
  71. package/dist/components/custom/tabs/index.d.ts +2 -5
  72. package/dist/components/ui/button/index.d.ts +1 -1
  73. package/dist/components/ui/card/Card.vue.d.ts +28 -10
  74. package/dist/components/ui/card/index.d.ts +1 -1
  75. package/dist/components/ui/carousel/CarouselNext.vue.d.ts +1 -1
  76. package/dist/components/ui/carousel/CarouselPrevious.vue.d.ts +1 -1
  77. package/dist/components/ui/carousel/GlassCarouselPager.vue.d.ts +3 -4
  78. package/dist/components/ui/progress/ProgressDefault.vue.d.ts +14 -4
  79. package/dist/components/ui/slider/Slider.vue.d.ts +3 -1
  80. package/dist/components/ui/slider/index.d.ts +14 -7
  81. package/dist/components/ui/tabs/TabsList.vue.d.ts +1 -1
  82. package/dist/composables/color/index.d.ts +19 -0
  83. package/dist/composables/context/createContext.d.ts +1 -1
  84. package/dist/composables/dom/index.d.ts +1 -1
  85. package/dist/composables/dom/useResolveTokenColor.d.ts +27 -0
  86. package/dist/composables/glass/canvas2d/index.d.ts +2 -1
  87. package/dist/composables/glass/canvas2d/resolveCanvasColor.d.ts +20 -0
  88. package/dist/composables/glass/canvas2d/useCanvas2D.d.ts +15 -1
  89. package/dist/composables/glass/index.d.ts +3 -1
  90. package/dist/composables/glass/useSpecularTracking.d.ts +33 -0
  91. package/dist/composables/glass/webgl/createCanvasLifecycle.d.ts +1 -1
  92. package/dist/composables/glass/webgl/shaders/procedural-color.glsl.d.ts +4 -0
  93. package/dist/composables/glass/webgl/useWebGLCanvas.d.ts +1 -1
  94. package/dist/composables/motion/core/index.d.ts +1 -0
  95. package/dist/configurator.js +1 -1
  96. package/dist/confirm-dialog.js +2 -2
  97. package/dist/constellation.js +143 -74
  98. package/dist/context-menu.js +2 -2
  99. package/dist/controls.js +1 -1
  100. package/dist/data-table.js +1 -1
  101. package/dist/deck-progress.d.ts +1 -0
  102. package/dist/deck-progress.js +2 -0
  103. package/dist/dialog.js +2 -2
  104. package/dist/dock.js +468 -354
  105. package/dist/{dockContext-DLgrVWsU.js → dockContext-BKeIMmNk.js} +1 -1
  106. package/dist/dom.js +6 -6
  107. package/dist/{dropdown-menu-BWpwutt0.js → dropdown-menu-D77jX6Df.js} +4 -4
  108. package/dist/dropdown-menu.js +1 -1
  109. package/dist/fonts/README.md +26 -18
  110. package/dist/forms.js +5 -5
  111. package/dist/fourier-field.js +35 -35
  112. package/dist/glass-panel.js +7 -7
  113. package/dist/glass-ui.css +1 -1
  114. package/dist/glass-ui.js +154 -169
  115. package/dist/goo-blob.js +361 -274
  116. package/dist/header-ribbon.js +1 -1
  117. package/dist/hover-card.js +1 -1
  118. package/dist/hover-popover.js +1 -1
  119. package/dist/icon-tooltip.js +1 -1
  120. package/dist/index.d.ts +1 -2
  121. package/dist/instrument-chassis.js +1 -1
  122. package/dist/instrument-rail.js +1 -1
  123. package/dist/label.js +1 -1
  124. package/dist/labeled-field.js +5 -5
  125. package/dist/{menuItemVariants-B4eFR5Ix.js → menuItemVariants-CCoBNkU5.js} +1 -1
  126. package/dist/metric-badge.js +1 -1
  127. package/dist/metric-stack.js +1 -1
  128. package/dist/motion-core.js +65 -65
  129. package/dist/motion.js +1 -1
  130. package/dist/notification.js +1 -1
  131. package/dist/number-field.js +1 -1
  132. package/dist/paper-backdrop.js +1 -1
  133. package/dist/popover.js +1 -1
  134. package/dist/progress.js +1 -1
  135. package/dist/pulse.js +2 -2
  136. package/dist/reactive.js +2 -2
  137. package/dist/resolveCanvasColor-BBiYfp8g.js +11 -0
  138. package/dist/scrolling-text.js +1 -1
  139. package/dist/search.js +9 -9
  140. package/dist/select.js +3 -3
  141. package/dist/separator.js +1 -1
  142. package/dist/{sheet-Cv1uO-Gq.js → sheet-VZEtNFvO.js} +2 -2
  143. package/dist/sheet.js +1 -1
  144. package/dist/slider-DC2mwuoe.js +142 -0
  145. package/dist/slider.js +1 -1
  146. package/dist/sortable-list.js +2 -2
  147. package/dist/status-dot.js +17 -16
  148. package/dist/styles/animations.css +6 -2
  149. package/dist/styles/components.css +1 -1
  150. package/dist/styles/dock/density.css +230 -0
  151. package/dist/styles/dock/layer-group.css +263 -0
  152. package/dist/styles/dock/layers.css +355 -0
  153. package/dist/styles/dock/morph.css +244 -0
  154. package/dist/styles/dock/overflow.css +201 -0
  155. package/dist/styles/dock/shell.css +457 -0
  156. package/dist/styles/dock-controls.css +187 -50
  157. package/dist/styles/dock.css +65 -1181
  158. package/dist/styles/fonts.css +0 -25
  159. package/dist/styles/glass.css +482 -102
  160. package/dist/styles/index.css +12 -11
  161. package/dist/styles/theme.css +60 -2
  162. package/dist/styles/tokens.css +625 -72
  163. package/dist/styles/typography.css +52 -69
  164. package/dist/styles/utilities.css +91 -40
  165. package/dist/styles/view-transition.css +12 -40
  166. package/dist/switch.js +1 -1
  167. package/dist/tabs.js +183 -152
  168. package/dist/timeline.js +3 -3
  169. package/dist/toast.js +1 -1
  170. package/dist/toggle-group.js +1 -1
  171. package/dist/tooltip.js +1 -1
  172. package/dist/typewriter.js +1 -1
  173. package/dist/{useCanvas2D-C31yfUGY.js → useCanvas2D-ClrQuZtp.js} +2 -1
  174. package/dist/{useConfiguratorState-B24TcgIn.js → useConfiguratorState-DxAqksK6.js} +24 -13
  175. package/dist/useResolveTokenColor-r9pnNjqK.js +24 -0
  176. package/dist/useSpecularTracking-BVRZsRHA.js +26 -0
  177. package/dist/{useTextHighlight-aMPdEpkq.js → useTextHighlight-DON84L8w.js} +1 -1
  178. package/dist/{useTouchGate-YQRwyUGU.js → useTouchGate-WFyJYsth.js} +1 -1
  179. package/dist/vReveal-CqaCz_mC.js +44 -0
  180. package/dist/watercolor-dot.js +1 -1
  181. package/package.json +67 -36
  182. package/src/fonts/README.md +26 -18
  183. package/src/styles/animations.css +6 -2
  184. package/src/styles/dock/density.css +230 -0
  185. package/src/styles/dock/layer-group.css +263 -0
  186. package/src/styles/dock/layers.css +355 -0
  187. package/src/styles/dock/morph.css +244 -0
  188. package/src/styles/dock/overflow.css +201 -0
  189. package/src/styles/dock/shell.css +457 -0
  190. package/src/styles/dock-controls.css +187 -50
  191. package/src/styles/dock.css +65 -1181
  192. package/src/styles/fonts.css +0 -25
  193. package/src/styles/glass.css +482 -102
  194. package/src/styles/index.css +12 -11
  195. package/src/styles/theme.css +60 -2
  196. package/src/styles/tokens.css +625 -72
  197. package/src/styles/typography.css +52 -69
  198. package/src/styles/utilities.css +91 -40
  199. package/src/styles/view-transition.css +12 -40
  200. package/dist/CardAction-D8nSE62a.js +0 -163
  201. package/dist/DiscoGlyph-ZoBYpoxI.js +0 -132
  202. package/dist/GlyphFace-Ce_oe3tM.js +0 -51
  203. package/dist/UnderlineTabs-D_c1iYo-.js +0 -39
  204. package/dist/compile-iM4IUB66.js +0 -158
  205. package/dist/components/custom/aurora/constants/shaders/painterly.wgsl.d.ts +0 -7
  206. package/dist/components/custom/aurora/constants/shaders/wake.wgsl.d.ts +0 -4
  207. package/dist/components/custom/disco-glyph/DiscoGlyph.vue.d.ts +0 -58
  208. package/dist/components/custom/disco-glyph/index.d.ts +0 -1
  209. package/dist/components/custom/glass-carousel/GlassCarousel.vue.d.ts +0 -36
  210. package/dist/components/custom/glass-carousel/GlassCarouselItem.vue.d.ts +0 -28
  211. package/dist/components/custom/glass-carousel/index.d.ts +0 -4
  212. package/dist/components/custom/glass-carousel/useGlassCarousel.d.ts +0 -30
  213. package/dist/components/custom/glyph-face/GlyphFace.vue.d.ts +0 -64
  214. package/dist/components/custom/glyph-face/index.d.ts +0 -2
  215. package/dist/components/custom/glyph-face/keys.d.ts +0 -9
  216. package/dist/components/custom/responsive-tabs/ResponsiveTabs.vue.d.ts +0 -73
  217. package/dist/components/custom/responsive-tabs/index.d.ts +0 -2
  218. package/dist/components/custom/tabs/BouncyTabs.vue.d.ts +0 -27
  219. package/dist/components/custom/tabs/BouncyToggle.vue.d.ts +0 -60
  220. package/dist/components/custom/tabs/UnderlineTabs.vue.d.ts +0 -20
  221. package/dist/components/custom/tabs/composables/useBouncySlider.d.ts +0 -39
  222. package/dist/disco-glyph.d.ts +0 -1
  223. package/dist/disco-glyph.js +0 -2
  224. package/dist/fonts/fraunces/fraunces-latin.woff2 +0 -0
  225. package/dist/glass-carousel.d.ts +0 -1
  226. package/dist/glass-carousel.js +0 -214
  227. package/dist/glyph-face.d.ts +0 -1
  228. package/dist/glyph-face.js +0 -3
  229. package/dist/keys-Dyf_uTs5.js +0 -9
  230. package/dist/responsive-tabs.d.ts +0 -1
  231. package/dist/responsive-tabs.js +0 -95
  232. package/dist/slider-BA9wIs5e.js +0 -130
  233. package/dist/styles/disco-glyph.css +0 -26
  234. package/dist/styles/glyph-face.css +0 -92
  235. package/dist/useViewTransition-DaN5Zrox.js +0 -21
  236. package/dist/vReveal-BE2TsPoR.js +0 -24
  237. package/src/fonts/fraunces/fraunces-latin.woff2 +0 -0
  238. package/src/styles/disco-glyph.css +0 -26
  239. package/src/styles/glyph-face.css +0 -92
  240. /package/dist/{CollapsibleContent-D7UNsXQr.js → CollapsibleContent-CP9qF1v4.js} +0 -0
  241. /package/dist/{ContextMenuContent-DGKZX1Du.js → ContextMenuContent-DDa0v27s.js} +0 -0
  242. /package/dist/{HoverCardContent-Bn0UBMY9.js → HoverCardContent-B6CcQ8He.js} +0 -0
  243. /package/dist/{InstrumentChassis-BLJBl1BW.js → InstrumentChassis-C2f6cBaQ.js} +0 -0
  244. /package/dist/{InstrumentRail-BElFwKyt.js → InstrumentRail-CUJ05YVG.js} +0 -0
  245. /package/dist/{Label-DBc7bKzZ.js → Label-zRfbQdCn.js} +0 -0
  246. /package/dist/{ModalOverlay-D1VrWyoi.js → ModalOverlay-B1GkAjNb.js} +0 -0
  247. /package/dist/{PaperBackdrop-DCGoGatv.js → PaperBackdrop-CqwjnfLv.js} +0 -0
  248. /package/dist/{SelectGroup-BcaFjvP4.js → SelectGroup-BFiod7X2.js} +0 -0
  249. /package/dist/{Separator-DZyqsCum.js → Separator-D7sD7RKj.js} +0 -0
  250. /package/dist/{Switch-2rsAtDfA.js → Switch-LDKLmmgx.js} +0 -0
  251. /package/dist/{TooltipProvider-CD2IXfRv.js → TooltipProvider-CmZzCuaG.js} +0 -0
  252. /package/dist/{_plugin-vue_export-helper-gLzyemvl.js → _plugin-vue_export-helper-ok8pgSl5.js} +0 -0
  253. /package/dist/composables/{dom → motion}/useTextHighlight.d.ts +0 -0
  254. /package/dist/{createContext-CvNyBk4o.js → createContext-B44Sah_H.js} +0 -0
  255. /package/dist/{useGlassRenderer-HebgSEgN.js → useGlassRenderer-5I0YkI4t.js} +0 -0
  256. /package/dist/{useInterval-D3USn0yc.js → useInterval-Dsmqwi03.js} +0 -0
  257. /package/dist/{useResizeObserver-CSyxGxIX.js → useResizeObserver-CWCbk5Px.js} +0 -0
  258. /package/dist/{useSpringMount-LRZqJTaF.js → useSpringMount-BJSHcLl3.js} +0 -0
  259. /package/dist/{useTimer-DEJonOZa.js → useTimer-3xqf_54b.js} +0 -0
  260. /package/dist/{useUserInvalidAria-CQKjEfwP.js → useUserInvalidAria-DoJKMZ3s.js} +0 -0
  261. /package/dist/{useViewportReady-K96m-Mdv.js → useViewportReady-RIr5Wgkh.js} +0 -0
@@ -1 +1 @@
1
- export declare const AURORA_BRUSH_GLSL = "// \u2500\u2500 Curved swept brushstroke primitive \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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\u00B7K\u00B7along0\u00B7(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 \u2014 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 \u2014 thin-fat-thin (classic brush)\n// 1 load-drag \u2014 fat start, tapers to a point\n// 2 dab \u2014 ellipse-like, round center\n// 3 even \u2014 near-constant, slight end taper\nfloat strokeShape(float t, int type) {\n t = clamp(t, 0.0, 1.0);\n if (type == 1) {\n // loaded at t=0, tapering\n return pow(1.0 - t, 0.55) * smoothstep(0.0, 0.08, t);\n } else if (type == 2) {\n // dab / blob\n float d = t - 0.5;\n return exp(-d * d * 12.0);\n } else if (type == 3) {\n // mostly even, slight end softening\n return smoothstep(0.0, 0.08, t) * smoothstep(1.0, 0.92, t) * 0.95 + 0.05;\n }\n // 0 tapered\n return smoothstep(0.0, 0.22, t) * smoothstep(1.0, 0.78, t);\n}\n\n// A single curved stroke.\n// a, b \u2014 endpoints\n// halfW \u2014 base half-width (world units)\n// bulge \u2014 signed perpendicular midpoint offset, in world units\n// shapeType \u2014 0..3\n// bristleFreq \u2014 spatial frequency of edge raggedness\n// bristleAmp \u2014 0..0.5 fraction of halfW chewed away at edge extrema\n// streakSeed \u2014 uniqueness seed for internal streaks\n// colAtMid \u2014 pre-sampled color at midpoint\nStrokeHit curvedStroke(vec2 p, vec2 a, vec2 b, float halfW,\n float bulge, int shapeType,\n float bristleFreq, float bristleAmp,\n float streakSeed, vec3 colAtMid) {\n vec2 ab = b - a;\n float L = length(ab);\n if (L < 1e-5) return noHit();\n vec2 tang = ab / L;\n vec2 norm = vec2(-tang.y, tang.x);\n\n vec2 rel = p - a;\n float along0 = dot(rel, tang) / L; // straight projection 0..1 along AB\n float cross0 = dot(rel, norm); // signed cross\n\n // Spine sideways offset due to bulge (parabolic)\n float bend = 4.0 * bulge * along0 * (1.0 - along0);\n float cross1 = cross0 - bend;\n\n // One refinement step: as the spine bends, the closest-point along shifts.\n // Spine tangent differs from straight tangent; first-order correction:\n float dBend = 4.0 * bulge * (1.0 - 2.0 * along0); // d(bend)/d(along)\n float along1 = along0 + (cross1 * dBend) / (L * (1.0 + dBend * dBend / (L * L)));\n\n // Bristle-ragged edge: half-width gets chewed by 1D noise along the spine\n float edgeNoise = fbm(vec2(along1 * bristleFreq, streakSeed * 7.3)) - 0.5;\n float edgeNoise2 = fbm(vec2(along1 * bristleFreq * 2.3, streakSeed * 3.1 + 17.0)) - 0.5;\n float edgeMod = 1.0 - bristleAmp * (0.6 + 0.4 * edgeNoise) * (0.5 + edgeNoise2);\n\n // Width profile along the stroke\n float shape = strokeShape(along1, shapeType);\n float halfWNow = halfW * shape * edgeMod;\n\n // Inside-segment coverage\n float cov = 0.0;\n if (along1 >= 0.0 && along1 <= 1.0 && halfWNow > 1e-6) {\n float cn = abs(cross1) / halfWNow;\n // fwidth-derived AA: half-width from the field's screen-space gradient so the\n // edge holds ~1px regardless of zoom/DPR (mirrors metaball.frag.ts:252-255).\n float aaC = max(fwidth(cn), 1e-6);\n cov = 1.0 - smoothstep(1.0 - aaC, 1.0 + aaC, cn);\n }\n\n // End-cap blobs (rounded tips, not perpendicular cuts)\n float capA = 0.0, capB = 0.0;\n {\n vec2 capCenterA = a + norm * (bulge * 0.0); // at a\n vec2 capCenterB = a + tang * L + norm * bend * 0.0 + norm * 0.0; // at b; bend is 0 at endpoints\n capCenterB = b;\n float endShapeA = strokeShape(0.02, shapeType);\n float endShapeB = strokeShape(0.98, shapeType);\n float rA = halfW * endShapeA * (0.9 + 0.2 * fbm(vec2(streakSeed * 13.0)));\n float rB = halfW * endShapeB * (0.9 + 0.2 * fbm(vec2(streakSeed * 19.0 + 5.0)));\n float dA = length(p - capCenterA);\n float dB = length(p - capCenterB);\n // fwidth-derived AA on the clean Euclidean cap distances (mirrors metaball.frag.ts:252-255).\n float aaA = max(fwidth(dA), 1e-6);\n float aaB = max(fwidth(dB), 1e-6);\n capA = 1.0 - smoothstep(rA - aaA, rA + aaA, dA);\n capB = 1.0 - smoothstep(rB - aaB, rB + aaB, dB);\n // Only apply caps if we're BEYOND the segment; otherwise the segment wins.\n capA *= (along1 < 0.05) ? 1.0 : 0.0;\n capB *= (along1 > 0.95) ? 1.0 : 0.0;\n }\n\n float coverage = max(cov, max(capA, capB));\n if (coverage < 0.002) return noHit();\n\n // Edge distance (for impasto later): 0 at edge, 1 at spine\n float edgeDist = halfWNow > 1e-6 ? clamp(1.0 - abs(cross1) / halfWNow, 0.0, 1.0) : 0.0;\n\n return StrokeHit(\n coverage,\n colAtMid,\n clamp(along1, 0.0, 1.0),\n halfWNow > 1e-6 ? cross1 / halfWNow : 0.0,\n edgeDist\n );\n}\n\n// Composite stroke over col with internal streaking + ACCUMULATE paint HEIGHT.\n// streakAmp \u2014 0..0.2 how much internal streaks darken/lighten\n// impastoAmp \u2014 0..1 paint-thickness contribution (the height the relight reads)\n// hardness \u2014 0..1 how crisp the compositing transition is (1 = razor, 0 = creamy)\n//\n// AW.W4.2 \u2014 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().\nvoid paintOver(inout vec3 col, inout float height, StrokeHit s,\n float streakFreq, float streakAmp,\n float impastoAmp, float hardness, float streakSeed) {\n if (s.coverage < 0.002) return;\n float strokeOpacity = clamp(uStrokeAmount, 0.0, 1.0);\n if (strokeOpacity <= 0.001) return;\n vec3 c = s.color;\n\n // Internal streaks \u2014 fbm along spine, modulated by cross position. Gives\n // the loaded-brush look: some bristles carry more pigment than others.\n float streakA = fbm(vec2(s.alongT * streakFreq, s.crossN * 2.7 + streakSeed));\n float streakB = fbm(vec2(s.alongT * streakFreq * 0.6 + streakSeed * 3.7, s.crossN * 4.1));\n float streak = 0.6 * (streakA - 0.5) + 0.4 * (streakB - 0.5);\n c *= 1.0 + streak * streakAmp * 2.0;\n\n // Subtle value variance across width (hollow-center catch-light)\n float crossShade = smoothstep(0.0, 0.4, s.edgeN) * (1.0 - smoothstep(0.65, 1.0, s.edgeN));\n c *= 1.0 + crossShade * 0.05;\n\n float softLimit = mix(0.35, 0.98, hardness);\n float alpha = smoothstep(0.0, 1.0 - softLimit, s.coverage) * strokeOpacity;\n col = mix(col, c, alpha);\n\n // \u2500\u2500 Accumulate paint HEIGHT (AW.W4.2). Thickness peaks at the stroke spine and\n // falls to zero at the edge (edgeN: 0=edge, 1=spine), perturbed by the bristle\n // streak so ridges/grooves form. The relight reads the gradient of this field.\n float ridge = 0.7 + 0.3 * (streak + 0.5);\n height += s.coverage * impastoAmp * (0.4 + 0.6 * s.edgeN) * ridge * alpha;\n}\n\n// AW.W4.2 \u2014 relight the accumulated paint height. Derive a normal from the height\n// gradient (dFdx/dFdy \u2014 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 \u2192 high shininess (sharp glint); thin paint \u2192 broad soft sheen.\n float shininess = mix(8.0, 64.0, clamp(height * 2.0, 0.0, 1.0));\n float spec = pow(max(dot(N, H), 0.0), shininess);\n // Modulate the lit terms by how much paint sits here (no relief on bare ground).\n float relief = clamp(h * 1.5, 0.0, 1.0);\n vec3 lit = col * (1.0 + uImpasto * relief * (diff - 0.5) * 0.5 * uLightColor);\n lit += uImpasto * relief * spec * 0.6 * uLightColor;\n return lit;\n}\n\n// Best-of-9-neighbor placement: sample 3x3 surrounding cells and take the\n// stroke that covers this pixel most. Breaks the grid by per-cell jitter and\n// sparse density via noise thresholding.\nStrokeHit bestOil(vec2 p, float cellSize, float lenMul, float halfWMul,\n float jitterAmt, float density, int shapeType,\n float bristleAmp, vec2 flow, float t, float seed) {\n vec2 cell = floor(p / cellSize);\n\n StrokeHit best = noHit();\n // 3x3 neighborhood\n for (int dy = -1; dy <= 1; dy++) {\n for (int dx = -1; dx <= 1; dx++) {\n vec2 cc = cell + vec2(float(dx), float(dy));\n vec2 hh = hash22(cc + seed) - 0.5;\n // Density gate \u2014 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 \u2014 the AW.W4.1 strokeOrient switch:\n // uStrokeOrient==0 (flow) \u2014 the layer-provided hand-authored flowField.\n // uStrokeOrient==1 (tensor) \u2014 the structure-tensor minor eigenvector at the\n // cell center (the color field's edge-tangent),\n // so strokes hug the color zones (van Gogh).\n // coh carries the per-cell coherence A for the W4.3 energy grading (long\n // confident strokes where structure is coherent, stubby dabs in flat zones).\n vec2 f;\n float coh = 1.0;\n if (uStrokeOrient == 1) {\n vec3 tf = structureTensorField(center, t, safeDir(flow));\n f = safeDir(tf.xy);\n coh = tf.z;\n } else {\n f = safeDir(flow);\n }\n // Per-stroke deterministic local perturbation so alternate stroke layers stay\n // live. Tensor strokes get LESS angular jitter (the field already orients them).\n float jScale = (uStrokeOrient == 1) ? 0.4 : 1.0;\n float angJ = (hash21(cc + seed + 11.0) - 0.5) * 0.9 * jScale; // +/- 0.45/0.18 rad\n float localCurl = (fbm(center * (2.6 + seed * 0.11) + seed * 1.9) - 0.5) * 0.55 * uFlowCurl;\n vec2 dir = rotateDir(f, angJ + localCurl);\n\n // AW.W4.3 \u2014 energy-graded length/density for the van-Gogh cascade: in vangogh\n // mode (uMedium==5) modulate the stroke length by local luminance (bright \u2192\n // long confident strokes, dark \u2192 short dabs, the Starry-Night Kolmogorov/\n // Batchelor congruence) AND by coherence (coherent zones \u2192 long, flat \u2192 stubby).\n float energy = 1.0;\n if (uMedium == 5) {\n float luma = clamp(dot(sampleBase(center, t), W_LUMA), 0.0, 1.0);\n // long in the lights + coherent zones; short dabs in the darks + flat zones.\n energy = mix(0.5, 1.5, luma) * mix(0.6, 1.0, coh);\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";
1
+ export declare const AURORA_BRUSH_GLSL = "// \u2500\u2500 Curved swept brushstroke primitive \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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\u00B7K\u00B7along0\u00B7(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 \u2014 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 \u2014 thin-fat-thin (classic brush)\n// 1 load-drag \u2014 fat start, tapers to a point\n// 2 dab \u2014 ellipse-like, round center\n// 3 even \u2014 near-constant, slight end taper\n// 4 comma \u2014 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 \u2014 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 \u2014 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 \u2014 endpoints\n// halfW \u2014 base half-width (world units)\n// bulge \u2014 signed perpendicular midpoint offset, in world units\n// shapeType \u2014 0..3\n// bristleFreq \u2014 spatial frequency of edge raggedness\n// bristleAmp \u2014 0..0.5 fraction of halfW chewed away at edge extrema\n// streakSeed \u2014 uniqueness seed for internal streaks\n// colAtMid \u2014 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 \u2014 no new color math). Two\n// complementary-hued strokes overlapping transition through OKLab, where the midpoint\n// holds chroma, instead of the linear mix() that desaturates toward grey (slice 8 F2).\nvec3 paintOverOklab(vec3 under, vec3 over, float alpha) {\n vec3 labU = linOklab(under);\n vec3 labO = linOklab(over);\n return max(oklabToLinearSrgb(mix(labU, labO, alpha)), vec3(0.0));\n}\n\n// Composite stroke over col with internal streaking + ACCUMULATE paint HEIGHT.\n// streakAmp \u2014 0..0.2 how much internal streaks darken/lighten\n// impastoAmp \u2014 0..1 paint-thickness contribution (the height the relight reads)\n// hardness \u2014 0..1 how crisp the compositing transition is (1 = razor, 0 = creamy)\n//\n// AW.W4.2 \u2014 the fixed-RGB edge rim (the phantom upper-left light) is RETIRED. Each\n// stroke now deposits PAINT HEIGHT into the height accumulator (coverage x per-stroke\n// thickness, perturbed by the bristle/streak fbm for ridges/grooves); mediumOil\n// derives a normal from the accumulated height gradient (dFdx/dFdy) and relights it\n// once with the movable uLightDir source (relightImpasto), in linear before aces().\n//\n// AX.W13 \u2014 the internal streak now perturbs HUE + CHROMA in OKLCh (not value-only),\n// so a single stroke carries a small hue gradient (broken color at the ATOM level \u2014\n// adjacent strokes shimmer like real impasto, not flat stamped swatches; slice 8 F5);\n// and the OVER-composite runs in OKLab on the painterly stroke mediums so overlapping\n// complementaries mix as pigment, not the linear-mix grey (slice 8 F2).\n// impastoFloor \u2014 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 \u2014 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 // \u2500\u2500 WITHIN-stroke OKLCh broken color (AX.W13, slice 8 F5). The streak fBm perturbs\n // the stroke's HUE + CHROMA along alongT/crossN, seeded per-stroke, so a single\n // stroke reads a small hue gradient (the atom-level broken-color shimmer). Value is\n // carried by the same streak so the loaded-brush light/dark survives. Gated to the\n // painterly stroke mediums + uBrokenColor (the smooth pole keeps the cheap value-only\n // streak). A second decorrelated fBm drives the hue axis so hue \u2260 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 // \u00B1~10\u00B0 within-stroke hue swing + \u00B1~9% chroma, scaled by uBrokenColor. Smaller\n // than the per-stroke cell jitter (brokenColorJitter) \u2014 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) \u2014 kept for every\n // medium that reaches paintOver (the smooth pole never does).\n c *= 1.0 + streak * streakAmp * 2.0;\n\n // Subtle value variance across width (hollow-center catch-light)\n float crossShade = smoothstep(0.0, 0.4, s.edgeN) * (1.0 - smoothstep(0.65, 1.0, s.edgeN));\n c *= 1.0 + crossShade * 0.05;\n\n float softLimit = mix(0.35, 0.98, hardness);\n float alpha = smoothstep(0.0, 1.0 - softLimit, s.coverage) * strokeOpacity;\n // OKLab OVER-composite on the painterly stroke mediums (overlap mixes as pigment,\n // not grey); the smooth pole never reaches paintOver, so it keeps the cheap path.\n col = isPainterlyStroke() ? paintOverOklab(col, c, alpha) : mix(col, c, alpha);\n\n // \u2500\u2500 Accumulate paint HEIGHT (AW.W4.2). Thickness peaks at the stroke spine and\n // falls toward the impastoFloor at the edge (edgeN: 0=edge, 1=spine), perturbed by\n // the bristle streak so ridges/grooves form. The relight reads the gradient of this\n // field. The van-Gogh profile's floor=1.0 gives every atomic dab a full-height crown.\n float ridge = 0.7 + 0.3 * (streak + 0.5);\n float crown = impastoFloor + (1.0 - impastoFloor) * s.edgeN;\n height += s.coverage * impastoAmp * crown * ridge * alpha;\n}\n\n// AW.W4.2 \u2014 relight the accumulated paint height. Derive a normal from the height\n// gradient (dFdx/dFdy \u2014 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 \u2192 high shininess (sharp glint); thin paint \u2192 broad soft sheen.\n float shininess = mix(8.0, 64.0, clamp(height * 2.0, 0.0, 1.0));\n float spec = pow(max(dot(N, H), 0.0), shininess);\n // Modulate the lit terms by how much paint sits here (no relief on bare ground).\n float relief = clamp(h * 1.5, 0.0, 1.0);\n vec3 lit = col * (1.0 + uImpasto * relief * (diff - 0.5) * 0.5 * uLightColor);\n lit += uImpasto * relief * spec * 0.6 * uLightColor;\n return lit;\n}\n\n// Best-of-9-neighbor placement: sample 3x3 surrounding cells and take the\n// stroke that covers this pixel most. Breaks the grid by per-cell jitter and\n// sparse density via noise thresholding.\n// energyGrade \u2014 the SBR energy-grade magnitude (0 = none, the oil/oil-pastel\n// baseline; 1 = the full van-Gogh Starry-Night cascade \u2014 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 \u2014 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 \u2014 the AW.W4.1 strokeOrient switch:\n // uStrokeOrient==0 (flow) \u2014 the layer-provided hand-authored flowField.\n // uStrokeOrient==1 (tensor) \u2014 the structure-tensor minor eigenvector at the\n // cell center (the color field's edge-tangent),\n // so strokes hug the color zones (van Gogh).\n // coh carries the per-cell coherence A for the W4.3 energy grading (long\n // confident strokes where structure is coherent, stubby dabs in flat zones).\n vec2 f;\n float coh = 1.0;\n if (uStrokeOrient == 1) {\n vec3 tf = structureTensorField(center, t, safeDir(flow));\n f = safeDir(tf.xy);\n coh = tf.z;\n } else {\n f = safeDir(flow);\n }\n // Per-stroke deterministic local perturbation so alternate stroke layers stay\n // live. Tensor strokes get LESS angular jitter (the field already orients them).\n float jScale = (uStrokeOrient == 1) ? 0.4 : 1.0;\n float angJ = (hash21(cc + seed + 11.0) - 0.5) * 0.9 * jScale; // +/- 0.45/0.18 rad\n float localCurl = (fbm(center * (2.6 + seed * 0.11) + seed * 1.9) - 0.5) * 0.55 * uFlowCurl;\n vec2 dir = rotateDir(f, angJ + localCurl);\n\n // AX.W13 \u2014 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 \u2192 long confident strokes,\n // dark \u2192 short dabs, the Kolmogorov/Batchelor congruence) AND by coherence\n // (coherent zones \u2192 long, flat \u2192 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";
@@ -1 +1 @@
1
- export declare const AURORA_COMPOSITION_GLSL = "// \u2500\u2500 Palette LUT (W5 \u2014 OKLCh/OKLab interpolation) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// The endpoints stay CPU-baked to LINEAR sRGB (the Aras precompute pattern); only\n// the BETWEEN-endpoint interpolation moves to perceptual space. OKLab-rectangular\n// for the default ramp (holds chroma, no grey midpoint); the OKLCh hue-arc only on\n// uHuePath increasing/decreasing (deliberate rainbow travel). The prior linear\n// mix() (the muddy-midtone source) is GONE.\nvec3 samplePalette(float id) {\n id = clamp(id, 0.0, 1.0);\n float scaled = id * float(uStopCount - 1);\n int i0 = int(floor(scaled));\n int i1 = min(i0 + 1, uStopCount - 1);\n float t = fract(scaled);\n t = smoothstep(0.0, 1.0, t);\n vec3 a = uPalette[i0];\n vec3 b = uPalette[i1];\n // increasing(2)/decreasing(3) request the OKLCh hue-arc (rainbow sweep); every\n // other method takes the OKLab-rectangular straight line (no hue detour).\n if (uHuePath == 2 || uHuePath == 3) {\n return mixPaletteOklchArc(a, b, t, uHuePath);\n }\n return mixPaletteOklab(a, b, t);\n}\n\n// \u2500\u2500 Nuclei field \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 \u2014 slow global paletteId breathe between adjacent stops. The\n // rate rides K_PAL (perceptible ~30\u201360s 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";
1
+ export declare const AURORA_COMPOSITION_GLSL = "// \u2500\u2500 Palette LUT (W5 \u2014 OKLCh/OKLab interpolation) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// The endpoints stay CPU-baked to LINEAR sRGB (the Aras precompute pattern); only\n// the BETWEEN-endpoint interpolation runs in perceptual space. AX.W11 \u2014 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// \u2500\u2500 Nuclei field \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 \u2014 slow global paletteId breathe between adjacent stops. The\n // rate rides K_PAL (perceptible ~30\u201360s 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";
@@ -1,2 +1,2 @@
1
1
  export declare const AURORA_MEDIUMS_PRE_BRUSH_GLSL = "// \u2500\u2500 Medium overlays \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n// A quick re-computation of base color for edge-mask sampling\nvec3 sampleBase(vec2 p, float t) {\n vec2 pw = domainWarp(p, t);\n float id; float vm;\n nucleiField(pw, t, id, vm);\n vec3 c = samplePalette(id);\n c *= 1.0 + uValueVariance * vm;\n return c;\n}\n\n// \u2500\u2500 Structure-tensor / edge-tangent-flow (AW.W4.1) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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\u00B2, GxGy],[GxGy, Gy\u00B2]] eigen-decomposes closed-form into the MINOR\n// eigenvector (the edge-TANGENT \u2014 least color change, the stroke direction; the\n// MAJOR eigenvector is the gradient/edge-NORMAL and would make strokes cross the\n// bands) and the coherence A = (\u03BB1-\u03BB2)/(\u03BB1+\u03BB2). Low-coherence (flat) zones relax\n// toward the smooth fallbackDir by (1-A) so tensor noise never reads as jitter.\n// Single-pass small-tap WebGL2 form; the Gaussian-smoothed multi-tap + LIC smear\n// is the AW.W7 WebGPU multi-pass fold. Returns vec3(tangent.x, tangent.y, A).\nvec3 structureTensorField(vec2 p, float t, vec2 fallbackDir) {\n float e = 0.0035; // small-tap neighborhood (edge-mask scale)\n // 3x3 luma samples.\n float l00 = dot(sampleBase(p + vec2(-e, -e), t), W_LUMA);\n float l10 = dot(sampleBase(p + vec2( 0.0, -e), t), W_LUMA);\n float l20 = dot(sampleBase(p + vec2( e, -e), t), W_LUMA);\n float l01 = dot(sampleBase(p + vec2(-e, 0.0), t), W_LUMA);\n float l21 = dot(sampleBase(p + vec2( e, 0.0), t), W_LUMA);\n float l02 = dot(sampleBase(p + vec2(-e, e), t), W_LUMA);\n float l12 = dot(sampleBase(p + vec2( 0.0, e), t), W_LUMA);\n float l22 = dot(sampleBase(p + vec2( e, e), t), W_LUMA);\n // Sobel derivatives.\n float Gx = (l20 + 2.0 * l21 + l22) - (l00 + 2.0 * l01 + l02);\n float Gy = (l02 + 2.0 * l12 + l22) - (l00 + 2.0 * l10 + l20);\n // Structure tensor components.\n float Jxx = Gx * Gx;\n float Jyy = Gy * Gy;\n float Jxy = Gx * Gy;\n // Closed-form 2x2 eigenvalues: \u03BB = 0.5(tr \u00B1 sqrt((Jxx-Jyy)\u00B2 + 4Jxy\u00B2)).\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 \u2014 the edge TANGENT (the stroke direction). The principal\n // angle is \u03B8 = 0.5\u00B7atan2(2\u00B7Jxy, Jxx-Jyy); this form is robust at Jxy\u22480 (where the\n // (\u03BB-Jyy, Jxy) eigenvector formula collapses to a zero vector). The tangent is at\n // \u03B8 + 90\u00B0. Guard the fully-isotropic (no-gradient) degenerate case \u2192 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] \u2014 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\u00B0 sign ambiguity toward the fallback so the blend is continuous.\n if (dot(tangent, fallbackDir) < 0.0) tangent = -tangent;\n vec2 dir = normalize(mix(fallbackDir, tangent, A) + 1e-6);\n return vec3(dir, A);\n}\n\nvec3 mediumWatercolor(vec3 col, vec2 p, float t) {\n // Wet-edge cauliflowers via luma-gradient magnitude\n float eps = 0.004;\n vec3 cx1 = sampleBase(p + vec2(eps, 0.0), t);\n vec3 cx2 = sampleBase(p - vec2(eps, 0.0), t);\n vec3 cy1 = sampleBase(p + vec2(0.0, eps), t);\n vec3 cy2 = sampleBase(p - vec2(0.0, eps), t);\n float gx = dot(cx1 - cx2, W_LUMA);\n float gy = dot(cy1 - cy2, W_LUMA);\n float edge = sqrt(gx * gx + gy * gy) / (2.0 * eps);\n float mask = smoothstep(0.0, 2.5, edge);\n col *= mix(1.0, 0.78, mask * uWetEdge);\n\n // Granulation \u2014 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 \u2014 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 \u2014 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";
2
- export declare const AURORA_MEDIUMS_POST_BRUSH_GLSL = "// \u2500\u2500 Crayon / oil-pastel \u2014 paper tooth \u00D7 wax pigment (PEER medium) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Crayon is not strokes. It's pigment crumbs dragged across paper tooth.\n// Model: heavy 2D tooth noise at multiple scales, anisotropically stretched\n// along flow direction, multiplied into the base color. Add a slow \"waxy\n// film\" that slightly unifies hues, and occasional darker \"pressed\" spots\n// where the crayon dug in. NO straight segments. Dispatched at main() level as\n// a peer of pastel/watercolor/oil \u2014 never a branch inside mediumOil().\n// AW.W4.4 \u2014 GENUINE oil pastel: pigment DEPOSITED on the paper tooth, a broken\n// SCUMBLE upper layer letting the paper/lower color show through, and a waxy BURNISH\n// film whose sheen grows with layer count. Reworked from the old tooth-multiply\n// gradient into a material-truth deposition model. Oriented along the ETF field\n// (W4.1); OKLCh broken color (W5). The oil-pastel first-class medium (uMedium==6)\n// AND the legacy crayon peer (uMedium==4) both dispatch this single body.\nvec3 mediumCrayon(vec3 col, vec2 p, float t) {\n // Orient the deposition streaks along the structure-tensor edge-tangent. The\n // painterly mediums force uStrokeOrient==tensor, so read the tensor field directly;\n // fall back to flowField for the legacy crayon strokeMode route.\n vec2 flow = (uStrokeOrient == 1)\n ? structureTensorField(p, t, flowField(p, t)).xy\n : flowField(p, t);\n float ang = atan(flow.y, flow.x);\n float ca = cos(-ang), sa = sin(-ang);\n vec2 pr = vec2(p.x * ca - p.y * sa, p.x * sa + p.y * ca);\n\n float aniso = mix(0.45, 0.95, uStrokeAnisotropy);\n float scale = max(uStrokeScale * 1.6, 180.0);\n\n // \u2500\u2500 Paper TOOTH height field \u2014 the relief the 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 paperHeight = 0.55 * t1 + 0.30 * t2 + 0.15 * t3;\n\n // \u2500\u2500 DEPOSITION \u2014 pigment lands on the tooth PEAKS and skips the valleys. Light\n // pressure (low uStrokeAmount) shows paper through the valleys; heavy pressure\n // fills them. deposit in [0,1] is the per-pixel pigment coverage.\n float pressure = clamp(uStrokeAmount, 0.0, 1.0);\n float toothFloor = mix(0.62, 0.18, pressure); // heavy pressure lowers the floor\n float deposit = smoothstep(toothFloor, toothFloor + 0.35, paperHeight);\n\n // \u2500\u2500 SCUMBLE \u2014 a broken UPPER layer at coverage < 1 (the signature oil-pastel\n // move). A coarse mask gates a lighter dragged stroke over the deposition so the\n // LOWER color shows through the gaps. Drag direction follows the tensor flow.\n float scumbleMask = smoothstep(0.35, 0.85, vnoise(vec2(pr.x * scale * 0.55, pr.y * scale * 0.22) + 7.0));\n float scumbleCoverage = scumbleMask * pressure * 0.6; // < 1 \u2014 paper/lower shows\n\n // The lower color is the base; the deposited pigment is the base lifted into the\n // tooth. Mix the base toward the deposited layer by the deposition coverage, then\n // let the scumble break the upper layer so the lower reads through the gaps.\n vec3 paper = col * 0.92; // the paper-showing-through tone\n vec3 deposited = col; // the pigment-on-tooth tone\n vec3 result = mix(paper, deposited, deposit);\n // Scumble: a broken pass of the deposited color at < 1 coverage.\n result = mix(result, deposited * (0.96 + 0.08 * t1), scumbleCoverage);\n\n // \u2500\u2500 WAXY BURNISH FILM \u2014 a low-roughness BROAD specular lobe whose sheen grows\n // with the pigment build-up (burnish), distinct from oil's sharp glint. Reads the\n // paper-height gradient as a soft normal and lights it with the movable uLightDir.\n float waxNormalZ = 2.4;\n vec3 N = normalize(vec3(-dFdx(paperHeight) * 30.0, -dFdy(paperHeight) * 30.0, waxNormalZ));\n vec3 L = normalize(uLightDir);\n vec3 V = vec3(0.0, 0.0, 1.0);\n vec3 H = normalize(L + V);\n float burnish = deposit * (0.5 + 0.5 * scumbleMask); // sheen grows with build-up\n float sheen = pow(max(dot(N, H), 0.0), 6.0); // broad waxy lobe\n result += burnish * sheen * 0.10 * uLightColor;\n\n // \u2500\u2500 Broken-color pigment: stable wax/pigment patches in OKLCh (W5), not flicker.\n vec2 pigmentCell = floor(pr * max(scale * 0.18, 32.0));\n float pigmentMask = smoothstep(0.28, 0.82, vnoise(pr * scale * 0.21 + 19.0));\n result = brokenColorJitter(\n result,\n hash21(pigmentCell + 17.0),\n hash21(pigmentCell * 2.1 + 31.0),\n 0.45 + 0.55 * pigmentMask\n );\n\n // Oil pastel is saturation-amplified (the waxy chroma punch).\n result = saturate3(result, 1.12);\n\n return result;\n}\n\nvec3 mediumOil(vec3 col, vec2 p, float t) {\n // Mode knobs (uStrokeMode) \u2014 oil-stroke modes ONLY (crayon is a peer medium,\n // uMedium==4, dispatched at main()):\n // 0 oil \u2014 balanced modern-abstract/palette-knife hybrid\n // 1 knife \u2014 palette-knife impasto: razor edges, heavy bristle/shadow\n // 3 brushwork \u2014 thick bristle brush\n int mode = uStrokeMode;\n\n // Per-mode parameters\n int shapeType = 0; // tapered\n float bristleAmp = 0.25; // 0..0.5\n float streakFreq = 9.0;\n float streakAmp = 0.09;\n float impastoAmp = 0.9;\n float hardness = 0.80; // edge compositing\n float toothScale = 240.0;\n float toothAmp = 0.09;\n float pigmentSat = 1.03;\n float densityBig = 0.65;\n float densityMed = 0.78;\n float densitySml = 0.90;\n\n if (mode == 1) { // palette knife\n shapeType = 3; // flat, even\n bristleAmp = 0.12;\n streakFreq = 4.0; streakAmp = 0.05;\n impastoAmp = 1.6;\n hardness = 0.95;\n toothAmp = 0.04;\n densityBig = 0.80; densityMed = 0.88; densitySml = 0.70;\n } else if (mode == 3) { // thick brushwork\n shapeType = 0; // tapered\n bristleAmp = 0.32;\n streakFreq = 14.0; streakAmp = 0.14;\n impastoAmp = 1.2;\n hardness = 0.85;\n toothAmp = 0.07;\n }\n\n // Scales & multipliers from uniforms\n float baseScale = max(uStrokeScale * 0.006, 0.008);\n // Three layers: big gestural, medium body, small dabs\n float sBig = baseScale * 2.4;\n float sMed = baseScale * 1.1;\n float sSml = baseScale * 0.45;\n\n float lenMulBig = mix(2.2, 3.8, uStrokeAnisotropy);\n float widMulBig = mix(0.55, 0.32, uStrokeAnisotropy);\n float lenMulMed = mix(2.0, 3.4, uStrokeAnisotropy);\n float widMulMed = mix(0.50, 0.30, uStrokeAnisotropy);\n float lenMulSml = mix(1.6, 2.6, uStrokeAnisotropy);\n float widMulSml = mix(0.45, 0.32, uStrokeAnisotropy);\n\n float jitterAmt = 0.75; // large jitter \u2014 no grid\n vec2 flow = flowField(p, t);\n\n vec3 result = col;\n // AW.W4.2 \u2014 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\n // Layer 1 \u2014 big gestural strokes (sparse, shaping)\n StrokeHit hBig = bestOil(p, sBig, lenMulBig, widMulBig, jitterAmt * 0.55,\n densityBig, shapeType, bristleAmp, flow, t, 1.3);\n paintOver(result, height, hBig, streakFreq * 0.7, streakAmp,\n uImpasto * impastoAmp * uStrokeAmount, hardness, 1.3);\n\n // Layer 2 \u2014 medium body strokes\n StrokeHit hMed = bestOil(p + vec2(11.3, 3.7), sMed, lenMulMed, widMulMed,\n jitterAmt, densityMed, shapeType, bristleAmp, flow, t, 2.7);\n paintOver(result, height, hMed, streakFreq, streakAmp,\n uImpasto * impastoAmp * uStrokeAmount, hardness, 2.7);\n\n // Layer 3 \u2014 small dabs (more frequent, smaller)\n int smlShape = (mode == 1) ? 2 : shapeType; // knife uses dabs for sparkle\n StrokeHit hSml = bestOil(p + vec2(-5.1, 8.4), sSml, lenMulSml, widMulSml,\n jitterAmt * 1.3, densitySml, smlShape,\n bristleAmp * 0.85, flow, t, 4.1);\n paintOver(result, height, hSml, streakFreq * 1.4, streakAmp * 0.8,\n uImpasto * impastoAmp * 0.65 * uStrokeAmount, hardness, 4.1);\n\n // Layer 4 \u2014 fill dabs (very dense, very small) \u2014 covers bald spots\n float sFill = baseScale * 0.22;\n float lenMulFill = mix(1.4, 2.0, uStrokeAnisotropy);\n float widMulFill = mix(0.50, 0.38, uStrokeAnisotropy);\n int fillShape = (mode == 1) ? 3 : 2; // knife=even, others=dab (round fills)\n StrokeHit hFill = bestOil(p + vec2(3.9, -6.2), sFill, lenMulFill, widMulFill,\n jitterAmt * 1.5, 0.95, fillShape,\n bristleAmp * 0.6, flow, t, 8.9);\n paintOver(result, height, hFill, streakFreq * 1.8, streakAmp * 0.6,\n uImpasto * impastoAmp * 0.4 * uStrokeAmount, hardness * 0.9, 8.9);\n\n // Optional crosshatch layer\n if (uStrokeLayers == 2) {\n vec2 flow2 = vec2(-flow.y, flow.x);\n StrokeHit hX = bestOil(p + vec2(7.3, -2.1), sMed, lenMulMed * 0.9, widMulMed,\n jitterAmt, densityMed * 0.7, shapeType, bristleAmp, flow2, t, 6.5);\n paintOver(result, height, hX, streakFreq, streakAmp * 0.85,\n uImpasto * impastoAmp * 0.55 * uStrokeAmount, hardness, 6.5);\n }\n\n // Canvas tooth \u2014 linen weave\n float tooth1 = vnoise(p * toothScale);\n float tooth2 = vnoise(p * toothScale * vec2(0.6, 2.4) + 37.0);\n float tooth = (0.6 * tooth1 + 0.4 * tooth2) - 0.5;\n result *= 1.0 + tooth * toothAmp * uCanvasGrain;\n\n // AW.W4.2 \u2014 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 * toothAmp * 0.5;\n result = relightImpasto(result, height, canvasBase);\n\n // Pigment saturation boost\n result = saturate3(result, pigmentSat);\n\n return result;\n}\n\n// AW.W4.3 \u2014 the van-Gogh atomic-stroke medium. Composes the oil stroke engine\n// (mediumOil) with the W4.1 ETF orientation (forced tensor via the bridge), the\n// W4.3 energy grading (gated by uMedium==5 inside bestOil \u2014 long confident strokes\n// in the lights/coherent zones, short dabs in the darks/flat zones, the Starry-Night\n// Kolmogorov/Batchelor cascade), the W5 OKLCh per-stroke pigment jitter (already in\n// bestOil's brokenColorJitter), and the W4.2 real impasto relight. No subject matter\n// \u2014 the \"source image\" is the generated nuclei field, so strokes trace its iso-bands.\nvec3 mediumVangogh(vec3 col, vec2 p, float t) {\n return mediumOil(col, p, t);\n}";
2
+ export declare const AURORA_MEDIUMS_POST_BRUSH_GLSL = "// \u2500\u2500 Medium id constants (mirror uniformBridge.ts MEDIUM_ID \u2014 the uMedium ladder) \u2500\u2500\n// The StrokeProfile selector (AX.W12 + AX.W13) dispatches on the medium id: oil,\n// van-Gogh, and oil-pastel each AUTHOR a first-class profile in profileFor.\n#define MEDIUM_OIL 3\n#define MEDIUM_CRAYON 4\n#define MEDIUM_VANGOGH 5\n#define MEDIUM_OILPASTEL 6\n\n// \u2500\u2500 Crayon \u2014 DRY wax pigment on paper tooth (first-class medium, uMedium==4) \u2500\u2500\n// Crayon is not strokes. It's dry pigment crumbs dragged across paper tooth \u2014 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 \u2014 that waxy gloss is\n// the OIL-PASTEL deposition's signature (mediumOilPastel), distinct from dry crayon\n// (AX.W13 slice 8 F1 \u2014 the split). Crayon shares the SUBSTRATE (the structure-tensor\n// orientation, the tooth noise, OKLCh brokenColorJitter), not the dispatch body.\nvec3 mediumCrayon(vec3 col, vec2 p, float t) {\n // Orient the deposition along the structure-tensor edge-tangent (the bridge forces\n // tensor for crayon, so read the tensor field directly).\n vec2 flow = structureTensorField(p, t, flowField(p, t)).xy;\n float ang = atan(flow.y, flow.x);\n float ca = cos(-ang), sa = sin(-ang);\n vec2 pr = vec2(p.x * ca - p.y * sa, p.x * sa + p.y * ca);\n\n float aniso = mix(0.45, 0.95, uStrokeAnisotropy);\n float scale = max(uStrokeScale * 1.6, 180.0);\n\n // \u2500\u2500 Paper TOOTH height field \u2014 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 // \u2500\u2500 DRY TOOTH-MULTIPLY \u2014 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 \u2014 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 // \u2500\u2500 HARD SCUMBLE \u2014 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 // \u2500\u2500 Broken-color pigment: stable wax/pigment patches in OKLCh (W5), not flicker.\n vec2 pigmentCell = floor(pr * max(scale * 0.18, 32.0));\n float pigmentMask = smoothstep(0.28, 0.82, vnoise(pr * scale * 0.21 + 19.0));\n result = brokenColorJitter(\n result,\n hash21(pigmentCell + 17.0),\n hash21(pigmentCell * 2.1 + 31.0),\n 0.45 + 0.55 * pigmentMask\n );\n\n // Crayon is saturation-amplified (the wax chroma punch \u2014 but DRY, no sheen).\n result = saturate3(result, 1.08);\n\n return result;\n}\n\n// \u2500\u2500 StrokeProfile \u2014 the oil-stroke parameter vector (AX.W12, slice 8 F6) \u2500\u2500\u2500\u2500\u2500\u2500\n// The SBR \"stroke = parameter vector\" canon (facet 11): the per-mode if-ladder knobs\n// become struct FIELDS \u2014 logic-as-DATA, not an imperative branch. profileFor(medium,\n// mode) populates the profile for a (medium, mode) pair; paintStrokeLayers(profile) runs\n// the four-layer bestOil/paintOver cascade off it. A new medium (the W13 van-Gogh /\n// oil-pastel profiles) AUTHORS a profile entry \u2014 it never edits a monolith.\nstruct StrokeProfile {\n int shapeType; // 0 tapered, 1 load-drag, 2 dab, 3 even, 4 comma/crescent\n float bristleAmp; // edge raggedness 0..0.5\n float streakFreq; // internal-streak spatial frequency\n float streakAmp; // internal-streak amplitude\n float impastoAmp; // paint-thickness contribution (the relight reads the height)\n float hardness; // edge-compositing crispness 0..1\n float toothScale; // canvas-tooth (linen weave) spatial frequency\n float toothAmp; // canvas-tooth amplitude\n float pigmentSat; // pigment saturation boost (OKLCh chroma scale)\n float densityBig; // layer-1 (big gestural) placement density gate\n float densityMed; // layer-2 (medium body) placement density gate\n float densitySml; // layer-3 (small dabs) placement density gate\n float energyGrade; // AX.W13 \u2014 SBR energy-grade magnitude (0 off; 1 the van-Gogh\n // Starry-Night length cascade). The energy grade is a PROFILE\n // field, NOT a buried uMedium==5 bestOil branch (slice 8 F0).\n float impastoFloor;// AX.W13 \u2014 the per-stroke height-crown floor (0.4 oil falloff;\n // 1.0 van-Gogh FULL-height crown so each dab catches its glint).\n float densityFill; // AX.W13 \u2014 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 \u2014 a 0.95 fill would close every gap to a smear).\n float groundFloor; // AX.W13 \u2014 the bare-ground darken floor. The gaps between strokes\n // (low accumulated height) multiply toward this floor, so van-Gogh's\n // sparse atomic dabs read over a visibly darker underpainting (the\n // Starry-Night visible ground). Oil/oil-pastel keep 1.0 (no darken).\n};\n\n// The (medium, mode) -> StrokeProfile selector. The if-ladder's knobs are the profile's\n// fields; a new medium adds a CASE here (never an edit to a dispatch body). mode is the\n// uStrokeMode oil-stroke sub-mode (0 oil, 1 palette-knife, 3 brushwork). crayon\n// (uMedium==4) is a PEER tooth-multiply medium that never reaches the stroke cascade.\n// The painterly STROKE mediums are oil (MEDIUM_OIL), van-Gogh (MEDIUM_VANGOGH) and\n// oil-pastel (MEDIUM_OILPASTEL) \u2014 each AUTHORS its own profile entry below.\nStrokeProfile profileFor(int medium, int mode) {\n // The oil baseline (mode 0) \u2014 balanced modern-abstract/palette-knife hybrid.\n StrokeProfile prof = StrokeProfile(\n 0, // shapeType \u2014 tapered\n 0.25, // bristleAmp\n 9.0, // streakFreq\n 0.09, // streakAmp\n 0.9, // impastoAmp\n 0.80, // hardness\n 240.0, // toothScale\n 0.09, // toothAmp\n 1.03, // pigmentSat\n 0.65, // densityBig\n 0.78, // densityMed\n 0.90, // densitySml\n 0.0, // energyGrade \u2014 oil is uniform-length (no Starry-Night cascade)\n 0.4, // impastoFloor \u2014 oil's 0.4+0.6\u00B7edgeN crown falloff\n 0.95, // densityFill \u2014 oil fills the bald spots (full coverage, no bare canvas)\n 1.0 // groundFloor \u2014 oil keeps the full base (no ground darken; fills everywhere)\n );\n\n // \u2500\u2500 Van-Gogh \u2014 first-class atomic comma/crescent dabs (slice 8 F0). NOT a length\n // modulation of oil: sparse high-contrast SEPARABLE marks with their own asymmetric\n // shape, the full Starry-Night energy cascade (energyGrade=1), and full-height\n // impasto crowns so each dab catches its own glint. The mode sub-modes don't apply\n // (van-Gogh has its own grammar), so this returns early.\n if (medium == MEDIUM_VANGOGH) {\n prof.shapeType = 4; // comma / crescent \u2014 the divisionist atomic dab\n prof.bristleAmp = 0.22; // a touch of raggedness on the loaded head\n prof.streakFreq = 16.0; // tight within-stroke streaks (the loaded comma)\n prof.streakAmp = 0.16;\n prof.impastoAmp = 1.5; // heavy paint per atomic dab\n prof.hardness = 0.92; // crisp separable edges (visible inter-stroke gaps)\n prof.toothScale = 220.0;\n prof.toothAmp = 0.06;\n prof.pigmentSat = 1.12; // the van-Gogh chroma punch\n // SPARSE placement \u2014 visible inter-stroke canvas gaps (the atomicity read). Lower\n // density gates than oil so each mark stays separable, not a coverage smear.\n prof.densityBig = 0.42;\n prof.densityMed = 0.52;\n prof.densitySml = 0.60;\n prof.energyGrade = 1.0; // the full Starry-Night length cascade (the profile field)\n prof.impastoFloor = 1.0; // FULL-height crowns \u2014 every atomic dab catches its glint\n prof.densityFill = 0.09; // SPARSE fill \u2014 the bald-spot layer stays mostly open so\n // the atomic dabs read as separable marks over a bare\n // ground, not a fill-closed coverage smear (the gap read).\n prof.groundFloor = 0.38; // darken the bare-ground gaps to the visible Starry-Night\n // underpainting so the bright impasto dabs read separable.\n return prof;\n }\n\n // \u2500\u2500 Oil-pastel \u2014 broad smeared directional strokes deposited via the brush engine\n // (slice 8 F1). Creamy soft edges (low hardness), heavy build-up, and a chroma punch.\n // Distinct from the dry-crayon tooth-multiply (mediumCrayon): oil-pastel LAYS strokes.\n if (medium == MEDIUM_OILPASTEL) {\n prof.shapeType = 2; // dab / blob \u2014 broad short smeared marks\n prof.bristleAmp = 0.10; // smooth creamy edges (waxy pastel, not bristle)\n prof.streakFreq = 6.0; // soft low-frequency internal smear\n prof.streakAmp = 0.07;\n prof.impastoAmp = 0.7; // build-up, but flatter than oil impasto\n prof.hardness = 0.42; // CREAMY \u2014 soft compositing, strokes blend on overlap\n prof.toothScale = 280.0;\n prof.toothAmp = 0.12; // the pastel tooth reads stronger\n prof.pigmentSat = 1.16; // the waxy chroma punch\n prof.densityBig = 0.80; // dense \u2014 broad coverage, pigment-on-pigment build-up\n prof.densityMed = 0.90;\n prof.densitySml = 0.95;\n prof.energyGrade = 0.0; // uniform (no Starry-Night grade)\n prof.impastoFloor = 0.55; // a softer crown than van-Gogh's full-height glint\n return prof;\n }\n\n // \u2500\u2500 Oil sub-modes (mode dispatch on the oil medium).\n if (mode == 1) { // palette knife \u2014 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 \u2014 heavy bristle brush\n prof.shapeType = 0; // tapered\n prof.bristleAmp = 0.32;\n prof.streakFreq = 14.0; prof.streakAmp = 0.14;\n prof.impastoAmp = 1.2;\n prof.hardness = 0.85;\n prof.toothAmp = 0.07;\n }\n return prof;\n}\n\n// The single parameterized four-layer stroke cascade (AX.W12). The four hand-unrolled\n// bestOil/paintOver invocations collapse into ONE body driven by the profile + the\n// uniform-derived per-layer scale/anisotropy multipliers (which are mode-INVARIANT \u2014 the\n// per-layer offsets, seeds, and len/wid muls are fixed across modes, so they stay here as\n// the cascade's structure; only the profile's knobs differentiate the medium). The\n// mode passes through for the two mode-special-cased shapes (knife layer-3 dabs +\n// layer-4 fill shape), which are placement details of the cascade, not profile knobs.\nvoid paintStrokeLayers(inout vec3 col, inout float height, StrokeProfile prof,\n int mode, vec2 p, float t) {\n // Scales & multipliers from uniforms (mode-invariant cascade structure).\n float baseScale = max(uStrokeScale * 0.006, 0.008);\n // Three primary layers: big gestural, medium body, small dabs.\n float sBig = baseScale * 2.4;\n float sMed = baseScale * 1.1;\n float sSml = baseScale * 0.45;\n\n float lenMulBig = mix(2.2, 3.8, uStrokeAnisotropy);\n float widMulBig = mix(0.55, 0.32, uStrokeAnisotropy);\n float lenMulMed = mix(2.0, 3.4, uStrokeAnisotropy);\n float widMulMed = mix(0.50, 0.30, uStrokeAnisotropy);\n float lenMulSml = mix(1.6, 2.6, uStrokeAnisotropy);\n float widMulSml = mix(0.45, 0.32, uStrokeAnisotropy);\n\n float jitterAmt = 0.75; // large jitter \u2014 no grid\n vec2 flow = flowField(p, t);\n\n // Layer 1 \u2014 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 \u2014 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 \u2014 small dabs (more frequent, smaller)\n int smlShape = (mode == 1) ? 2 : prof.shapeType; // knife uses dabs for sparkle\n StrokeHit hSml = bestOil(p + vec2(-5.1, 8.4), sSml, lenMulSml, widMulSml,\n jitterAmt * 1.3, prof.densitySml, smlShape,\n prof.bristleAmp * 0.85, flow, t, 4.1, prof.energyGrade);\n paintOver(col, height, hSml, prof.streakFreq * 1.4, prof.streakAmp * 0.8,\n uImpasto * prof.impastoAmp * 0.65 * uStrokeAmount, prof.hardness, 4.1, prof.impastoFloor);\n\n // Layer 4 \u2014 fill dabs (very dense, very small) \u2014 covers bald spots. The density is\n // PROFILE-driven (prof.densityFill): oil/oil-pastel fill to full coverage; van-Gogh\n // keeps it sparse so the bare-ground gaps between its atomic dabs survive.\n float sFill = baseScale * 0.22;\n float lenMulFill = mix(1.4, 2.0, uStrokeAnisotropy);\n float widMulFill = mix(0.50, 0.38, uStrokeAnisotropy);\n int fillShape = (mode == 1) ? 3 : 2; // knife=even, others=dab (round fills)\n StrokeHit hFill = bestOil(p + vec2(3.9, -6.2), sFill, lenMulFill, widMulFill,\n jitterAmt * 1.5, prof.densityFill, fillShape,\n prof.bristleAmp * 0.6, flow, t, 8.9, prof.energyGrade);\n paintOver(col, height, hFill, prof.streakFreq * 1.8, prof.streakAmp * 0.6,\n uImpasto * prof.impastoAmp * 0.4 * uStrokeAmount, prof.hardness * 0.9, 8.9, prof.impastoFloor);\n\n // Optional crosshatch layer\n if (uStrokeLayers == 2) {\n vec2 flow2 = vec2(-flow.y, flow.x);\n StrokeHit hX = bestOil(p + vec2(7.3, -2.1), sMed, lenMulMed * 0.9, widMulMed,\n jitterAmt, prof.densityMed * 0.7, prof.shapeType, prof.bristleAmp, flow2, t, 6.5,\n prof.energyGrade);\n paintOver(col, height, hX, prof.streakFreq, prof.streakAmp * 0.85,\n uImpasto * prof.impastoAmp * 0.55 * uStrokeAmount, prof.hardness, 6.5, prof.impastoFloor);\n }\n}\n\n// The shared stroke-medium SUBSTRATE finish (AX.W12 + AX.W13): paint the four-layer\n// cascade off the profile, then canvas-tooth + relight + saturation. The three painterly\n// STROKE mediums (oil / van-Gogh / oil-pastel) each AUTHOR their own profile and call\n// this \u2014 the substrate is shared, the PROFILE (not a dispatch-body fork) differentiates\n// the medium (slice 8 F6). mode passes through for the cascade's mode-special-cased shapes.\nvec3 paintStrokeMedium(vec3 col, vec2 p, float t, StrokeProfile prof, int mode) {\n vec3 result = col;\n // AW.W4.2 \u2014 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 \u2014 linen weave\n float tooth1 = vnoise(p * prof.toothScale);\n float tooth2 = vnoise(p * prof.toothScale * vec2(0.6, 2.4) + 37.0);\n float tooth = (0.6 * tooth1 + 0.4 * tooth2) - 0.5;\n result *= 1.0 + tooth * prof.toothAmp * uCanvasGrain;\n\n // AW.W4.2 \u2014 relight the accumulated paint height with the movable uLightDir\n // source (diffuse + Blinn specular, in LINEAR before aces()). The canvas tooth\n // is the base relief term so the weave also catches the raking light.\n float canvasBase = tooth * prof.toothAmp * 0.5;\n result = relightImpasto(result, height, canvasBase);\n\n // AX.W13 \u2014 the visible ground. The bare-ground gaps (low accumulated stroke height)\n // multiply toward prof.groundFloor, so van-Gogh's sparse atomic dabs read as separable\n // marks over a darker underpainting (oil/oil-pastel keep groundFloor=1.0 \u2192 no-op). The\n // smoothstep keeps the impasto dabs (high height) at full value; only the open ground\n // between them darkens.\n result *= mix(prof.groundFloor, 1.0, smoothstep(0.0, 0.38, height));\n\n // Pigment saturation boost\n result = saturate3(result, prof.pigmentSat);\n\n return result;\n}\n\nvec3 mediumOil(vec3 col, vec2 p, float t) {\n // The oil medium is a thin body: fetch the oil profile (mode-dispatched), paint via\n // the shared stroke substrate. uStrokeMode: 0 oil (gestural), 1 knife, 3 brushwork.\n int mode = uStrokeMode;\n StrokeProfile prof = profileFor(MEDIUM_OIL, mode);\n return paintStrokeMedium(col, p, t, prof, mode);\n}\n\n// AX.W13 \u2014 the van-Gogh atomic-stroke medium is now a FIRST-CLASS body, NOT a\n// mediumOil passthrough (slice 8 F0). It composes its OWN vangogh StrokeProfile (the\n// comma/crescent shapeType, sparse high-contrast atomic placement with visible\n// inter-stroke gaps, the full Starry-Night energy-grade cascade as a PROFILE field,\n// and full-height impasto crowns) through the shared stroke substrate. The bridge forces\n// the ETF (tensor) orientation so the swirl-rows queue along the color field's tangent;\n// the within-stroke OKLCh broken color (paintOver) + the OKLab stroke OVER-composite\n// give pigment-true atom-level shimmer. No subject matter \u2014 the \"source image\" is the\n// generated nuclei field, so dabs trace its iso-bands (the Starry-Night swirl-rows).\nvec3 mediumVangogh(vec3 col, vec2 p, float t) {\n StrokeProfile prof = profileFor(MEDIUM_VANGOGH, 0);\n return paintStrokeMedium(col, p, t, prof, 0);\n}\n\n// AX.W13 \u2014 the oil-pastel medium (uMedium==6) is a DISTINCT stroke-deposition body,\n// split out of the dry-crayon tooth-multiply (mediumCrayon) (slice 8 F1). It DEPOSITS\n// broad smeared directional strokes via the same brush engine with a creamy soft\n// hardness, heavy pigment build-up where strokes overlap, and a chroma punch \u2014 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}";
@@ -6,7 +6,7 @@ export { useCursorInteraction } from "./composables/useCursorInteraction";
6
6
  export { createAurora } from "./composables/runtime";
7
7
  export type { AuroraRuntimeMode, AuroraRuntimeOptions } from "./composables/runtime";
8
8
  export { DEFAULT_AURORA_CONFIG, MAX_NUCLEI, MAX_STOPS, type AuroraConfig, type AuroraCursorApi, type AuroraFlow, type AuroraInstance, type AuroraInteractivity, type AuroraMedium, type AuroraNucleus, type FlowPattern, type OklchStop, type StrokeMode, type StrokeOrient, type WarpMode, } from "./constants/presets";
9
- export { resolveAtoms, DEFAULT_ATOMS, type AuroraAtoms, type AuroraMoodAtom, type AuroraMotionAtom, } from "./composables/atoms";
10
- export { cssToOklch, deriveAurora, deriveScene, flattenPalette, hexToOklchStop, oklchStopToHex, oklchToLinear, paletteToCssGradient, } from "./composables/color";
11
- export type { AuroraHarmony, AuroraMood, DeriveAuroraOptions, DeriveEasing, } from "./composables/color";
9
+ export { resolveAtoms, nucleiPrior, DEFAULT_ATOMS, type AuroraAtoms, type AuroraMotionAtom, type AuroraZones, type AuroraZoneArrangement, type AuroraMediumAtom, type AuroraInteractivityAtom, } from "./composables/atoms";
10
+ export { cssToOklch, deriveAurora, flattenPalette, hexToOklchStop, oklchStopToHex, oklchToLinear, paletteToCssGradient, } from "./composables/color";
11
+ export type { AuroraHarmony, DeriveAuroraOptions, DeriveEasing, } from "./composables/color";
12
12
  export type { AuroraHuePath } from "./constants/presets";
@@ -8,7 +8,7 @@ import { type HTMLAttributes } from "vue";
8
8
  *
9
9
  * The layer is uncontrolled by default (`defaultOpen` opens at mount);
10
10
  * pass `:open` + `@update:open` for controlled mode (e.g., when a
11
- * BouncyTabs / DockLayerGroup at the parent governs which layer is
11
+ * SegmentedTabs / DockLayerGroup at the parent governs which layer is
12
12
  * active).
13
13
  *
14
14
  * # M.W2 Lane A (F-ε-3 fix) — recursion-free reveal
@@ -12,9 +12,16 @@ import { type ConstellationField } from "./constellationField";
12
12
  * neutral passes and receives the live field so it can pin itself to a real
13
13
  * node. Zero deck-domain content lives in the component.
14
14
  *
15
- * Palette reads `--constellation-node` / `--constellation-node-dim` /
16
- * `--constellation-line` off the canvas (neutral fallbacks), so a consumer
17
- * override or a dark-mode flip re-tints the lattice.
15
+ * Palette reads the FULL `--constellation-*` legibility set off the canvas (the
16
+ * node/node-dim/line colors + the edge-alpha multipliers + the field-yields-to-
17
+ * type `--constellation-alpha` knob; neutral fallbacks), so a consumer override
18
+ * or a dark-mode flip re-tints AND re-weights the lattice (AX.W17).
19
+ *
20
+ * The focal node + click-to-warp (AX.W17) is a first-class engine concept: with
21
+ * `warpOnClick`, a click warps the focal node to the nearest drifting node via a
22
+ * critically-damped spring stepped INSIDE the substrate's single rAF (no
23
+ * `useSpring`, no second rAF). The consumer paints the focal mark at
24
+ * `field.warp.{x,y}` in its `drawOverlay`.
18
25
  */
19
26
  type __VLS_Props = {
20
27
  /** Node count. Default 64. */
@@ -30,16 +37,44 @@ type __VLS_Props = {
30
37
  seed?: number | string;
31
38
  /** Steer-toward-cursor + tap ripples. Default true; auto-off under reduced-motion. */
32
39
  pointerReactive?: boolean;
40
+ /**
41
+ * Click-to-warp (AX.W17). A click warps the focal node to the nearest
42
+ * drifting node and SPRINGS it there (a critically-damped path, chasing a
43
+ * LIVE drifting target). INDEPENDENT of `pointerReactive` — warp works on a
44
+ * static (non-ripple) lattice, and ripples work without warp. Default false;
45
+ * auto-off under reduced-motion (the click does NOT warp — the focal mark
46
+ * stays put; the stated PRM policy). The low-level `warpTo(...)` imperative
47
+ * method (via `defineExpose`) is the seam this prop sugars.
48
+ */
49
+ warpOnClick?: boolean;
33
50
  class?: HTMLAttributes["class"];
34
51
  /**
35
52
  * The skin seam. Runs after the neutral passes with the live field, so a
36
- * consumer paints its own focal mark pinned to a real node. The branded
37
- * content (a domain accent, a callout label) lives HERE, in the consumer.
53
+ * consumer paints its own focal mark pinned to a real node — read
54
+ * `field.warp.{x,y}` for the spring-eased focal position when `warpOnClick`
55
+ * is on. The branded content (a domain accent, a callout label) lives HERE,
56
+ * in the consumer.
38
57
  */
39
58
  drawOverlay?: (ctx: CanvasRenderingContext2D, field: ConstellationField, now: number) => void;
40
59
  };
41
60
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
61
+ /** The live field (the low-level imperative seam — for a custom overlay). */
42
62
  field: ConstellationField;
63
+ /**
64
+ * Imperative warp (AX.W17). Two call shapes:
65
+ * `warpTo(localPoint)` — `{x, y}` already in canvas-local px
66
+ * (the lower primitive).
67
+ * `warpTo(clientX, clientY)` — client coords, mapped via the
68
+ * deck-scale `toLocal` (the sugar
69
+ * `warpOnClick` calls internally).
70
+ * The focal node re-points to the nearest drifting node + springs there.
71
+ * Returns the chosen node index, or -1 on a degenerate no-op. No-op under
72
+ * reduced-motion (the spring does not advance while the substrate is frozen).
73
+ */
74
+ warpTo(a: {
75
+ x: number;
76
+ y: number;
77
+ } | number, b?: number): number;
43
78
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
44
79
  declare const _default: typeof __VLS_export;
45
80
  export default _default;
@@ -19,16 +19,49 @@ export interface ConstellationPointer {
19
19
  x: number;
20
20
  y: number;
21
21
  }
22
- /** The neutral palette resolved off the canvas custom properties. */
22
+ /**
23
+ * The neutral palette + legibility weights resolved off the canvas custom
24
+ * properties (the FULL `--constellation-*` set — AX.W17). `node`/`nodeDim`/
25
+ * `line` are the colors; `edgeAlpha`/`edgeFocusAlpha`/`alpha` are the per-mode
26
+ * legibility multipliers (replacing the `0.17`/`0.24` magic literals).
27
+ */
23
28
  export interface ConstellationPalette {
24
29
  node: string;
25
30
  nodeDim: string;
26
31
  line: string;
32
+ /** The hairline-edge alpha multiplier (was the `0.17` literal). */
33
+ edgeAlpha: number;
34
+ /** The focus-proximity edge multiplier (consumer skins may read it). */
35
+ edgeFocusAlpha: number;
36
+ /** The field-yields-to-type translucency knob (the global field dimmer). */
37
+ alpha: number;
38
+ }
39
+ /**
40
+ * The per-axis critically-damped warp spring on the focal node (AX.W17). The
41
+ * focal mark's position is `(x, y)`; `vx`/`vy` its velocity; `targetIdx` the
42
+ * LIVE node index it chases (re-read each frame so it tracks a DRIFTING target,
43
+ * not a frozen click-time snapshot). `-1` = no active warp (the focal mark
44
+ * rides its node's drift directly — the identity-ride at settle).
45
+ */
46
+ export interface ConstellationWarp {
47
+ x: number;
48
+ y: number;
49
+ vx: number;
50
+ vy: number;
51
+ /** The chased node's INDEX (live target), or -1 when inactive. */
52
+ targetIdx: number;
27
53
  }
28
54
  /**
29
55
  * The field state the component exposes to its `drawOverlay` consumer so a
30
56
  * skin can pin itself to a real field node. `k` is the `width / BASE_WIDTH`
31
57
  * scale, `dpr` the device-pixel ratio applied by the substrate.
58
+ *
59
+ * The focal node (AX.W17) is a FIRST-CLASS library concept: `focalIndex` names
60
+ * which node is focal (consumer-owned via `warpTo`), and the engine OWNS its
61
+ * position via the `warp` spring stepped inside `stepField`. A `drawOverlay`
62
+ * paints the focal mark at `field.warp.{x,y}` (the spring-eased position) —
63
+ * node-position mutation `drawOverlay` structurally cannot express, resolved by
64
+ * ONE first-class concept (warp + drift unify; no second parallel hook).
32
65
  */
33
66
  export interface ConstellationField {
34
67
  nodes: ConstellationNode[];
@@ -38,6 +71,14 @@ export interface ConstellationField {
38
71
  h: number;
39
72
  k: number;
40
73
  dpr: number;
74
+ /**
75
+ * The designated focal node's INDEX, or `-1` when none is pinned. Re-points
76
+ * on each `warpTo`; node count is conserved (it is a designation, not a new
77
+ * node). A `drawOverlay` reads `field.warp.{x,y}` for the spring-eased mark.
78
+ */
79
+ focalIndex: number;
80
+ /** The per-axis warp spring the engine steps inside `stepField` (AX.W17). */
81
+ warp: ConstellationWarp;
41
82
  }
42
83
  /**
43
84
  * The `<Constellation>` consumer prop surface (the public type a consumer types
@@ -55,26 +96,90 @@ export interface ConstellationProps {
55
96
  seed?: number | string;
56
97
  /** Steer-toward-cursor + tap ripples. Default true; auto-off under reduced-motion. */
57
98
  pointerReactive?: boolean;
99
+ /**
100
+ * Click-to-warp (AX.W17): a click warps the focal node to the nearest
101
+ * drifting node + springs it there. INDEPENDENT of `pointerReactive` (warp
102
+ * works on a non-ripple lattice). Default false; auto-off under
103
+ * reduced-motion (the focal mark stays put — the stated PRM policy).
104
+ */
105
+ warpOnClick?: boolean;
58
106
  class?: string;
59
107
  /** The skin seam — paints the consumer's focal mark on the live field. */
60
108
  drawOverlay?: (ctx: CanvasRenderingContext2D, field: ConstellationField, now: number) => void;
61
109
  }
62
110
  export declare const DEFAULT_PALETTE: ConstellationPalette;
63
- /** Resolve the neutral palette off a canvas's resolved custom properties. */
111
+ /**
112
+ * Resolve the FULL neutral palette + legibility weights off a canvas's resolved
113
+ * custom properties (AX.W17). The colors AND the alpha multipliers route through
114
+ * the same `getComputedStyle` probe, so a dark-mode flip re-resolves BOTH the
115
+ * `:root`/`.dark` color arms AND the per-mode alpha defaults in one read.
116
+ */
64
117
  export declare function readPalette(canvas: HTMLCanvasElement): ConstellationPalette;
65
118
  /**
66
119
  * Seed `count` drifting nodes inside `w × h`. `rng` is a `() => number` in
67
120
  * `[0, 1)` (the glass-ui `mulberry32`/`Math.random`); a seeded `rng` lays out a
68
- * reproducible field. No node is pinned — every node drifts (pinning a focal
69
- * node is consumer skin, not field mechanics).
121
+ * reproducible field. Every node drifts; a focal node IS pinnable (AX.W17) — the
122
+ * `field.focalIndex` designation + the engine-owned `warp` spring chase a node,
123
+ * but the underlying node still drifts (warp re-points an EXISTING node, never
124
+ * adds one, so node count is conserved).
70
125
  */
71
126
  export declare function seedField(rng: () => number, count: number, w: number, h: number, speed: number): ConstellationNode[];
72
127
  /**
73
128
  * Advance the field one step: drift + wall-bounce every node, then (if the
74
129
  * pointer is live) lean nodes within reach toward the cursor WITHOUT changing
75
130
  * their speed (the slow geometric drift is preserved). `k` is the scale factor.
131
+ *
132
+ * `dt` (seconds since the previous frame) advances the focal-node warp spring
133
+ * via `warpStep` INSIDE this single per-frame call (AX.W17) — NO second rAF,
134
+ * NO `useSpring`. The warp rides the substrate's ONE parked rAF; a `dt` of `0`
135
+ * (omitted / first frame) leaves the spring untouched.
136
+ */
137
+ export declare function stepField(field: ConstellationField, k: number, speed: number, pointer: ConstellationPointer | null, dt?: number): void;
138
+ /**
139
+ * The nearest DRIFTING node to `(px, py)` in canvas-local px — a linear O(count)
140
+ * min-d² scan (count 64 default — negligible; matches the O(count²) edge pass).
141
+ * "Lattice point" = the nearest drifting NODE (the constellation has NO fixed
142
+ * lattice). `excludeIdx` drops a node from the candidate set (the focal node
143
+ * excludes ITSELF, so a cursor-on-focal warp no-ops). Returns `-1` if no
144
+ * eligible node exists (empty field, or only the excluded node).
145
+ */
146
+ export declare function nearestNode(field: ConstellationField, px: number, py: number, excludeIdx?: number): number;
147
+ /**
148
+ * Advance the focal-node warp spring one `dt` (seconds). A per-axis 2nd-order
149
+ * critically-damped integrator over `field.warp.{x,y}` toward the LIVE position
150
+ * of `field.nodes[field.warp.targetIdx]` — re-read EACH frame, so the spring
151
+ * CHASES a drifting target and arrives ON it (the identity-ride). No-ops when no
152
+ * warp is active (`targetIdx < 0`). `dt` is clamped to {@link WARP_DT_CLAMP} so a
153
+ * tab-throttle / offscreen-park-resume gap cannot teleport the mark.
154
+ *
155
+ * x += v·dt ; v += (−2ζω·v − ω²·(x − target))·dt
156
+ *
157
+ * Reuses the keyframes.js `(response, dampingFraction)` param model
158
+ * (ω = 2π/response, ζ = dampingFraction) but NOT its rAF — the FORBID-useSpring
159
+ * contract (a second rAF would defeat the parked-substrate one-path freeze).
160
+ */
161
+ export declare function warpStep(field: ConstellationField, dt: number): void;
162
+ /**
163
+ * Point the warp at a node INDEX (the live target-source seam). The focal node
164
+ * re-points to `idx`; the spring chases `nodes[idx]`'s LIVE position FROM the
165
+ * focal mark's CURRENT position (`warp.{x,y}`) — a continuous spring-eased path,
166
+ * never a snap. The component seeds `warp.{x,y}` at field-center on first layout,
167
+ * so even the first warp springs from a real start point. `idx < 0` clears the
168
+ * warp (the focal mark is removed). Re-pointing does NOT zero the velocity, so a
169
+ * mid-flight re-target curves smoothly toward the new node.
170
+ */
171
+ export declare function setWarpTarget(field: ConstellationField, idx: number): void;
172
+ /**
173
+ * Warp the focal node to the nearest drifting node to a canvas-LOCAL px point
174
+ * (the `warpTo(point)` primitive). The DEGENERATE case — the click's UNCONSTRAINED
175
+ * nearest node is already the current focal (cursor on/closest to the focal mark)
176
+ * — NO-OPS (returns the current focal; a click on yourself does not re-warp). Any
177
+ * OTHER click re-points to the nearest node, with the focal EXCLUDED from the
178
+ * candidate set (you never re-warp to yourself; the next-nearest wins). Returns
179
+ * the chosen node index, or `-1` when no eligible node exists. The component's
180
+ * `warpTo(clientX, clientY)` sugar maps client→local px via `toLocal` first.
76
181
  */
77
- export declare function stepField(field: ConstellationField, k: number, speed: number, pointer: ConstellationPointer | null): void;
182
+ export declare function warpTo(field: ConstellationField, px: number, py: number): number;
78
183
  /** Pass 1 — hairline edges between any two nodes within `link` px (alpha falls off with distance). */
79
184
  export declare function drawEdges(ctx: CanvasRenderingContext2D, field: ConstellationField, link: number, palette: ConstellationPalette): void;
80
185
  /** Pass 2 — ambient node dots. */
@@ -1,3 +1,3 @@
1
1
  export { default as Constellation } from "./Constellation.vue";
2
- export { seedField, stepField, readPalette, drawEdges, drawNodes, drawPointerWeb, drawRipples, BASE_WIDTH, DEFAULT_PALETTE, } from "./constellationField";
3
- export type { ConstellationNode, ConstellationRipple, ConstellationPointer, ConstellationPalette, ConstellationField, ConstellationProps, } from "./constellationField";
2
+ export { seedField, stepField, readPalette, drawEdges, drawNodes, drawPointerWeb, drawRipples, nearestNode, warpStep, warpTo, setWarpTarget, BASE_WIDTH, DEFAULT_PALETTE, } from "./constellationField";
3
+ export type { ConstellationNode, ConstellationRipple, ConstellationPointer, ConstellationPalette, ConstellationField, ConstellationWarp, ConstellationProps, } from "./constellationField";
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;