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