@mkbabb/glass-ui 3.8.0 → 3.10.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 (228) hide show
  1. package/README.md +1 -6
  2. package/dist/{CardAction-q2qHCJFG.js → CardAction-DuArVky-.js} +4 -4
  3. package/dist/{CommandShortcut-SzWRhROC.js → CommandShortcut-CefCj5mt.js} +7 -7
  4. package/dist/{ContextMenuSubContent-Do977Cbf.js → ContextMenuSubContent-Bhg4f5jX.js} +3 -3
  5. package/dist/{DataTable-CvlWXz65.js → DataTable-CUHMukiQ.js} +1 -1
  6. package/dist/{DialogContent-DjZWT3lK.js → DialogContent-BRkT1cjP.js} +1 -1
  7. package/dist/{MetricBadge-lTc002u2.js → MetricBadge-BZVWIxNy.js} +6 -4
  8. package/dist/{Notification-BxK8LZMw.js → Notification-DLMhblXR.js} +2 -2
  9. package/dist/{NumberFieldContent-CLNVR1iY.js → NumberFieldContent-BbLOh8RU.js} +2 -2
  10. package/dist/{SelectScrollDownButton-C7TRe-ep.js → SelectScrollDownButton-T32cVFeM.js} +3 -3
  11. package/dist/{SelectSeparator-L8pPEn4L.js → SelectSeparator-C4Ql0gzV.js} +1 -1
  12. package/dist/{Switch-LDKLmmgx.js → Switch-Ch-k3Rv-.js} +1 -1
  13. package/dist/{TabsList-qyHZ2lKY.js → TabsList-B8CVOQPC.js} +2 -2
  14. package/dist/{Toaster-CabYEEF_.js → Toaster-1rYWrmAm.js} +1 -1
  15. package/dist/{ToggleGroupItem-CkXUcBlt.js → ToggleGroupItem-Cev4pOVK.js} +4 -4
  16. package/dist/animated-digit.js +1 -1
  17. package/dist/api/index.d.ts +2 -1
  18. package/dist/api.js +1 -1
  19. package/dist/aurora.js +236 -545
  20. package/dist/{badge-DHQrc_GQ.js → badge-CaoFJzRb.js} +5 -5
  21. package/dist/badge.js +1 -1
  22. package/dist/{button-CAywcrhO.js → button-DN39cGqL.js} +13 -12
  23. package/dist/button.js +1 -1
  24. package/dist/card.js +1 -1
  25. package/dist/carousel.js +1 -1
  26. package/dist/color.js +1 -1
  27. package/dist/command.js +1 -1
  28. package/dist/{compile-BBTRjvQS.js → compile-BOXjLUxu.js} +24 -24
  29. package/dist/components/custom/aurora/composables/atoms.d.ts +21 -4
  30. package/dist/components/custom/aurora/composables/runtime.d.ts +0 -9
  31. package/dist/components/custom/aurora/composables/uniformBridge.d.ts +0 -11
  32. package/dist/components/custom/aurora/constants/renderMode.d.ts +0 -32
  33. package/dist/components/custom/aurora/constants/shaders/brush.glsl.d.ts +1 -1
  34. package/dist/components/custom/aurora/constants/shaders/mediums.glsl.d.ts +2 -2
  35. package/dist/components/custom/aurora/constants/shaders/tonemap.glsl.d.ts +1 -1
  36. package/dist/components/custom/aurora/index.d.ts +1 -1
  37. package/dist/components/custom/configurator/ConfiguratorLayer.vue.d.ts +1 -1
  38. package/dist/components/custom/constellation/Constellation.vue.d.ts +73 -1
  39. package/dist/components/custom/constellation/constellationField.d.ts +255 -3
  40. package/dist/components/custom/constellation/index.d.ts +2 -2
  41. package/dist/components/custom/dock/DockSeparator.vue.d.ts +3 -0
  42. package/dist/components/custom/dock/DockTabButton.vue.d.ts +4 -3
  43. package/dist/components/custom/dock/GlassDock.vue.d.ts +45 -63
  44. package/dist/components/custom/dock/composables/dockContext.d.ts +7 -0
  45. package/dist/components/custom/dock/index.d.ts +2 -1
  46. package/dist/components/custom/expandable-container/ExpandableContainer.vue.d.ts +29 -0
  47. package/dist/components/custom/fourier-field/FourierField.vue.d.ts +6 -0
  48. package/dist/components/custom/fourier-field/index.d.ts +25 -1
  49. package/dist/components/custom/fourier-field/math.d.ts +3 -8
  50. package/dist/components/custom/goo-blob/GooBlob.vue.d.ts +8 -10
  51. package/dist/components/custom/goo-blob/composables/useBlobMood.d.ts +19 -5
  52. package/dist/components/custom/goo-blob/composables/useMetaballRenderer.d.ts +6 -14
  53. package/dist/components/custom/goo-blob/index.d.ts +1 -1
  54. package/dist/components/custom/goo-blob/types.d.ts +59 -25
  55. package/dist/components/custom/handmark/HandMark.vue.d.ts +29 -0
  56. package/dist/components/custom/handmark/brush.d.ts +219 -0
  57. package/dist/components/custom/handmark/freehand.d.ts +69 -0
  58. package/dist/components/custom/handmark/geometry.d.ts +32 -0
  59. package/dist/components/custom/handmark/index.d.ts +21 -0
  60. package/dist/components/custom/handmark/ink.d.ts +11 -0
  61. package/dist/components/custom/handmark/texture.d.ts +39 -0
  62. package/dist/components/custom/handmark/types.d.ts +53 -0
  63. package/dist/components/custom/handmark/useHandMark.d.ts +66 -0
  64. package/dist/components/custom/metric-badge/MetricBadge.vue.d.ts +8 -0
  65. package/dist/components/custom/tabs/SegmentedTabs.vue.d.ts +92 -0
  66. package/dist/components/custom/tabs/composables/useTabIndicator.d.ts +49 -0
  67. package/dist/components/custom/tabs/index.d.ts +2 -5
  68. package/dist/components/ui/button/index.d.ts +1 -1
  69. package/dist/components/ui/card/Card.vue.d.ts +5 -3
  70. package/dist/components/ui/carousel/CarouselNext.vue.d.ts +1 -1
  71. package/dist/components/ui/carousel/CarouselPrevious.vue.d.ts +1 -1
  72. package/dist/components/ui/carousel/GlassCarouselPager.vue.d.ts +3 -4
  73. package/dist/components/ui/slider/index.d.ts +22 -11
  74. package/dist/components/ui/tabs/TabsList.vue.d.ts +1 -1
  75. package/dist/composables/color/index.d.ts +12 -7
  76. package/dist/composables/context/createContext.d.ts +1 -1
  77. package/dist/composables/glass/webgl/shaders/procedural-color.glsl.d.ts +1 -6
  78. package/dist/composables/motion/core/index.d.ts +1 -0
  79. package/dist/composables/motion/useRouteTransition.d.ts +42 -0
  80. package/dist/configurator.js +1 -1
  81. package/dist/confirm-dialog.js +1 -1
  82. package/dist/constellation.js +212 -72
  83. package/dist/context-menu.js +1 -1
  84. package/dist/controls.js +1 -1
  85. package/dist/dark.js +1 -1
  86. package/dist/data-table.js +1 -1
  87. package/dist/deck-progress.js +1 -1
  88. package/dist/dialog.js +1 -1
  89. package/dist/dock.js +257 -243
  90. package/dist/{dropdown-menu-B_4bWyyG.js → dropdown-menu-D77jX6Df.js} +3 -3
  91. package/dist/dropdown-menu.js +1 -1
  92. package/dist/expandable-container.js +61 -38
  93. package/dist/forms.js +5 -5
  94. package/dist/fourier-field.js +89 -97
  95. package/dist/fourier-math.d.ts +1 -0
  96. package/dist/fourier-math.js +2 -0
  97. package/dist/glass-panel.js +1 -1
  98. package/dist/glass-ui.css +1 -1
  99. package/dist/glass-ui.js +107 -109
  100. package/dist/goo-blob.js +316 -297
  101. package/dist/handmark.d.ts +1 -0
  102. package/dist/handmark.js +609 -0
  103. package/dist/hover-popover.js +1 -1
  104. package/dist/icon-tooltip.js +1 -1
  105. package/dist/index.d.ts +1 -2
  106. package/dist/keyboard.js +1 -1
  107. package/dist/labeled-field.js +5 -5
  108. package/dist/math-DTpRVC0e.js +27 -0
  109. package/dist/{menuItemVariants-DKM85SNE.js → menuItemVariants-CCoBNkU5.js} +1 -1
  110. package/dist/metric-badge.js +1 -1
  111. package/dist/metric-stack.js +1 -1
  112. package/dist/motion-core.js +70 -69
  113. package/dist/motion.js +2 -2
  114. package/dist/notification.js +1 -1
  115. package/dist/number-field.js +1 -1
  116. package/dist/pulse.js +1 -1
  117. package/dist/reactive.js +1 -1
  118. package/dist/scrolling-text.js +1 -1
  119. package/dist/search.js +4 -4
  120. package/dist/select.js +2 -2
  121. package/dist/{slider-D9puf-Ko.js → slider-CyzcxBoh.js} +6 -6
  122. package/dist/slider.js +1 -1
  123. package/dist/sortable-list.js +1 -1
  124. package/dist/status-dot.js +17 -16
  125. package/dist/styles/animations.css +80 -20
  126. package/dist/styles/cards.css +2 -2
  127. package/dist/styles/components.css +1 -1
  128. package/dist/styles/dock/density.css +230 -0
  129. package/dist/styles/dock/layer-group.css +275 -0
  130. package/dist/styles/dock/layers.css +355 -0
  131. package/dist/styles/dock/morph.css +244 -0
  132. package/dist/styles/dock/overflow.css +201 -0
  133. package/dist/styles/dock/shell.css +457 -0
  134. package/dist/styles/dock-controls.css +205 -55
  135. package/dist/styles/dock.css +42 -1349
  136. package/dist/styles/drawer.css +12 -4
  137. package/dist/styles/glass.css +535 -78
  138. package/dist/styles/index.css +12 -11
  139. package/dist/styles/paper.css +68 -2
  140. package/dist/styles/theme.css +58 -0
  141. package/dist/styles/tokens.css +601 -69
  142. package/dist/styles/transitions.css +20 -2
  143. package/dist/styles/utilities.css +218 -78
  144. package/dist/styles/view-transition.css +51 -0
  145. package/dist/switch.js +1 -1
  146. package/dist/tabs.js +182 -151
  147. package/dist/timeline.js +1 -1
  148. package/dist/toast.js +1 -1
  149. package/dist/toggle-group.js +1 -1
  150. package/dist/{useAnimatedNumber-C_3wZLx4.js → useAnimatedNumber-Bcpy5Etf.js} +1 -1
  151. package/dist/{useConfiguratorState-CMff4GzH.js → useConfiguratorState-DxAqksK6.js} +21 -10
  152. package/dist/{useGlassRenderer-5I0YkI4t.js → useGlassRenderer-D7YGOYiM.js} +1 -1
  153. package/dist/useViewTransition-DaN5Zrox.js +21 -0
  154. package/dist/vReveal-zUIQk-rJ.js +53 -0
  155. package/dist/watercolor-dot.js +1 -1
  156. package/package.json +80 -39
  157. package/src/styles/animations.css +80 -20
  158. package/src/styles/cards.css +2 -2
  159. package/src/styles/dock/density.css +230 -0
  160. package/src/styles/dock/layer-group.css +275 -0
  161. package/src/styles/dock/layers.css +355 -0
  162. package/src/styles/dock/morph.css +244 -0
  163. package/src/styles/dock/overflow.css +201 -0
  164. package/src/styles/dock/shell.css +457 -0
  165. package/src/styles/dock-controls.css +205 -55
  166. package/src/styles/dock.css +42 -1349
  167. package/src/styles/drawer.css +12 -4
  168. package/src/styles/glass.css +535 -78
  169. package/src/styles/index.css +12 -11
  170. package/src/styles/paper.css +68 -2
  171. package/src/styles/theme.css +58 -0
  172. package/src/styles/tokens.css +601 -69
  173. package/src/styles/transitions.css +20 -2
  174. package/src/styles/utilities.css +218 -78
  175. package/src/styles/view-transition.css +51 -0
  176. package/dist/DiscoGlyph-CG0F410s.js +0 -132
  177. package/dist/GlyphFace-DRjDkB0s.js +0 -51
  178. package/dist/UnderlineTabs-Uxn-R3MT.js +0 -39
  179. package/dist/components/custom/aurora/composables/gpuRuntime.d.ts +0 -16
  180. package/dist/components/custom/aurora/constants/shaders/aurora.wgsl.d.ts +0 -7
  181. package/dist/components/custom/aurora/constants/shaders/painterly.wgsl.d.ts +0 -7
  182. package/dist/components/custom/aurora/constants/shaders/wake.wgsl.d.ts +0 -4
  183. package/dist/components/custom/disco-glyph/DiscoGlyph.vue.d.ts +0 -58
  184. package/dist/components/custom/disco-glyph/index.d.ts +0 -1
  185. package/dist/components/custom/glass-carousel/GlassCarousel.vue.d.ts +0 -36
  186. package/dist/components/custom/glass-carousel/GlassCarouselItem.vue.d.ts +0 -28
  187. package/dist/components/custom/glass-carousel/index.d.ts +0 -4
  188. package/dist/components/custom/glass-carousel/useGlassCarousel.d.ts +0 -30
  189. package/dist/components/custom/glyph-face/GlyphFace.vue.d.ts +0 -64
  190. package/dist/components/custom/glyph-face/index.d.ts +0 -2
  191. package/dist/components/custom/glyph-face/keys.d.ts +0 -9
  192. package/dist/components/custom/responsive-tabs/ResponsiveTabs.vue.d.ts +0 -73
  193. package/dist/components/custom/responsive-tabs/index.d.ts +0 -2
  194. package/dist/components/custom/tabs/BouncyTabs.vue.d.ts +0 -27
  195. package/dist/components/custom/tabs/BouncyToggle.vue.d.ts +0 -60
  196. package/dist/components/custom/tabs/UnderlineTabs.vue.d.ts +0 -20
  197. package/dist/components/custom/tabs/composables/useBouncySlider.d.ts +0 -39
  198. package/dist/composables/glass/createGPUCanvas.d.ts +0 -40
  199. package/dist/disco-glyph.d.ts +0 -1
  200. package/dist/disco-glyph.js +0 -2
  201. package/dist/glass-carousel.d.ts +0 -1
  202. package/dist/glass-carousel.js +0 -219
  203. package/dist/glyph-face.d.ts +0 -1
  204. package/dist/glyph-face.js +0 -3
  205. package/dist/keys-BsYjEFOW.js +0 -9
  206. package/dist/responsive-tabs.d.ts +0 -1
  207. package/dist/responsive-tabs.js +0 -95
  208. package/dist/styles/disco-glyph.css +0 -26
  209. package/dist/styles/glyph-face.css +0 -92
  210. package/dist/vReveal-CqaCz_mC.js +0 -44
  211. package/src/styles/disco-glyph.css +0 -26
  212. package/src/styles/glyph-face.css +0 -92
  213. /package/dist/{DeckProgress-AVgbIULY.js → DeckProgress-Dmk4_AKN.js} +0 -0
  214. /package/dist/{HoverPopover-D56O6Ylp.js → HoverPopover-2LOIW34a.js} +0 -0
  215. /package/dist/{IconTooltip-D2T5nYOM.js → IconTooltip-Cus0fvK0.js} +0 -0
  216. /package/dist/{Input-DVG_J0ne.js → Input-xuDwALQR.js} +0 -0
  217. /package/dist/{ScrollingText-CVyqxvfv.js → ScrollingText-BPP84wKo.js} +0 -0
  218. /package/dist/{color-DMLTunJK.js → color-C5ls_QF7.js} +0 -0
  219. /package/dist/{constants-BPOqYJAm.js → constants-yKeKnE84.js} +0 -0
  220. /package/dist/{presets-D1XPuTj-.js → presets-Dkdv55Gt.js} +0 -0
  221. /package/dist/{prng-COGhh02-.js → prng-D27-B3tK.js} +0 -0
  222. /package/dist/{useGlobalDark-D3EBWzXK.js → useGlobalDark-d6waty-X.js} +0 -0
  223. /package/dist/{useIntersectionPause-Bj4sPkMh.js → useIntersectionPause-Cck5Uxk8.js} +0 -0
  224. /package/dist/{useInterval-CcLfq2WU.js → useInterval-Dsmqwi03.js} +0 -0
  225. /package/dist/{useKeyboardShortcuts-BXWXOZ-9.js → useKeyboardShortcuts-CyIYrndH.js} +0 -0
  226. /package/dist/{useScrollProgress-C9ysDic5.js → useScrollProgress-CffFGLa5.js} +0 -0
  227. /package/dist/{useSortable-MGAbCGVJ.js → useSortable-C8ReQ50P.js} +0 -0
  228. /package/dist/{useTextHighlight-9nMW23Qb.js → useTextHighlight-CWEi25hf.js} +0 -0
@@ -1,2 +1,2 @@
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 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}";
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 // The blend weight is a low-power curve (A^0.35) so even MODERATE-coherence zones queue\n // strongly along the tangent (a flowing van-Gogh band reads coherent the way the\n // structure-tensor metric measures it; a linear A under-orients the mid-band and the\n // rendered field reads too isotropic \u2014 the \u00A74.2 anisotropy lift). The flat-zone (A\u22480)\n // relax to the fallback survives (0^0.35=0), so tensor noise never reads as jitter.\n float blendW = pow(A, 0.28);\n vec2 dir = normalize(mix(fallbackDir, tangent, blendW) + 1e-6);\n return vec3(dir, A);\n}\n\nvec3 mediumWatercolor(vec3 col, vec2 p, float t) {\n // Wet-edge cauliflowers via luma-gradient magnitude\n float eps = 0.004;\n vec3 cx1 = sampleBase(p + vec2(eps, 0.0), t);\n vec3 cx2 = sampleBase(p - vec2(eps, 0.0), t);\n vec3 cy1 = sampleBase(p + vec2(0.0, eps), t);\n vec3 cy2 = sampleBase(p - vec2(0.0, eps), t);\n float gx = dot(cx1 - cx2, W_LUMA);\n float gy = dot(cy1 - cy2, W_LUMA);\n float edge = sqrt(gx * gx + gy * gy) / (2.0 * eps);\n float mask = smoothstep(0.0, 2.5, edge);\n col *= mix(1.0, 0.78, mask * uWetEdge);\n\n // Granulation \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 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.92, // densityFill \u2014 oil fills DENSELY with the directional low-relief fill so the\n // flat-colour regions between the macro strokes are TEXTURED with\n // flow-aligned fine strokes (the \u00A74.2 anisotropy needs every pixel\n // to carry directional gradient; a flat untextured region reads as\n // an isotropic/degenerate tensor and drags mean A down). The fill\n // is now directional + low-streak, so dense coverage textures\n // WITHOUT the prior round-dab speckle.\n 1.0 // groundFloor \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; // within-stroke streaks (the loaded comma) \u2014 kept in the\n // FFT inertial band (a too-fine streak falls past the \u00A74.3\n // resolved-band edge and reads as steeper, not shallower)\n prof.streakAmp = 0.22;\n prof.impastoAmp = 1.1; // paint per atomic dab \u2014 eased off 1.5 so the impasto crown\n // does not push the dab past the PBR-Neutral 0.8 highlight\n // knee (where it compresses + loses the directional contrast\n // the \u00A74.2 anisotropy reads)\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 = 0.60; // the van-Gogh chroma \u2014 pulled DOWN off the hand-set 1.12\n // so the rendered colorfulness lands inside [55.67, 95.67]\n // (the hand-set punch + the saturated indigo/cobalt/yellow\n // ramp pushed C past the garish ceiling \u2014 \u00A74.1 bidirectional)\n // SPARSE-BUT-FLOWING placement \u2014 visible inter-stroke canvas gaps (the atomicity read)\n // yet enough directional coverage that the swirl-rows read as coherent flowing bands\n // (the \u00A74.2 anisotropy needs the strokes to dominate the gradient field; too-sparse\n // gates left large flat-ground regions that read isotropic and dragged mean A down).\n prof.densityBig = 0.58;\n prof.densityMed = 0.68;\n prof.densitySml = 0.74;\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.30; // a LIGHT directional fill \u2014 textures the dark ground with\n // flow-aligned fine strokes so the gaps carry directional\n // gradient (the \u00A74.2 A lift: an untextured dark ground reads\n // isotropic) WHILE staying open enough that the bright atomic\n // dabs still read separable over it (the gap-fraction floor\n // stays satisfied \u2014 the fill is fine + dark, not a smear).\n prof.groundFloor = 0.31; // darken the bare-ground gaps to the visible Starry-Night\n // underpainting so the bright impasto dabs read separable \u2014\n // the dab\u2194ground contrast injects MID-scale energy (the \u00A74.3\n // \u03B2-flatten: the PBR-Neutral highlight compression concentrated\n // energy at low-k and steepened \u03B2). 0.31 is the balance point:\n // deep enough to hold \u03B2 in-band, light enough that the textured\n // ground keeps the \u00A74.2 coherence the too-dark 0.26 floor cost.\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 = 0; // tapered directional smear \u2014 a round dab reads locally\n // isotropic (steep \u03B2 / low A); the creamy oil-pastel mark\n // is a broad DIRECTIONAL smear, which carries the \u00A74.2\n // coherence + adds the directional mid-band the \u00A74.3 slope\n // needs (the round-dab profile rolled \u03B2 off to \u22122.8)\n prof.bristleAmp = 0.10; // smooth creamy edges (waxy pastel, not bristle)\n prof.streakFreq = 18.0; // finer internal smear \u2014 adds the mid/high-band the smooth\n // round-dab profile lacked (the \u03B2-too-steep fix); the\n // directional along-spine streak flattens the \u00A74.3 slope\n // without crossing the strokes\n prof.streakAmp = 0.20;\n prof.impastoAmp = 0.7; // build-up, but flatter than oil impasto\n prof.hardness = 0.42; // CREAMY \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 = 0.80; // the waxy chroma \u2014 eased off 1.16 so the rendered C clears\n // the \u00A74.1 band ceiling with margin while the K-M subtractive\n // overlap path (paintOverOklab) keeps the chroma OFF the grey floor\n prof.hardness = 0.58; // creamy but the strokes still REGISTER \u2014 a too-soft blend\n // (0.42) smeared the marks into the smooth colour zones and\n // the field rolled off too steep (\u03B2\u2248\u22122.9, a near-gradient);\n // a crisper deposit keeps the mid/high-band the \u22125/3 slope needs\n prof.densityBig = 0.62; // broad directional coverage \u2014 but NOT over-dense: too many\n // overlapping creamy strokes point in slightly-varied\n // directions and the local \u00A74.2 coherence DROPS (over-dense\n // measured A=0.57 < the 0.62-density A=0.63). The mid-density\n // flow-aligned deposit is the coherence sweet spot.\n prof.densityMed = 0.78;\n prof.densitySml = 0.90;\n prof.energyGrade = 0.80; // a strong Starry-Night length cascade \u2014 varies stroke length\n // by luma/coherence so the dab-size energy spreads across\n // scales (the \u22125/3 cascade the uniform-length 0.0 grade lacked,\n // the \u00A74.3 \u03B2-too-steep fix). Not the full van-Gogh 1.0 (oil-\n // pastel keeps its creamy broad character).\n prof.impastoFloor = 0.55; // a softer crown than van-Gogh's full-height glint\n return prof;\n }\n\n // \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 // W-AUR-STUDIO D5 \u2014 the \u22125/3 radii respacing was MEASURED + REVERTED (recorded, not\n // silently struck). The \u03C6-adjacent geometric candidate (2.4 / 1.45 / 0.87, a fixed\n // \u22481.66\u00D7 step) moved oil-pastel \u03B2 \u22122.534 \u2192 \u22122.413 \u2014 TOWARD the \u22125/3 band but NOT into\n // it (floor \u22121.85), so the keep-iff-into-band gate failed. The single-pass WebGL2 path\n // is the true limiter (HC-aurora \u00A75): the oil-pastel \u03B2 + the oil/oil-pastel anisotropy\n // residual is the named AY.W-AUR-T5 multi-pass anisotropic-Kuwahara candidate. The\n // hand-set 2.4 / 1.1 / 0.45 stays \u2014 it is van-Gogh's landed-band spacing.\n float sBig = baseScale * 2.4;\n float sMed = baseScale * 1.1;\n float sSml = baseScale * 0.45;\n\n // Strokes are elongated HARD (long\u00B7thin) so the rendered field reads strongly\n // directional \u2014 the \u00A74.2 structure-tensor anisotropy lift. A short fat dab is locally\n // isotropic and drags the measured A down; a long thin stroke that hugs the tensor\n // tangent is what gives Starry Night its A\u22480.83. The width muls are pulled tighter to\n // keep the long\u00B7thin aspect.\n float lenMulBig = mix(3.0, 5.2, uStrokeAnisotropy);\n float widMulBig = mix(0.42, 0.22, uStrokeAnisotropy);\n float lenMulMed = mix(2.8, 4.8, uStrokeAnisotropy);\n float widMulMed = mix(0.40, 0.21, uStrokeAnisotropy);\n float lenMulSml = mix(2.2, 3.6, uStrokeAnisotropy);\n float widMulSml = mix(0.36, 0.23, uStrokeAnisotropy);\n\n float jitterAmt = 0.75; // large jitter \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 strokes (more frequent, smaller). Even the knife layer-3 stays the\n // medium's DIRECTIONAL shape, not a round dab \u2014 the round-dab sparkle was a fine-scale\n // isotropic speckle that dragged the \u00A74.2 anisotropy down + rolled the \u00A74.3 slope shallow.\n int smlShape = prof.shapeType;\n StrokeHit hSml = bestOil(p + vec2(-5.1, 8.4), sSml, lenMulSml, widMulSml,\n jitterAmt * 0.9, prof.densitySml, smlShape,\n prof.bristleAmp * 0.85, flow, t, 4.1, prof.energyGrade);\n paintOver(col, height, hSml, prof.streakFreq * 1.4, prof.streakAmp * 0.6,\n uImpasto * prof.impastoAmp * 0.45 * uStrokeAmount, prof.hardness, 4.1, prof.impastoFloor);\n\n // Layer 4 \u2014 fill strokes (dense, fine) \u2014 cover bald spots. The density is\n // PROFILE-driven (prof.densityFill): oil/oil-pastel fill to full coverage; van-Gogh\n // keeps it sparse so the bare-ground gaps between its atomic dabs survive. The fill is\n // ELONGATED + flow-oriented (NOT a round dab) \u2014 a dense round-dab fill is locally\n // isotropic and was the dominant \u00A74.2 anisotropy sink; an oriented fine stroke keeps the\n // fill directional so the whole field reads coherent.\n float sFill = baseScale * 0.22;\n float lenMulFill = mix(2.0, 3.2, uStrokeAnisotropy);\n float widMulFill = mix(0.34, 0.24, uStrokeAnisotropy);\n int fillShape = (mode == 1) ? 3 : prof.shapeType; // knife=even; others inherit the medium's directional shape\n StrokeHit hFill = bestOil(p + vec2(3.9, -6.2), sFill, lenMulFill, widMulFill,\n jitterAmt * 0.8, prof.densityFill, fillShape,\n prof.bristleAmp * 0.6, flow, t, 8.9, prof.energyGrade);\n // The fill is a low-relief, low-streak base coat \u2014 it covers bald spots WITHOUT adding\n // fine bright speckle (a high-impasto/high-streak fill caught a per-dab glint that read\n // as isotropic noise over the directional macro strokes \u2014 the \u00A74.2/\u00A74.3 sink). Its\n // positional jitter is tamed too so the fill strokes stay flow-aligned, not scattered.\n paintOver(col, height, hFill, prof.streakFreq * 1.2, prof.streakAmp * 0.35,\n uImpasto * prof.impastoAmp * 0.18 * uStrokeAmount, prof.hardness * 0.9, 8.9, prof.impastoFloor);\n\n // Optional crosshatch layer\n if (uStrokeLayers == 2) {\n vec2 flow2 = vec2(-flow.y, flow.x);\n StrokeHit hX = bestOil(p + vec2(7.3, -2.1), sMed, lenMulMed * 0.9, widMulMed,\n jitterAmt, prof.densityMed * 0.7, prof.shapeType, prof.bristleAmp, flow2, t, 6.5,\n prof.energyGrade);\n paintOver(col, height, hX, prof.streakFreq, prof.streakAmp * 0.85,\n uImpasto * prof.impastoAmp * 0.55 * uStrokeAmount, prof.hardness, 6.5, prof.impastoFloor);\n }\n}\n\n// The shared stroke-medium SUBSTRATE finish (AX.W12 + AX.W13): paint the four-layer\n// cascade off the profile, then canvas-tooth + relight + saturation. The three painterly\n// STROKE mediums (oil / van-Gogh / oil-pastel) each AUTHOR their own profile and call\n// this \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}";
@@ -1 +1 @@
1
- export declare const AURORA_TONEMAP_GLSL = "// ACES approximation\nvec3 aces(vec3 x) {\n float a = 2.51;\n float b = 0.03;\n float c = 2.43;\n float d = 0.59;\n float e = 0.14;\n return clamp((x * (a * x + b)) / (x * (c * x + d) + e), 0.0, 1.0);\n}\n";
1
+ export declare const AURORA_TONEMAP_GLSL = "// Khronos PBR-Neutral tonemap (hue + saturation preserving)\nvec3 aces(vec3 color) {\n const float startCompression = 0.8 - 0.04;\n const float desaturation = 0.15;\n float x = min(color.r, min(color.g, color.b));\n float offset = x < 0.08 ? x - 6.25 * x * x : 0.04;\n color -= offset;\n float peak = max(color.r, max(color.g, color.b));\n if (peak < startCompression) return clamp(color, 0.0, 1.0);\n float d = 1.0 - startCompression;\n float newPeak = 1.0 - d * d / (peak + d - startCompression);\n color *= newPeak / peak;\n float g = 1.0 - 1.0 / (desaturation * (peak - newPeak) + 1.0);\n return clamp(mix(color, newPeak * vec3(1.0), g), 0.0, 1.0);\n}\n";
@@ -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, nucleiPrior, DEFAULT_ATOMS, type AuroraAtoms, type AuroraMotionAtom, type AuroraZones, type AuroraZoneArrangement, type AuroraMediumAtom, type AuroraInteractivityAtom, } from "./composables/atoms";
9
+ export { resolveAtoms, configToAtoms, nucleiPrior, DEFAULT_ATOMS, type AuroraAtoms, type AuroraMotionAtom, type AuroraZones, type AuroraZoneArrangement, type AuroraMediumAtom, type AuroraInteractivityAtom, } from "./composables/atoms";
10
10
  export { cssToOklch, deriveAurora, flattenPalette, hexToOklchStop, oklchStopToHex, oklchToLinear, paletteToCssGradient, } from "./composables/color";
11
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
@@ -47,19 +47,81 @@ type __VLS_Props = {
47
47
  * method (via `defineExpose`) is the seam this prop sugars.
48
48
  */
49
49
  warpOnClick?: boolean;
50
+ /**
51
+ * Auto-DRIFT (AY.W-CON1). A periodic auto-pick re-points the focal node to a
52
+ * RANDOM node on a jittered cadence — the wander source on the SAME warp
53
+ * spring (the 2nd half of the AX.W17 "drift + warp are ONE mechanic" thesis;
54
+ * no second rAF, no second mechanic). `true` uses the default cadence
55
+ * (8–16s, the slides rhythm); `{ minIdle?, jitter? }` tunes it (ms). Default
56
+ * OFF — `wander` absent leaves `field.wander` undefined, byte-identical to
57
+ * HEAD. PRM-gated by the WARP precedent: the cadence lives inside the
58
+ * `!reducedMotion` step block, so under reduced-motion it never advances and
59
+ * the focal mark stays at its seed (NOT fire-but-freeze).
60
+ */
61
+ wander?: boolean | {
62
+ minIdle?: number;
63
+ jitter?: number;
64
+ };
65
+ /**
66
+ * Pointer-held GRAVITY-WELL (AY.W-CON2). Hold the pointer over the lattice and
67
+ * the nodes within reach are PULLED toward it (an inverse-square force on the
68
+ * SAME engine, no second rAF); release and the field cools back to `speed`.
69
+ * INDEPENDENT of `warpOnClick` and `pointerReactive` (a consumer can
70
+ * hold-to-pull on a non-ripple, non-warp lattice). `true` uses the tokenised
71
+ * `--constellation-well-*` defaults; an object overrides the gains. Default
72
+ * OFF — `gravityWell` absent leaves `field.well` undefined (byte-identical to
73
+ * HEAD; `stepField` skips the force pass). PRM-gated by the WARP precedent: the
74
+ * held-timer block lives inside `!reducedMotion`, so under reduced-motion the
75
+ * well never arms; AND the well STATE resets to neutral on the PRM-true edge
76
+ * (no half-ramped force frozen on).
77
+ */
78
+ gravityWell?: boolean | {
79
+ holdMs?: number;
80
+ gain?: number;
81
+ reach?: number;
82
+ ramp?: number;
83
+ maxSpeed?: number;
84
+ soften?: number;
85
+ };
86
+ /**
87
+ * Deterministic-capture freeze (AY.W-CON3). When `true`, the lattice lays
88
+ * out ONE reproducible STATIC frame and does NOT advance — seeded layout
89
+ * (set `seed` for a stable field ACROSS runs; an unseeded freeze still
90
+ * freezes but lays out a one-shot `Math.random` frame), no `stepField`, no
91
+ * ripple / warp / wander / well advance, and a FROZEN `now` handed to
92
+ * `drawOverlay` so a phase-driven skin (`(now % T) / T`) resolves to a fixed
93
+ * value (the pulse-ring radius is identical frame-over-frame). Omit to
94
+ * AUTO-DERIVE from `location.search` matching `export | print | freeze` (the
95
+ * deploy-pipeline contract — a consumer's pptx / print / screenshot capture
96
+ * gets a stable frame with zero per-consumer wiring). An explicit
97
+ * `:freeze="false"` forces live even under a capture URL. Unifies with the
98
+ * reduced-motion one-static-frame path — `freeze || reducedMotion` is the
99
+ * single static-frame predicate; the capture takes no input (the pointer /
100
+ * warp / well listeners are NOT registered under freeze).
101
+ */
102
+ freeze?: boolean;
50
103
  class?: HTMLAttributes["class"];
51
104
  /**
52
105
  * The skin seam. Runs after the neutral passes with the live field, so a
53
106
  * consumer paints its own focal mark pinned to a real node — read
54
107
  * `field.warp.{x,y}` for the spring-eased focal position when `warpOnClick`
55
108
  * is on. The branded content (a domain accent, a callout label) lives HERE,
56
- * in the consumer.
109
+ * in the consumer. Under `freeze` it receives a FROZEN `now`, so a phase-
110
+ * driven mark resolves to a fixed value.
57
111
  */
58
112
  drawOverlay?: (ctx: CanvasRenderingContext2D, field: ConstellationField, now: number) => void;
59
113
  };
60
114
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
61
115
  /** The live field (the low-level imperative seam — for a custom overlay). */
62
116
  field: ConstellationField;
117
+ /**
118
+ * The resolved deterministic-capture predicate (AY.W-CON3) — `true` when the
119
+ * lattice is laying out a frozen static frame (`freeze` prop OR a
120
+ * `?export|print|freeze` URL OR reduced-motion via the substrate). A read-only
121
+ * test/debug seam (the π freeze-live spec reads it to confirm the auto-derive
122
+ * fired without inferring from drift).
123
+ */
124
+ isFrozen: import("vue").ComputedRef<boolean>;
63
125
  /**
64
126
  * Imperative warp (AX.W17). Two call shapes:
65
127
  * `warpTo(localPoint)` — `{x, y}` already in canvas-local px
@@ -75,6 +137,16 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
75
137
  x: number;
76
138
  y: number;
77
139
  } | number, b?: number): number;
140
+ /**
141
+ * Imperatively ARM the gravity-well at a canvas-LOCAL point (AY.W-CON2) — the
142
+ * π-lane test seam that drives the well WITHOUT racing a real held-pointer
143
+ * gesture + the held-timer. Sets the well position + `target = 1` directly (the
144
+ * timer is bypassed; the ramp still eases `strength` over the next frames). No-op
145
+ * when `gravityWell` is off (no `field.well`). Returns true when the well armed.
146
+ */
147
+ holdWellAt(x: number, y: number): boolean;
148
+ /** Imperatively RELEASE the well (AY.W-CON2) — `target → 0`, the field cools. */
149
+ releaseWell(): void;
78
150
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
79
151
  declare const _default: typeof __VLS_export;
80
152
  export default _default;
@@ -51,6 +51,98 @@ export interface ConstellationWarp {
51
51
  /** The chased node's INDEX (live target), or -1 when inactive. */
52
52
  targetIdx: number;
53
53
  }
54
+ /**
55
+ * The auto-DRIFT target-source (AY.W-CON1 — the 2nd half of the AX.W17 "drift +
56
+ * warp are ONE mechanic" thesis). The warp re-points its focal node to a
57
+ * periodically-chosen RANDOM node on a jittered cadence: the SAME warp spring,
58
+ * a different target-PICKER (a click for warp, this cadence for drift). No new
59
+ * rAF, no second mechanic — `stepField` steps this cadence inline, re-pointing
60
+ * `setWarpTarget` to a picked node only when the spring has SETTLED (so a
61
+ * click-warp in flight always pre-empts the cadence). PRM is enforced by the
62
+ * CALLER (the component does not advance the cadence under reduced-motion — the
63
+ * `stepField` call lives inside the `!reducedMotion` block — so the focal mark
64
+ * stays at its seed: the WARP precedent, NOT fire-but-freeze).
65
+ */
66
+ export interface ConstellationWander {
67
+ /** ms timestamp of the next auto re-target; -1 until armed on the first stepped frame. */
68
+ nextAt: number;
69
+ /** the minimum idle (ms) between auto re-targets. */
70
+ minIdle: number;
71
+ /** the random extra idle (ms) added per cadence (so the rhythm is not metronomic). */
72
+ jitter: number;
73
+ }
74
+ /**
75
+ * Auto-DRIFT cadence DEFAULTS (AY.W-CON1 source; tokenised in AY.W-CON2). The
76
+ * `--constellation-wander-idle`/`-wander-jitter` tokens override these on mount via
77
+ * `readInteractionConfig` (the SAME numeric `--constellation-*` cohort as the warp
78
+ * spring + the well gains); a prop `{ minIdle, jitter }` still wins over the token.
79
+ * The 8s/8s default rhythm re-targets the focal mark every 8–16s (the slides cadence).
80
+ */
81
+ export declare const DEFAULT_WANDER_IDLE = 8000;
82
+ export declare const DEFAULT_WANDER_JITTER = 8000;
83
+ /**
84
+ * The warp-spring tuning the integrator reads (AY.W-CON2). Tokenised numeric (the
85
+ * `--constellation-warp-*` cohort) so a consumer retunes the warp WITHOUT editing
86
+ * src — read ONCE on mount via `parseFloat`, threaded onto the field.
87
+ *
88
+ * `response` is the keyframes.js `(response, dampingFraction)` model's ANGULAR
89
+ * PERIOD (the SwiftUI `.spring(response:)` axis): `ω₀ = 2π/response`. It is NOT a
90
+ * settle-duration — at ζ=1 the 2%-settle lands at `t₂ ≈ 5.83/ω₀ ≈ 0.93·response`
91
+ * (the `(1 + ω₀t)e^(−ω₀t)` critically-damped envelope). Documented here + in the
92
+ * `--constellation-warp-response` token comment so the token semantics are honest
93
+ * at ANY ζ (the AY.W-CON2 ω-reconcile; the engine keeps the keyframes.js convention,
94
+ * mints NO second ω formula).
95
+ */
96
+ export interface ConstellationWarpConfig {
97
+ /** keyframes.js angular period (s) — `ω₀ = 2π/response`, NOT a settle-duration. */
98
+ response: number;
99
+ /** damping ratio ζ — 1 critically damped (a focal mark must NOT ring). */
100
+ zeta: number;
101
+ }
102
+ /**
103
+ * The gravity-well tuning (AY.W-CON2). The held-pointer pull is an inverse-square
104
+ * force composed into `stepField` (no new rAF). Tokenised numeric (the
105
+ * `--constellation-well-*` cohort) so a consumer retunes the well WITHOUT editing
106
+ * src. All distances are base-width px (`k`-scaled at step time).
107
+ */
108
+ export interface ConstellationWellConfig {
109
+ /** the inverse-square pull gain (the force scale at unit distance). */
110
+ gain: number;
111
+ /** the reach (base-width px) — nodes beyond it are untouched (the O(within-reach) cost floor). */
112
+ reach: number;
113
+ /**
114
+ * the ARM ramp rate (1/s) — `strength` eases toward `target = 1` at this rate
115
+ * while HELD (the gentle bloom the token tunes). The RELEASE is NOT this rate:
116
+ * it is the fixed brisk {@link WELL_RELEASE_RAMP} (the field-cools invariant —
117
+ * not consumer-tunable, F8.2). A consumer slowing this token slows the ARM
118
+ * bloom only; the release stays brisk so the cool gate holds.
119
+ */
120
+ ramp: number;
121
+ /** the per-node speed cap (base-width px/frame) — the no-slingshot clamp. */
122
+ maxSpeed: number;
123
+ /** the singularity floor (px) — a node AT the cursor gets a bounded pull, never `∞`. */
124
+ soften: number;
125
+ /** ms the pointer is held before the well arms. */
126
+ holdMs: number;
127
+ }
128
+ /**
129
+ * The gravity-well live STATE (AY.W-CON2). `(x, y)` is the held pointer in
130
+ * canvas-local px (`-1` = inactive); `strength` is the eased pull 0→1 (ramps to
131
+ * `target` at `cfg.ramp`/s); `target` is the strength the ramp eases toward (1
132
+ * while held, 0 on release). A transient force — the field renormalises back to
133
+ * `speed` after release (the field-cools invariant).
134
+ */
135
+ export interface ConstellationWell {
136
+ /** canvas-local px of the held pointer; `-1` = inactive. */
137
+ x: number;
138
+ y: number;
139
+ /** the eased pull strength 0→1 (ramps to `target` at `cfg.ramp`/s). */
140
+ strength: number;
141
+ /** the strength the ramp is easing toward: 1 while held, 0 on release. */
142
+ target: number;
143
+ /** the well tuning (gain/reach/ramp/maxSpeed/soften). */
144
+ cfg: ConstellationWellConfig;
145
+ }
54
146
  /**
55
147
  * The field state the component exposes to its `drawOverlay` consumer so a
56
148
  * skin can pin itself to a real field node. `k` is the `width / BASE_WIDTH`
@@ -79,6 +171,28 @@ export interface ConstellationField {
79
171
  focalIndex: number;
80
172
  /** The per-axis warp spring the engine steps inside `stepField` (AX.W17). */
81
173
  warp: ConstellationWarp;
174
+ /**
175
+ * The warp-spring tuning `warpStep` reads (AY.W-CON2). Optional — absent
176
+ * falls back to the shipped `{ response: 0.55, zeta: 1.0 }` defaults (the
177
+ * byte-identical HEAD spring). A tokenised override (read on mount) reaches
178
+ * the integrator through this member.
179
+ */
180
+ warpCfg?: ConstellationWarpConfig;
181
+ /**
182
+ * The optional auto-DRIFT cadence (AY.W-CON1). When set, `stepField`
183
+ * periodically re-points the warp to a random node (the wander source on the
184
+ * SAME spring). Absent (`undefined`) leaves the field BYTE-IDENTICAL to the
185
+ * pre-wander HEAD — `stepField` skips the cadence block entirely.
186
+ */
187
+ wander?: ConstellationWander;
188
+ /**
189
+ * The optional gravity-well (AY.W-CON2). When set, `stepField` composes a
190
+ * held-pointer inverse-square pull force (no new rAF) — `well.target → 1`
191
+ * while held, `→ 0` on release, the field renormalising back to `speed` once
192
+ * the well cools. Absent (`undefined`) leaves the field BYTE-IDENTICAL to the
193
+ * pre-well HEAD — `stepField` skips the force pass entirely.
194
+ */
195
+ well?: ConstellationWell;
82
196
  }
83
197
  /**
84
198
  * The `<Constellation>` consumer prop surface (the public type a consumer types
@@ -103,6 +217,46 @@ export interface ConstellationProps {
103
217
  * reduced-motion (the focal mark stays put — the stated PRM policy).
104
218
  */
105
219
  warpOnClick?: boolean;
220
+ /**
221
+ * Auto-DRIFT (AY.W-CON1): a periodic auto-pick re-points the focal node to a
222
+ * random node on a jittered cadence — the wander source on the SAME warp
223
+ * spring (no second mechanic). `true` uses the default cadence (8–16s, the
224
+ * slides rhythm); `{ minIdle, jitter }` tunes it. Default OFF (absent →
225
+ * byte-identical to HEAD). PRM-gated by the WARP precedent (the cadence never
226
+ * advances under reduced-motion — the focal stays at its seed).
227
+ */
228
+ wander?: boolean | {
229
+ minIdle?: number;
230
+ jitter?: number;
231
+ };
232
+ /**
233
+ * Pointer-held GRAVITY-WELL (AY.W-CON2): hold the pointer and the lattice is
234
+ * pulled toward it (an inverse-square force on the same engine, no new rAF);
235
+ * release and the field cools back to `speed`. INDEPENDENT of `warpOnClick`
236
+ * and `pointerReactive` (a consumer can hold-to-pull on a non-ripple,
237
+ * non-warp lattice). `true` uses the tokenised defaults; an object tunes the
238
+ * gains. Default OFF (absent → byte-identical to HEAD). PRM-gated by the WARP
239
+ * precedent (the held-timer is not registered under reduced-motion).
240
+ */
241
+ gravityWell?: boolean | {
242
+ holdMs?: number;
243
+ gain?: number;
244
+ reach?: number;
245
+ ramp?: number;
246
+ maxSpeed?: number;
247
+ soften?: number;
248
+ };
249
+ /**
250
+ * Deterministic-capture freeze (AY.W-CON3): when `true`, lays out ONE
251
+ * reproducible STATIC frame (no `stepField`, no ripple / warp / wander / well
252
+ * advance) and hands `drawOverlay` a FROZEN `now` so a phase-driven skin
253
+ * resolves to a fixed value. Omit to AUTO-DERIVE from `location.search`
254
+ * matching `export | print | freeze` (the deploy-pipeline contract); an
255
+ * explicit `false` forces live even under a capture URL. Set `seed` for a
256
+ * field stable ACROSS runs. Unifies with the reduced-motion one-static-frame
257
+ * path (`freeze || reducedMotion` is the single static-frame predicate).
258
+ */
259
+ freeze?: boolean;
106
260
  class?: string;
107
261
  /** The skin seam — paints the consumer's focal mark on the live field. */
108
262
  drawOverlay?: (ctx: CanvasRenderingContext2D, field: ConstellationField, now: number) => void;
@@ -115,6 +269,23 @@ export declare const DEFAULT_PALETTE: ConstellationPalette;
115
269
  * `:root`/`.dark` color arms AND the per-mode alpha defaults in one read.
116
270
  */
117
271
  export declare function readPalette(canvas: HTMLCanvasElement): ConstellationPalette;
272
+ /**
273
+ * Read the NUMERIC interaction-tuning tokens (the warp spring + the gravity-well
274
+ * gains) off a canvas's resolved custom properties — ONCE on mount (AY.W-CON2),
275
+ * via `parseFloat`, NOT per-frame `getComputedStyle` (the hot loop stays clean).
276
+ * Returns `{ warp, well }` configs the field carries; every member falls back to
277
+ * the shipped default when the token is absent (an SSR / no-token mount reads the
278
+ * byte-identical spring). The tokens are PLAIN numbers (`0.55`, `12000`), immune
279
+ * to the W30 `light-dark()`-into-Canvas2D leak (they never reach a `fillStyle`).
280
+ */
281
+ export declare function readInteractionConfig(canvas: HTMLCanvasElement): {
282
+ warp: ConstellationWarpConfig;
283
+ well: ConstellationWellConfig;
284
+ wander: {
285
+ minIdle: number;
286
+ jitter: number;
287
+ };
288
+ };
118
289
  /**
119
290
  * Seed `count` drifting nodes inside `w × h`. `rng` is a `() => number` in
120
291
  * `[0, 1)` (the glass-ui `mulberry32`/`Math.random`); a seeded `rng` lays out a
@@ -124,6 +295,19 @@ export declare function readPalette(canvas: HTMLCanvasElement): ConstellationPal
124
295
  * adds one, so node count is conserved).
125
296
  */
126
297
  export declare function seedField(rng: () => number, count: number, w: number, h: number, speed: number): ConstellationNode[];
298
+ /**
299
+ * Re-fit the existing lattice to a NEW canvas size, proportionally, ON the
300
+ * size-change frame (AY.W-CON1). Without it a field seeded at a transitional
301
+ * size (the canvas measures mid responsive-scale — the deck slide-enter case)
302
+ * keeps its small-canvas positions and DRIFTS out to fill the larger box at
303
+ * `speed` px/frame — the visible "takes a while to expand out" lag the slides
304
+ * bespoke copy fixed. Scales node + warp positions by the per-axis dimension
305
+ * ratio; velocities are UNTOUCHED (they are base-width direction vectors
306
+ * `k`-scaled at step time — scaling them would HEAT the field, breaking the
307
+ * cool-down invariant). No-op on first layout (`prev ≤ 0`; the seed path owns
308
+ * it) or unchanged dims.
309
+ */
310
+ export declare function refitField(field: ConstellationField, prevW: number, prevH: number): void;
127
311
  /**
128
312
  * Advance the field one step: drift + wall-bounce every node, then (if the
129
313
  * pointer is live) lean nodes within reach toward the cursor WITHOUT changing
@@ -133,8 +317,53 @@ export declare function seedField(rng: () => number, count: number, w: number, h
133
317
  * via `warpStep` INSIDE this single per-frame call (AX.W17) — NO second rAF,
134
318
  * NO `useSpring`. The warp rides the substrate's ONE parked rAF; a `dt` of `0`
135
319
  * (omitted / first frame) leaves the spring untouched.
320
+ *
321
+ * `now` (ms; default `0` — every existing caller stays green) + `rng` (default
322
+ * `Math.random`) drive the optional auto-DRIFT cadence (AY.W-CON1): AFTER the
323
+ * warp step, if `field.wander` is set and `now > 0`, the cadence re-points
324
+ * `setWarpTarget` to a random node when it elapses AND the spring has settled
325
+ * (a click-warp in flight pre-empts the cadence — `warpSettled` reports false).
136
326
  */
137
- export declare function stepField(field: ConstellationField, k: number, speed: number, pointer: ConstellationPointer | null, dt?: number): void;
327
+ export declare function stepField(field: ConstellationField, k: number, speed: number, pointer: ConstellationPointer | null, dt?: number, now?: number, rng?: () => number): void;
328
+ /**
329
+ * Advance the gravity-well one `dt` (AY.W-CON2). A transient held-pointer force
330
+ * composed inside `stepField` (NO new rAF): ease `well.strength` toward
331
+ * `well.target` (1 held / 0 released) at `cfg.ramp`/s; while active, add an
332
+ * inverse-square pull toward the held point onto each node within `cfg.reach`
333
+ * (the `max(d, soften)` no-singularity floor + the `cfg.maxSpeed` no-slingshot
334
+ * clamp); ALWAYS ease every node's |v| back toward `speed` (the field-cools
335
+ * invariant — so a released well re-settles). No-ops (entirely skipped) when
336
+ * `field.well` is undefined → the default render is byte-identical to HEAD.
337
+ *
338
+ * `dt` is clamped to {@link WARP_DT_CLAMP} (the same park-resume guard the warp
339
+ * uses) so a tab-throttle gap cannot slingshot the field in one giant step.
340
+ */
341
+ export declare function stepWell(field: ConstellationField, k: number, speed: number, dt: number): void;
342
+ /**
343
+ * Warp spring tuning DEFAULTS — gentle critically-damped (no overshoot on a focal
344
+ * mark). The `--constellation-warp-response`/`-zeta` tokens override these on mount
345
+ * via `field.warpCfg` (AY.W-CON2); these stay the byte-identical fallback.
346
+ *
347
+ * `WARP_RESPONSE` is the keyframes.js `(response, dampingFraction)` model's ANGULAR
348
+ * PERIOD (the SwiftUI `.spring(response:)` axis), `ω₀ = 2π/response` — NOT a
349
+ * settle-duration. At ζ=1 the 2%-settle lands at `t₂ ≈ 5.83/ω₀ ≈ 0.93·response`
350
+ * (the `(1 + ω₀t)e^(−ω₀t)` critically-damped envelope); the shipped 0.55 settles
351
+ * at ≈0.51s. This is the AY.W-CON2 ω-reconcile: the engine keeps the keyframes.js
352
+ * `ω₀ = 2π/response` convention (the shared house model — `regen-spring-tokens.mjs`,
353
+ * `keyframes.d.ts:860-882`) and mints NO second ω formula; the SEMANTIC honesty
354
+ * (period, not settle) lives in this doc + the token comment + the settle-time unit
355
+ * assertion, so a consumer setting the token gets the documented behaviour at ANY ζ.
356
+ */
357
+ export declare const WARP_RESPONSE = 0.55;
358
+ export declare const WARP_ZETA = 1;
359
+ /**
360
+ * Gravity-well tuning DEFAULTS (AY.W-CON2). The `--constellation-well-*` tokens
361
+ * override these on mount via `field.well.cfg`; these stay the fallback. SET by the
362
+ * §6 egg-live π readback (the field-perturbs-then-cools capture is the binding
363
+ * truth) — a moderate gain over a generous reach, a brisk ramp, a tight no-slingshot
364
+ * speed cap, and the singularity-soften floor.
365
+ */
366
+ export declare const DEFAULT_WELL_CONFIG: ConstellationWellConfig;
138
367
  /**
139
368
  * The nearest DRIFTING node to `(px, py)` in canvas-local px — a linear O(count)
140
369
  * min-d² scan (count 64 default — negligible; matches the O(count²) edge pass).
@@ -154,8 +383,12 @@ export declare function nearestNode(field: ConstellationField, px: number, py: n
154
383
  *
155
384
  * x += v·dt ; v += (−2ζω·v − ω²·(x − target))·dt
156
385
  *
157
- * Reuses the keyframes.js `(response, dampingFraction)` param model
158
- * (ω = 2π/response, ζ = dampingFraction) but NOT its rAF — the FORBID-useSpring
386
+ * Reads `(response, ζ)` from `field.warpCfg` (the tokenised override — AY.W-CON2),
387
+ * falling back to the shipped `WARP_RESPONSE`/`WARP_ZETA` defaults. `ω = 2π/response`
388
+ * is the keyframes.js `(response, dampingFraction)` convention (the SHARED house
389
+ * model — `ω₀` is the angular frequency, `response` its ANGULAR PERIOD, NOT a
390
+ * settle-duration; at ζ=1 the 2%-settle is `t₂ ≈ 5.83/ω₀ ≈ 0.93·response`). NO
391
+ * second ω formula is minted (the ω-reconcile). NOT its rAF — the FORBID-useSpring
159
392
  * contract (a second rAF would defeat the parked-substrate one-path freeze).
160
393
  */
161
394
  export declare function warpStep(field: ConstellationField, dt: number): void;
@@ -180,6 +413,25 @@ export declare function setWarpTarget(field: ConstellationField, idx: number): v
180
413
  * `warpTo(clientX, clientY)` sugar maps client→local px via `toLocal` first.
181
414
  */
182
415
  export declare function warpTo(field: ConstellationField, px: number, py: number): number;
416
+ /**
417
+ * Is the warp spring ARRIVED on its target (AY.W-CON1)? True when there is no
418
+ * active target (`targetIdx < 0`) OR the focal's gap to its LIVE target node is
419
+ * within {@link WARP_SETTLE_BAND}. A click-warp in flight (hundreds of px out)
420
+ * reports NOT arrived, so a user click always PRE-EMPTS the auto-drift cadence
421
+ * (the wander only re-targets a spring that has reached its current node). The
422
+ * gate is POSITION-only by design — a critically-damped spring tracking a drifting
423
+ * node carries a non-zero steady-state velocity forever, so a velocity term would
424
+ * never clear on a live field.
425
+ */
426
+ export declare function warpSettled(field: ConstellationField): boolean;
427
+ /**
428
+ * Pick a random eligible node index for the auto-drift re-target (AY.W-CON1) —
429
+ * any node EXCEPT the current `focalIndex` (so the focal actually MOVES). `rng`
430
+ * is the seeded `() => number` in `[0, 1)`. A degenerate field (≤1 node, or only
431
+ * the current focal) returns the current focal, which `setWarpTarget` no-ops
432
+ * cleanly (no crash, no jump).
433
+ */
434
+ export declare function pickWanderTarget(field: ConstellationField, rng: () => number): number;
183
435
  /** Pass 1 — hairline edges between any two nodes within `link` px (alpha falls off with distance). */
184
436
  export declare function drawEdges(ctx: CanvasRenderingContext2D, field: ConstellationField, link: number, palette: ConstellationPalette): void;
185
437
  /** 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, nearestNode, warpStep, warpTo, setWarpTarget, BASE_WIDTH, DEFAULT_PALETTE, } from "./constellationField";
3
- export type { ConstellationNode, ConstellationRipple, ConstellationPointer, ConstellationPalette, ConstellationField, ConstellationWarp, ConstellationProps, } from "./constellationField";
2
+ export { seedField, stepField, stepWell, refitField, readPalette, drawEdges, drawNodes, drawPointerWeb, drawRipples, nearestNode, readInteractionConfig, warpStep, warpTo, setWarpTarget, warpSettled, pickWanderTarget, BASE_WIDTH, DEFAULT_PALETTE, DEFAULT_WELL_CONFIG, DEFAULT_WANDER_IDLE, DEFAULT_WANDER_JITTER, WARP_RESPONSE, WARP_ZETA, } from "./constellationField";
3
+ export type { ConstellationNode, ConstellationRipple, ConstellationPointer, ConstellationPalette, ConstellationField, ConstellationWarp, ConstellationWarpConfig, ConstellationWander, ConstellationWell, ConstellationWellConfig, 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;
@@ -12,9 +12,10 @@ import { type HTMLAttributes } from "vue";
12
12
  *
13
13
  * K.W6 Lane B — when the consumer attaches `data-tier="primary"`, the
14
14
  * button composes the canonical `btn-audacious` utility (the lifted
15
- * disco-grain + sparkle-sweep + specular-highlight recipe at
16
- * `utilities.css`). `dock.css` retains the dock-local phase-tinting +
17
- * structural shell on top of the canonical recipe.
15
+ * disco-grain + specular-highlight recipe at `utilities.css`; the
16
+ * sparkle-sweep star was excised at D6 — the gold identity now rides the
17
+ * `--gold-rim` gilt edge). `dock.css` retains the dock-local
18
+ * phase-tinting + structural shell on top of the canonical recipe.
18
19
  */
19
20
  type __VLS_Props = {
20
21
  as?: string;