From e27d4e8b10da3bd3b0b4ccb201e4acc30a743c92 Mon Sep 17 00:00:00 2001 From: AlexZ005 Date: Fri, 23 Oct 2026 15:52:04 +0300 Subject: [PATCH] backgrounds: five fluid shader background presets (core roadmap 65 M2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mesh-gradient, silk-gradient, noise-flow, metaballs, grain-dither — ordinary background-domain shader graphs built by tools/backgrounds/build.cjs (--check verifies the committed files). CC0. Co-Authored-By: Claude Opus 5.5 (1M context) --- backgrounds/grain-dither.json | 450 +++++++++++++++++++++++++++++++++ backgrounds/index.json | 37 +++ backgrounds/mesh-gradient.json | 132 ++++++++++ backgrounds/metaballs.json | 308 ++++++++++++++++++++++ backgrounds/noise-flow.json | 400 +++++++++++++++++++++++++++++ backgrounds/silk-gradient.json | 423 +++++++++++++++++++++++++++++++ tools/backgrounds/build.cjs | 319 +++++++++++++++++++++++ 7 files changed, 2069 insertions(+) create mode 100644 backgrounds/grain-dither.json create mode 100644 backgrounds/index.json create mode 100644 backgrounds/mesh-gradient.json create mode 100644 backgrounds/metaballs.json create mode 100644 backgrounds/noise-flow.json create mode 100644 backgrounds/silk-gradient.json create mode 100644 tools/backgrounds/build.cjs diff --git a/backgrounds/grain-dither.json b/backgrounds/grain-dither.json new file mode 100644 index 0000000..fbf4f50 --- /dev/null +++ b/backgrounds/grain-dither.json @@ -0,0 +1,450 @@ +{ + "format": 1, + "domain": "background", + "name": "Grain & dither", + "preset": "grain-dither", + "layer": { + "pointer": true, + "scroll": false + }, + "nodes": [ + { + "id": "uv", + "type": "aspectUv", + "position": { + "x": 40, + "y": 40 + }, + "data": {} + }, + { + "id": "chan", + "type": "split", + "position": { + "x": 240, + "y": 40 + }, + "data": {} + }, + { + "id": "cloud", + "type": "fbm", + "position": { + "x": 240, + "y": 220 + }, + "data": { + "scale": 2, + "octaves": 4, + "lacunarity": 2, + "gain": 0.5 + } + }, + { + "id": "cloudAmt", + "type": "float", + "position": { + "x": 240, + "y": 400 + }, + "data": { + "value": 0.22 + } + }, + { + "id": "cloudS", + "type": "multiply", + "position": { + "x": 440, + "y": 260 + }, + "data": {} + }, + { + "id": "t", + "type": "add", + "position": { + "x": 620, + "y": 100 + }, + "data": {} + }, + { + "id": "ramp", + "type": "gradient", + "position": { + "x": 800, + "y": 80 + }, + "data": { + "colorA": "#2b1b4a", + "colorB": "#c4577a", + "colorC": "#ffc98a", + "mid": 0.42 + } + }, + { + "id": "ptr", + "type": "pointer", + "position": { + "x": 40, + "y": 560 + }, + "data": {} + }, + { + "id": "ptrUv", + "type": "aspectUv", + "position": { + "x": 240, + "y": 560 + }, + "data": {} + }, + { + "id": "dist", + "type": "distance", + "position": { + "x": 440, + "y": 520 + }, + "data": {} + }, + { + "id": "spotRise", + "type": "smoothstep", + "position": { + "x": 620, + "y": 460 + }, + "data": { + "edge0": 0, + "edge1": 0.35 + } + }, + { + "id": "spot", + "type": "oneMinus", + "position": { + "x": 660, + "y": 580 + }, + "data": {} + }, + { + "id": "spotAmt", + "type": "float", + "position": { + "x": 620, + "y": 680 + }, + "data": { + "value": 0.35 + } + }, + { + "id": "spotS", + "type": "multiply", + "position": { + "x": 800, + "y": 540 + }, + "data": {} + }, + { + "id": "lit", + "type": "add", + "position": { + "x": 1000, + "y": 200 + }, + "data": {} + }, + { + "id": "bayer", + "type": "bayer", + "position": { + "x": 1000, + "y": 420 + }, + "data": { + "scale": 2 + } + }, + { + "id": "half", + "type": "float", + "position": { + "x": 1000, + "y": 560 + }, + "data": { + "value": 0.5 + } + }, + { + "id": "centred", + "type": "subtract", + "position": { + "x": 1180, + "y": 440 + }, + "data": {} + }, + { + "id": "depth", + "type": "float", + "position": { + "x": 1180, + "y": 600 + }, + "data": { + "value": 0.16 + } + }, + { + "id": "dither", + "type": "multiply", + "position": { + "x": 1360, + "y": 460 + }, + "data": {} + }, + { + "id": "dithered", + "type": "add", + "position": { + "x": 1360, + "y": 220 + }, + "data": {} + }, + { + "id": "steps", + "type": "posterize", + "position": { + "x": 1540, + "y": 220 + }, + "data": { + "steps": 7 + } + }, + { + "id": "grain", + "type": "grain", + "position": { + "x": 1540, + "y": 420 + }, + "data": { + "amount": 0.05, + "size": 2, + "animate": 1 + } + }, + { + "id": "final", + "type": "add", + "position": { + "x": 1740, + "y": 260 + }, + "data": {} + }, + { + "id": "out", + "type": "backgroundOutput", + "position": { + "x": 1940, + "y": 260 + }, + "data": {} + } + ], + "edges": [ + { + "id": "e-uv.out-chan.value", + "source": "uv", + "sourceHandle": "out", + "target": "chan", + "targetHandle": "value" + }, + { + "id": "e-uv.out-cloud.uv", + "source": "uv", + "sourceHandle": "out", + "target": "cloud", + "targetHandle": "uv" + }, + { + "id": "e-cloud.out-cloudS.a", + "source": "cloud", + "sourceHandle": "out", + "target": "cloudS", + "targetHandle": "a" + }, + { + "id": "e-cloudAmt.out-cloudS.b", + "source": "cloudAmt", + "sourceHandle": "out", + "target": "cloudS", + "targetHandle": "b" + }, + { + "id": "e-chan.y-t.a", + "source": "chan", + "sourceHandle": "y", + "target": "t", + "targetHandle": "a" + }, + { + "id": "e-cloudS.out-t.b", + "source": "cloudS", + "sourceHandle": "out", + "target": "t", + "targetHandle": "b" + }, + { + "id": "e-t.out-ramp.t", + "source": "t", + "sourceHandle": "out", + "target": "ramp", + "targetHandle": "t" + }, + { + "id": "e-ptr.position-ptrUv.uv", + "source": "ptr", + "sourceHandle": "position", + "target": "ptrUv", + "targetHandle": "uv" + }, + { + "id": "e-uv.out-dist.a", + "source": "uv", + "sourceHandle": "out", + "target": "dist", + "targetHandle": "a" + }, + { + "id": "e-ptrUv.out-dist.b", + "source": "ptrUv", + "sourceHandle": "out", + "target": "dist", + "targetHandle": "b" + }, + { + "id": "e-dist.out-spotRise.a", + "source": "dist", + "sourceHandle": "out", + "target": "spotRise", + "targetHandle": "a" + }, + { + "id": "e-spotRise.out-spot.a", + "source": "spotRise", + "sourceHandle": "out", + "target": "spot", + "targetHandle": "a" + }, + { + "id": "e-spot.out-spotS.a", + "source": "spot", + "sourceHandle": "out", + "target": "spotS", + "targetHandle": "a" + }, + { + "id": "e-spotAmt.out-spotS.b", + "source": "spotAmt", + "sourceHandle": "out", + "target": "spotS", + "targetHandle": "b" + }, + { + "id": "e-ramp.out-lit.a", + "source": "ramp", + "sourceHandle": "out", + "target": "lit", + "targetHandle": "a" + }, + { + "id": "e-spotS.out-lit.b", + "source": "spotS", + "sourceHandle": "out", + "target": "lit", + "targetHandle": "b" + }, + { + "id": "e-bayer.out-centred.a", + "source": "bayer", + "sourceHandle": "out", + "target": "centred", + "targetHandle": "a" + }, + { + "id": "e-half.out-centred.b", + "source": "half", + "sourceHandle": "out", + "target": "centred", + "targetHandle": "b" + }, + { + "id": "e-centred.out-dither.a", + "source": "centred", + "sourceHandle": "out", + "target": "dither", + "targetHandle": "a" + }, + { + "id": "e-depth.out-dither.b", + "source": "depth", + "sourceHandle": "out", + "target": "dither", + "targetHandle": "b" + }, + { + "id": "e-lit.out-dithered.a", + "source": "lit", + "sourceHandle": "out", + "target": "dithered", + "targetHandle": "a" + }, + { + "id": "e-dither.out-dithered.b", + "source": "dither", + "sourceHandle": "out", + "target": "dithered", + "targetHandle": "b" + }, + { + "id": "e-dithered.out-steps.a", + "source": "dithered", + "sourceHandle": "out", + "target": "steps", + "targetHandle": "a" + }, + { + "id": "e-steps.out-final.a", + "source": "steps", + "sourceHandle": "out", + "target": "final", + "targetHandle": "a" + }, + { + "id": "e-grain.out-final.b", + "source": "grain", + "sourceHandle": "out", + "target": "final", + "targetHandle": "b" + }, + { + "id": "e-final.out-out.color", + "source": "final", + "sourceHandle": "out", + "target": "out", + "targetHandle": "color" + } + ] +} diff --git a/backgrounds/index.json b/backgrounds/index.json new file mode 100644 index 0000000..a6b787c --- /dev/null +++ b/backgrounds/index.json @@ -0,0 +1,37 @@ +{ + "format": 1, + "title": "Fluid backgrounds", + "license": "CC0-1.0", + "presets": [ + { + "key": "mesh-gradient", + "label": "Mesh gradient", + "hint": "Four soft colours drifting into each other; the first one leans towards your pointer.", + "file": "mesh-gradient.json" + }, + { + "key": "silk-gradient", + "label": "Silk gradient", + "hint": "A tilted, slowly folding ribbon of colour in the style of modern product pages. Scrolling shifts the palette; the pointer nudges the folds.", + "file": "silk-gradient.json" + }, + { + "key": "noise-flow", + "label": "Noise flow", + "hint": "Contour lines streaming through warped noise, like wind on a weather map. The flow bends towards your pointer.", + "file": "noise-flow.json" + }, + { + "key": "metaballs", + "label": "Metaballs", + "hint": "Glossy blobs that melt into each other like a lava lamp; one of them follows your pointer.", + "file": "metaballs.json" + }, + { + "key": "grain-dither", + "label": "Grain & dither", + "hint": "A soft dusk gradient broken into retro dither steps with film grain on top; a pool of light follows your pointer.", + "file": "grain-dither.json" + } + ] +} diff --git a/backgrounds/mesh-gradient.json b/backgrounds/mesh-gradient.json new file mode 100644 index 0000000..2c92066 --- /dev/null +++ b/backgrounds/mesh-gradient.json @@ -0,0 +1,132 @@ +{ + "format": 1, + "domain": "background", + "name": "Mesh gradient", + "preset": "mesh-gradient", + "layer": { + "pointer": true, + "scroll": false + }, + "nodes": [ + { + "id": "uv", + "type": "aspectUv", + "position": { + "x": 40, + "y": 60 + }, + "data": {} + }, + { + "id": "ptr", + "type": "pointer", + "position": { + "x": 40, + "y": 220 + }, + "data": {} + }, + { + "id": "ptrUv", + "type": "aspectUv", + "position": { + "x": 240, + "y": 240 + }, + "data": {} + }, + { + "id": "mesh", + "type": "meshGradient", + "position": { + "x": 460, + "y": 80 + }, + "data": { + "colorA": "#ff7a9c", + "colorB": "#ffd58a", + "colorC": "#7a8cff", + "colorD": "#46d9c4", + "speed": 0.35, + "warp": 0.7, + "pull": 0.45, + "sharpness": 2.2 + } + }, + { + "id": "grain", + "type": "grain", + "position": { + "x": 460, + "y": 380 + }, + "data": { + "amount": 0.035, + "size": 1.5, + "animate": 1 + } + }, + { + "id": "mixed", + "type": "add", + "position": { + "x": 760, + "y": 160 + }, + "data": {} + }, + { + "id": "out", + "type": "backgroundOutput", + "position": { + "x": 980, + "y": 160 + }, + "data": {} + } + ], + "edges": [ + { + "id": "e-uv.out-mesh.uv", + "source": "uv", + "sourceHandle": "out", + "target": "mesh", + "targetHandle": "uv" + }, + { + "id": "e-ptr.position-ptrUv.uv", + "source": "ptr", + "sourceHandle": "position", + "target": "ptrUv", + "targetHandle": "uv" + }, + { + "id": "e-ptrUv.out-mesh.focus", + "source": "ptrUv", + "sourceHandle": "out", + "target": "mesh", + "targetHandle": "focus" + }, + { + "id": "e-mesh.out-mixed.a", + "source": "mesh", + "sourceHandle": "out", + "target": "mixed", + "targetHandle": "a" + }, + { + "id": "e-grain.out-mixed.b", + "source": "grain", + "sourceHandle": "out", + "target": "mixed", + "targetHandle": "b" + }, + { + "id": "e-mixed.out-out.color", + "source": "mixed", + "sourceHandle": "out", + "target": "out", + "targetHandle": "color" + } + ] +} diff --git a/backgrounds/metaballs.json b/backgrounds/metaballs.json new file mode 100644 index 0000000..3989cc7 --- /dev/null +++ b/backgrounds/metaballs.json @@ -0,0 +1,308 @@ +{ + "format": 1, + "domain": "background", + "name": "Metaballs", + "preset": "metaballs", + "layer": { + "pointer": true, + "scroll": false + }, + "nodes": [ + { + "id": "uv", + "type": "aspectUv", + "position": { + "x": 40, + "y": 40 + }, + "data": {} + }, + { + "id": "ptr", + "type": "pointer", + "position": { + "x": 40, + "y": 220 + }, + "data": {} + }, + { + "id": "ptrUv", + "type": "aspectUv", + "position": { + "x": 240, + "y": 240 + }, + "data": {} + }, + { + "id": "blobs", + "type": "blobs", + "position": { + "x": 460, + "y": 60 + }, + "data": { + "count": 6, + "radius": 0.11, + "speed": 0.45, + "smoothness": 0.14, + "softness": 0.006, + "attractRadius": 0.085 + } + }, + { + "id": "chan", + "type": "split", + "position": { + "x": 260, + "y": 420 + }, + "data": {} + }, + { + "id": "bg", + "type": "gradient", + "position": { + "x": 700, + "y": 360 + }, + "data": { + "colorA": "#120a2a", + "colorB": "#1c1450", + "colorC": "#2a1a5e", + "mid": 0.5 + } + }, + { + "id": "fill", + "type": "gradient", + "position": { + "x": 700, + "y": 120 + }, + "data": { + "colorA": "#ff4fa3", + "colorB": "#ff9a5a", + "colorC": "#ffe46b", + "mid": 0.5 + } + }, + { + "id": "body", + "type": "mix", + "position": { + "x": 940, + "y": 200 + }, + "data": {} + }, + { + "id": "glowRise", + "type": "smoothstep", + "position": { + "x": 940, + "y": 420 + }, + "data": { + "edge0": 0, + "edge1": 0.16 + } + }, + { + "id": "glowW", + "type": "oneMinus", + "position": { + "x": 1000, + "y": 520 + }, + "data": {} + }, + { + "id": "glowAmt", + "type": "float", + "position": { + "x": 940, + "y": 560 + }, + "data": { + "value": 0.35 + } + }, + { + "id": "glow", + "type": "multiply", + "position": { + "x": 1140, + "y": 440 + }, + "data": {} + }, + { + "id": "glowCol", + "type": "color", + "position": { + "x": 1140, + "y": 600 + }, + "data": { + "value": "#ff6ad5" + } + }, + { + "id": "glowRgb", + "type": "multiply", + "position": { + "x": 1320, + "y": 460 + }, + "data": {} + }, + { + "id": "lit", + "type": "add", + "position": { + "x": 1500, + "y": 260 + }, + "data": {} + }, + { + "id": "out", + "type": "backgroundOutput", + "position": { + "x": 1700, + "y": 260 + }, + "data": {} + } + ], + "edges": [ + { + "id": "e-uv.out-blobs.uv", + "source": "uv", + "sourceHandle": "out", + "target": "blobs", + "targetHandle": "uv" + }, + { + "id": "e-ptr.position-ptrUv.uv", + "source": "ptr", + "sourceHandle": "position", + "target": "ptrUv", + "targetHandle": "uv" + }, + { + "id": "e-ptrUv.out-blobs.attract", + "source": "ptrUv", + "sourceHandle": "out", + "target": "blobs", + "targetHandle": "attract" + }, + { + "id": "e-uv.out-chan.value", + "source": "uv", + "sourceHandle": "out", + "target": "chan", + "targetHandle": "value" + }, + { + "id": "e-chan.y-bg.t", + "source": "chan", + "sourceHandle": "y", + "target": "bg", + "targetHandle": "t" + }, + { + "id": "e-chan.y-fill.t", + "source": "chan", + "sourceHandle": "y", + "target": "fill", + "targetHandle": "t" + }, + { + "id": "e-bg.out-body.a", + "source": "bg", + "sourceHandle": "out", + "target": "body", + "targetHandle": "a" + }, + { + "id": "e-fill.out-body.b", + "source": "fill", + "sourceHandle": "out", + "target": "body", + "targetHandle": "b" + }, + { + "id": "e-blobs.mask-body.t", + "source": "blobs", + "sourceHandle": "mask", + "target": "body", + "targetHandle": "t" + }, + { + "id": "e-blobs.sdf-glowRise.a", + "source": "blobs", + "sourceHandle": "sdf", + "target": "glowRise", + "targetHandle": "a" + }, + { + "id": "e-glowRise.out-glowW.a", + "source": "glowRise", + "sourceHandle": "out", + "target": "glowW", + "targetHandle": "a" + }, + { + "id": "e-glowW.out-glow.a", + "source": "glowW", + "sourceHandle": "out", + "target": "glow", + "targetHandle": "a" + }, + { + "id": "e-glowAmt.out-glow.b", + "source": "glowAmt", + "sourceHandle": "out", + "target": "glow", + "targetHandle": "b" + }, + { + "id": "e-glowCol.out-glowRgb.a", + "source": "glowCol", + "sourceHandle": "out", + "target": "glowRgb", + "targetHandle": "a" + }, + { + "id": "e-glow.out-glowRgb.b", + "source": "glow", + "sourceHandle": "out", + "target": "glowRgb", + "targetHandle": "b" + }, + { + "id": "e-body.out-lit.a", + "source": "body", + "sourceHandle": "out", + "target": "lit", + "targetHandle": "a" + }, + { + "id": "e-glowRgb.out-lit.b", + "source": "glowRgb", + "sourceHandle": "out", + "target": "lit", + "targetHandle": "b" + }, + { + "id": "e-lit.out-out.color", + "source": "lit", + "sourceHandle": "out", + "target": "out", + "targetHandle": "color" + } + ] +} diff --git a/backgrounds/noise-flow.json b/backgrounds/noise-flow.json new file mode 100644 index 0000000..cd46119 --- /dev/null +++ b/backgrounds/noise-flow.json @@ -0,0 +1,400 @@ +{ + "format": 1, + "domain": "background", + "name": "Noise flow", + "preset": "noise-flow", + "layer": { + "pointer": true, + "scroll": false + }, + "nodes": [ + { + "id": "uv", + "type": "aspectUv", + "position": { + "x": 40, + "y": 40 + }, + "data": {} + }, + { + "id": "ptr", + "type": "pointer", + "position": { + "x": 40, + "y": 200 + }, + "data": {} + }, + { + "id": "ptrUv", + "type": "aspectUv", + "position": { + "x": 240, + "y": 220 + }, + "data": {} + }, + { + "id": "toward", + "type": "subtract", + "position": { + "x": 440, + "y": 160 + }, + "data": {} + }, + { + "id": "pullAmt", + "type": "float", + "position": { + "x": 440, + "y": 320 + }, + "data": { + "value": 0.08 + } + }, + { + "id": "pull", + "type": "multiply", + "position": { + "x": 640, + "y": 180 + }, + "data": {} + }, + { + "id": "bent", + "type": "add", + "position": { + "x": 640, + "y": 40 + }, + "data": {} + }, + { + "id": "warp", + "type": "domainWarp", + "position": { + "x": 840, + "y": 40 + }, + "data": { + "amount": 0.7, + "scale": 2.2, + "speed": 0.07 + } + }, + { + "id": "field", + "type": "fbm", + "position": { + "x": 1040, + "y": 40 + }, + "data": { + "scale": 2.6, + "octaves": 5, + "lacunarity": 2, + "gain": 0.5 + } + }, + { + "id": "bands", + "type": "float", + "position": { + "x": 1040, + "y": 220 + }, + "data": { + "value": 9 + } + }, + { + "id": "scaled", + "type": "multiply", + "position": { + "x": 1240, + "y": 60 + }, + "data": {} + }, + { + "id": "time", + "type": "time", + "position": { + "x": 1240, + "y": 220 + }, + "data": { + "speed": 0.25 + } + }, + { + "id": "moving", + "type": "subtract", + "position": { + "x": 1420, + "y": 100 + }, + "data": {} + }, + { + "id": "wrap", + "type": "fract", + "position": { + "x": 1600, + "y": 100 + }, + "data": {} + }, + { + "id": "half", + "type": "float", + "position": { + "x": 1600, + "y": 240 + }, + "data": { + "value": 0.5 + } + }, + { + "id": "centre", + "type": "subtract", + "position": { + "x": 1780, + "y": 120 + }, + "data": {} + }, + { + "id": "dist", + "type": "abs", + "position": { + "x": 1960, + "y": 120 + }, + "data": {} + }, + { + "id": "line", + "type": "smoothstep", + "position": { + "x": 2140, + "y": 120 + }, + "data": { + "edge0": 0.4, + "edge1": 0.5 + } + }, + { + "id": "ramp", + "type": "gradient", + "position": { + "x": 1960, + "y": 300 + }, + "data": { + "colorA": "#05070f", + "colorB": "#0f2b4a", + "colorC": "#1a6b7a", + "mid": 0.55 + } + }, + { + "id": "ink", + "type": "color", + "position": { + "x": 2140, + "y": 300 + }, + "data": { + "value": "#9ff6ff" + } + }, + { + "id": "mixed", + "type": "mix", + "position": { + "x": 2340, + "y": 200 + }, + "data": {} + }, + { + "id": "out", + "type": "backgroundOutput", + "position": { + "x": 2540, + "y": 200 + }, + "data": {} + } + ], + "edges": [ + { + "id": "e-ptr.position-ptrUv.uv", + "source": "ptr", + "sourceHandle": "position", + "target": "ptrUv", + "targetHandle": "uv" + }, + { + "id": "e-ptrUv.out-toward.a", + "source": "ptrUv", + "sourceHandle": "out", + "target": "toward", + "targetHandle": "a" + }, + { + "id": "e-uv.out-toward.b", + "source": "uv", + "sourceHandle": "out", + "target": "toward", + "targetHandle": "b" + }, + { + "id": "e-toward.out-pull.a", + "source": "toward", + "sourceHandle": "out", + "target": "pull", + "targetHandle": "a" + }, + { + "id": "e-pullAmt.out-pull.b", + "source": "pullAmt", + "sourceHandle": "out", + "target": "pull", + "targetHandle": "b" + }, + { + "id": "e-uv.out-bent.a", + "source": "uv", + "sourceHandle": "out", + "target": "bent", + "targetHandle": "a" + }, + { + "id": "e-pull.out-bent.b", + "source": "pull", + "sourceHandle": "out", + "target": "bent", + "targetHandle": "b" + }, + { + "id": "e-bent.out-warp.uv", + "source": "bent", + "sourceHandle": "out", + "target": "warp", + "targetHandle": "uv" + }, + { + "id": "e-warp.out-field.uv", + "source": "warp", + "sourceHandle": "out", + "target": "field", + "targetHandle": "uv" + }, + { + "id": "e-field.out-scaled.a", + "source": "field", + "sourceHandle": "out", + "target": "scaled", + "targetHandle": "a" + }, + { + "id": "e-bands.out-scaled.b", + "source": "bands", + "sourceHandle": "out", + "target": "scaled", + "targetHandle": "b" + }, + { + "id": "e-scaled.out-moving.a", + "source": "scaled", + "sourceHandle": "out", + "target": "moving", + "targetHandle": "a" + }, + { + "id": "e-time.out-moving.b", + "source": "time", + "sourceHandle": "out", + "target": "moving", + "targetHandle": "b" + }, + { + "id": "e-moving.out-wrap.a", + "source": "moving", + "sourceHandle": "out", + "target": "wrap", + "targetHandle": "a" + }, + { + "id": "e-wrap.out-centre.a", + "source": "wrap", + "sourceHandle": "out", + "target": "centre", + "targetHandle": "a" + }, + { + "id": "e-half.out-centre.b", + "source": "half", + "sourceHandle": "out", + "target": "centre", + "targetHandle": "b" + }, + { + "id": "e-centre.out-dist.a", + "source": "centre", + "sourceHandle": "out", + "target": "dist", + "targetHandle": "a" + }, + { + "id": "e-dist.out-line.a", + "source": "dist", + "sourceHandle": "out", + "target": "line", + "targetHandle": "a" + }, + { + "id": "e-field.out-ramp.t", + "source": "field", + "sourceHandle": "out", + "target": "ramp", + "targetHandle": "t" + }, + { + "id": "e-ramp.out-mixed.a", + "source": "ramp", + "sourceHandle": "out", + "target": "mixed", + "targetHandle": "a" + }, + { + "id": "e-ink.out-mixed.b", + "source": "ink", + "sourceHandle": "out", + "target": "mixed", + "targetHandle": "b" + }, + { + "id": "e-line.out-mixed.t", + "source": "line", + "sourceHandle": "out", + "target": "mixed", + "targetHandle": "t" + }, + { + "id": "e-mixed.out-out.color", + "source": "mixed", + "sourceHandle": "out", + "target": "out", + "targetHandle": "color" + } + ] +} diff --git a/backgrounds/silk-gradient.json b/backgrounds/silk-gradient.json new file mode 100644 index 0000000..3ce6e49 --- /dev/null +++ b/backgrounds/silk-gradient.json @@ -0,0 +1,423 @@ +{ + "format": 1, + "domain": "background", + "name": "Silk gradient", + "preset": "silk-gradient", + "layer": { + "pointer": true, + "scroll": true + }, + "nodes": [ + { + "id": "uv", + "type": "aspectUv", + "position": { + "x": 40, + "y": 40 + }, + "data": {} + }, + { + "id": "ptr", + "type": "pointer", + "position": { + "x": 40, + "y": 200 + }, + "data": {} + }, + { + "id": "nudgeAmt", + "type": "float", + "position": { + "x": 40, + "y": 330 + }, + "data": { + "value": 0.06 + } + }, + { + "id": "nudge", + "type": "multiply", + "position": { + "x": 240, + "y": 220 + }, + "data": {} + }, + { + "id": "shifted", + "type": "add", + "position": { + "x": 240, + "y": 60 + }, + "data": {} + }, + { + "id": "tilt", + "type": "rotateUv", + "position": { + "x": 440, + "y": 60 + }, + "data": { + "angle": -14, + "speed": 0, + "center": [ + 0.5, + 0.5 + ] + } + }, + { + "id": "warp", + "type": "domainWarp", + "position": { + "x": 640, + "y": 60 + }, + "data": { + "amount": 0.55, + "scale": 1.4, + "speed": 0.05 + } + }, + { + "id": "chan", + "type": "split", + "position": { + "x": 840, + "y": 20 + }, + "data": {} + }, + { + "id": "folds", + "type": "fbm", + "position": { + "x": 840, + "y": 200 + }, + "data": { + "scale": 1.6, + "octaves": 3, + "lacunarity": 2, + "gain": 0.5 + } + }, + { + "id": "xw", + "type": "float", + "position": { + "x": 840, + "y": 380 + }, + "data": { + "value": 0.85 + } + }, + { + "id": "xs", + "type": "multiply", + "position": { + "x": 1040, + "y": 20 + }, + "data": {} + }, + { + "id": "fw", + "type": "float", + "position": { + "x": 1040, + "y": 380 + }, + "data": { + "value": 0.55 + } + }, + { + "id": "fs", + "type": "multiply", + "position": { + "x": 1040, + "y": 200 + }, + "data": {} + }, + { + "id": "scroll", + "type": "scroll", + "position": { + "x": 1040, + "y": 500 + }, + "data": {} + }, + { + "id": "sw", + "type": "float", + "position": { + "x": 1040, + "y": 620 + }, + "data": { + "value": 0.45 + } + }, + { + "id": "ss", + "type": "multiply", + "position": { + "x": 1240, + "y": 500 + }, + "data": {} + }, + { + "id": "t1", + "type": "add", + "position": { + "x": 1240, + "y": 100 + }, + "data": {} + }, + { + "id": "t", + "type": "add", + "position": { + "x": 1420, + "y": 160 + }, + "data": {} + }, + { + "id": "ramp", + "type": "gradient", + "position": { + "x": 1600, + "y": 120 + }, + "data": { + "colorA": "#1d2a7a", + "colorB": "#c23bd4", + "colorC": "#ffb067", + "mid": 0.48 + } + }, + { + "id": "cyan", + "type": "color", + "position": { + "x": 1600, + "y": 340 + }, + "data": { + "value": "#52e5ff" + } + }, + { + "id": "hl", + "type": "smoothstep", + "position": { + "x": 1600, + "y": 470 + }, + "data": { + "edge0": 0.62, + "edge1": 0.9 + } + }, + { + "id": "silk", + "type": "mix", + "position": { + "x": 1820, + "y": 200 + }, + "data": {} + }, + { + "id": "out", + "type": "backgroundOutput", + "position": { + "x": 2040, + "y": 200 + }, + "data": {} + } + ], + "edges": [ + { + "id": "e-ptr.position-nudge.a", + "source": "ptr", + "sourceHandle": "position", + "target": "nudge", + "targetHandle": "a" + }, + { + "id": "e-nudgeAmt.out-nudge.b", + "source": "nudgeAmt", + "sourceHandle": "out", + "target": "nudge", + "targetHandle": "b" + }, + { + "id": "e-uv.out-shifted.a", + "source": "uv", + "sourceHandle": "out", + "target": "shifted", + "targetHandle": "a" + }, + { + "id": "e-nudge.out-shifted.b", + "source": "nudge", + "sourceHandle": "out", + "target": "shifted", + "targetHandle": "b" + }, + { + "id": "e-shifted.out-tilt.uv", + "source": "shifted", + "sourceHandle": "out", + "target": "tilt", + "targetHandle": "uv" + }, + { + "id": "e-tilt.out-warp.uv", + "source": "tilt", + "sourceHandle": "out", + "target": "warp", + "targetHandle": "uv" + }, + { + "id": "e-warp.out-chan.value", + "source": "warp", + "sourceHandle": "out", + "target": "chan", + "targetHandle": "value" + }, + { + "id": "e-warp.out-folds.uv", + "source": "warp", + "sourceHandle": "out", + "target": "folds", + "targetHandle": "uv" + }, + { + "id": "e-chan.x-xs.a", + "source": "chan", + "sourceHandle": "x", + "target": "xs", + "targetHandle": "a" + }, + { + "id": "e-xw.out-xs.b", + "source": "xw", + "sourceHandle": "out", + "target": "xs", + "targetHandle": "b" + }, + { + "id": "e-folds.out-fs.a", + "source": "folds", + "sourceHandle": "out", + "target": "fs", + "targetHandle": "a" + }, + { + "id": "e-fw.out-fs.b", + "source": "fw", + "sourceHandle": "out", + "target": "fs", + "targetHandle": "b" + }, + { + "id": "e-scroll.progress-ss.a", + "source": "scroll", + "sourceHandle": "progress", + "target": "ss", + "targetHandle": "a" + }, + { + "id": "e-sw.out-ss.b", + "source": "sw", + "sourceHandle": "out", + "target": "ss", + "targetHandle": "b" + }, + { + "id": "e-xs.out-t1.a", + "source": "xs", + "sourceHandle": "out", + "target": "t1", + "targetHandle": "a" + }, + { + "id": "e-fs.out-t1.b", + "source": "fs", + "sourceHandle": "out", + "target": "t1", + "targetHandle": "b" + }, + { + "id": "e-t1.out-t.a", + "source": "t1", + "sourceHandle": "out", + "target": "t", + "targetHandle": "a" + }, + { + "id": "e-ss.out-t.b", + "source": "ss", + "sourceHandle": "out", + "target": "t", + "targetHandle": "b" + }, + { + "id": "e-t.out-ramp.t", + "source": "t", + "sourceHandle": "out", + "target": "ramp", + "targetHandle": "t" + }, + { + "id": "e-folds.out-hl.a", + "source": "folds", + "sourceHandle": "out", + "target": "hl", + "targetHandle": "a" + }, + { + "id": "e-ramp.out-silk.a", + "source": "ramp", + "sourceHandle": "out", + "target": "silk", + "targetHandle": "a" + }, + { + "id": "e-cyan.out-silk.b", + "source": "cyan", + "sourceHandle": "out", + "target": "silk", + "targetHandle": "b" + }, + { + "id": "e-hl.out-silk.t", + "source": "hl", + "sourceHandle": "out", + "target": "silk", + "targetHandle": "t" + }, + { + "id": "e-silk.out-out.color", + "source": "silk", + "sourceHandle": "out", + "target": "out", + "targetHandle": "color" + } + ] +} diff --git a/tools/backgrounds/build.cjs b/tools/backgrounds/build.cjs new file mode 100644 index 0000000..d641a2a --- /dev/null +++ b/tools/backgrounds/build.cjs @@ -0,0 +1,319 @@ +#!/usr/bin/env node +// Builds backgrounds/*.json + backgrounds/index.json — the fluid shader BACKGROUND presets +// (core roadmap 65 M2). Each preset is an ORDINARY background-domain shader graph: the same +// document a person gets by building it node by node in the app's Shader editor (Background +// tab), so "delete a node and see what changes" is how the vocabulary is learnt. Positions +// are authored left-to-right because these are the first background graphs most people open. +// +// node tools/backgrounds/build.cjs writes the files +// node tools/backgrounds/build.cjs --check exits 1 when the committed files are stale +const fs = require('fs'); +const path = require('path'); + +const OUT = path.join(__dirname, '..', '..', 'backgrounds'); +/** the document format the app reads (core src/lib/backgroundPresets.js PRESET_FORMAT) */ +const FORMAT = 1; + +const node = (id, type, x, y, data = {}) => ({ id, type, position: { x, y }, data }); +// the editor's canonical edge id shape, handles included +const edge = (source, sourceHandle, target, targetHandle) => ({ + id: 'e-' + source + '.' + sourceHandle + '-' + target + '.' + targetHandle, + source, + sourceHandle, + target, + targetHandle +}); +const num = (id, value, x, y) => node(id, 'float', x, y, { value }); + +const PRESETS = [ + { + key: 'mesh-gradient', + label: 'Mesh gradient', + hint: 'Four soft colours drifting into each other; the first one leans towards your pointer.', + layer: { pointer: true, scroll: false }, + nodes: [ + node('uv', 'aspectUv', 40, 60), + node('ptr', 'pointer', 40, 220), + node('ptrUv', 'aspectUv', 240, 240), + node('mesh', 'meshGradient', 460, 80, { + colorA: '#ff7a9c', colorB: '#ffd58a', colorC: '#7a8cff', colorD: '#46d9c4', + speed: 0.35, warp: 0.7, pull: 0.45, sharpness: 2.2 + }), + node('grain', 'grain', 460, 380, { amount: 0.035, size: 1.5, animate: 1 }), + node('mixed', 'add', 760, 160), + node('out', 'backgroundOutput', 980, 160) + ], + edges: [ + edge('uv', 'out', 'mesh', 'uv'), + edge('ptr', 'position', 'ptrUv', 'uv'), + edge('ptrUv', 'out', 'mesh', 'focus'), + edge('mesh', 'out', 'mixed', 'a'), + edge('grain', 'out', 'mixed', 'b'), + edge('mixed', 'out', 'out', 'color') + ] + }, + { + key: 'silk-gradient', + label: 'Silk gradient', + hint: 'A tilted, slowly folding ribbon of colour in the style of modern product pages. Scrolling shifts the palette; the pointer nudges the folds.', + layer: { pointer: true, scroll: true }, + nodes: [ + node('uv', 'aspectUv', 40, 40), + node('ptr', 'pointer', 40, 200), + node('nudgeAmt', 'float', 40, 330, { value: 0.06 }), + node('nudge', 'multiply', 240, 220), + node('shifted', 'add', 240, 60), + node('tilt', 'rotateUv', 440, 60, { angle: -14, speed: 0, center: [0.5, 0.5] }), + node('warp', 'domainWarp', 640, 60, { amount: 0.55, scale: 1.4, speed: 0.05 }), + node('chan', 'split', 840, 20), + node('folds', 'fbm', 840, 200, { scale: 1.6, octaves: 3, lacunarity: 2, gain: 0.5 }), + node('xw', 'float', 840, 380, { value: 0.85 }), + node('xs', 'multiply', 1040, 20), + node('fw', 'float', 1040, 380, { value: 0.55 }), + node('fs', 'multiply', 1040, 200), + node('scroll', 'scroll', 1040, 500), + node('sw', 'float', 1040, 620, { value: 0.45 }), + node('ss', 'multiply', 1240, 500), + node('t1', 'add', 1240, 100), + node('t', 'add', 1420, 160), + node('ramp', 'gradient', 1600, 120, { colorA: '#1d2a7a', colorB: '#c23bd4', colorC: '#ffb067', mid: 0.48 }), + node('cyan', 'color', 1600, 340, { value: '#52e5ff' }), + node('hl', 'smoothstep', 1600, 470, { edge0: 0.62, edge1: 0.9 }), + node('silk', 'mix', 1820, 200), + node('out', 'backgroundOutput', 2040, 200) + ], + edges: [ + edge('ptr', 'position', 'nudge', 'a'), + edge('nudgeAmt', 'out', 'nudge', 'b'), + edge('uv', 'out', 'shifted', 'a'), + edge('nudge', 'out', 'shifted', 'b'), + edge('shifted', 'out', 'tilt', 'uv'), + edge('tilt', 'out', 'warp', 'uv'), + edge('warp', 'out', 'chan', 'value'), + edge('warp', 'out', 'folds', 'uv'), + edge('chan', 'x', 'xs', 'a'), + edge('xw', 'out', 'xs', 'b'), + edge('folds', 'out', 'fs', 'a'), + edge('fw', 'out', 'fs', 'b'), + edge('scroll', 'progress', 'ss', 'a'), + edge('sw', 'out', 'ss', 'b'), + edge('xs', 'out', 't1', 'a'), + edge('fs', 'out', 't1', 'b'), + edge('t1', 'out', 't', 'a'), + edge('ss', 'out', 't', 'b'), + edge('t', 'out', 'ramp', 't'), + edge('folds', 'out', 'hl', 'a'), + edge('ramp', 'out', 'silk', 'a'), + edge('cyan', 'out', 'silk', 'b'), + edge('hl', 'out', 'silk', 't'), + edge('silk', 'out', 'out', 'color') + ] + }, + { + key: 'noise-flow', + label: 'Noise flow', + hint: 'Contour lines streaming through warped noise, like wind on a weather map. The flow bends towards your pointer.', + layer: { pointer: true, scroll: false }, + nodes: [ + node('uv', 'aspectUv', 40, 40), + node('ptr', 'pointer', 40, 200), + node('ptrUv', 'aspectUv', 240, 220), + node('toward', 'subtract', 440, 160), + node('pullAmt', 'float', 440, 320, { value: 0.08 }), + node('pull', 'multiply', 640, 180), + node('bent', 'add', 640, 40), + node('warp', 'domainWarp', 840, 40, { amount: 0.7, scale: 2.2, speed: 0.07 }), + node('field', 'fbm', 1040, 40, { scale: 2.6, octaves: 5, lacunarity: 2, gain: 0.5 }), + node('bands', 'float', 1040, 220, { value: 9 }), + node('scaled', 'multiply', 1240, 60), + node('time', 'time', 1240, 220, { speed: 0.25 }), + node('moving', 'subtract', 1420, 100), + node('wrap', 'fract', 1600, 100), + node('half', 'float', 1600, 240, { value: 0.5 }), + node('centre', 'subtract', 1780, 120), + node('dist', 'abs', 1960, 120), + node('line', 'smoothstep', 2140, 120, { edge0: 0.4, edge1: 0.5 }), + node('ramp', 'gradient', 1960, 300, { colorA: '#05070f', colorB: '#0f2b4a', colorC: '#1a6b7a', mid: 0.55 }), + node('ink', 'color', 2140, 300, { value: '#9ff6ff' }), + node('mixed', 'mix', 2340, 200), + node('out', 'backgroundOutput', 2540, 200) + ], + edges: [ + edge('ptr', 'position', 'ptrUv', 'uv'), + edge('ptrUv', 'out', 'toward', 'a'), + edge('uv', 'out', 'toward', 'b'), + edge('toward', 'out', 'pull', 'a'), + edge('pullAmt', 'out', 'pull', 'b'), + edge('uv', 'out', 'bent', 'a'), + edge('pull', 'out', 'bent', 'b'), + edge('bent', 'out', 'warp', 'uv'), + edge('warp', 'out', 'field', 'uv'), + edge('field', 'out', 'scaled', 'a'), + edge('bands', 'out', 'scaled', 'b'), + edge('scaled', 'out', 'moving', 'a'), + edge('time', 'out', 'moving', 'b'), + edge('moving', 'out', 'wrap', 'a'), + edge('wrap', 'out', 'centre', 'a'), + edge('half', 'out', 'centre', 'b'), + edge('centre', 'out', 'dist', 'a'), + edge('dist', 'out', 'line', 'a'), + edge('field', 'out', 'ramp', 't'), + edge('ramp', 'out', 'mixed', 'a'), + edge('ink', 'out', 'mixed', 'b'), + edge('line', 'out', 'mixed', 't'), + edge('mixed', 'out', 'out', 'color') + ] + }, + { + key: 'metaballs', + label: 'Metaballs', + hint: 'Glossy blobs that melt into each other like a lava lamp; one of them follows your pointer.', + layer: { pointer: true, scroll: false }, + nodes: [ + node('uv', 'aspectUv', 40, 40), + node('ptr', 'pointer', 40, 220), + node('ptrUv', 'aspectUv', 240, 240), + node('blobs', 'blobs', 460, 60, { count: 6, radius: 0.11, speed: 0.45, smoothness: 0.14, softness: 0.006, attractRadius: 0.085 }), + node('chan', 'split', 260, 420), + node('bg', 'gradient', 700, 360, { colorA: '#120a2a', colorB: '#1c1450', colorC: '#2a1a5e', mid: 0.5 }), + node('fill', 'gradient', 700, 120, { colorA: '#ff4fa3', colorB: '#ff9a5a', colorC: '#ffe46b', mid: 0.5 }), + node('body', 'mix', 940, 200), + // GLSL leaves smoothstep undefined for edge0 >= edge1, so a falloff is One minus a rise + node('glowRise', 'smoothstep', 940, 420, { edge0: 0.0, edge1: 0.16 }), + node('glowW', 'oneMinus', 1000, 520), + node('glowAmt', 'float', 940, 560, { value: 0.35 }), + node('glow', 'multiply', 1140, 440), + node('glowCol', 'color', 1140, 600, { value: '#ff6ad5' }), + node('glowRgb', 'multiply', 1320, 460), + node('lit', 'add', 1500, 260), + node('out', 'backgroundOutput', 1700, 260) + ], + edges: [ + edge('uv', 'out', 'blobs', 'uv'), + edge('ptr', 'position', 'ptrUv', 'uv'), + edge('ptrUv', 'out', 'blobs', 'attract'), + edge('uv', 'out', 'chan', 'value'), + edge('chan', 'y', 'bg', 't'), + edge('chan', 'y', 'fill', 't'), + edge('bg', 'out', 'body', 'a'), + edge('fill', 'out', 'body', 'b'), + edge('blobs', 'mask', 'body', 't'), + edge('blobs', 'sdf', 'glowRise', 'a'), + edge('glowRise', 'out', 'glowW', 'a'), + edge('glowW', 'out', 'glow', 'a'), + edge('glowAmt', 'out', 'glow', 'b'), + edge('glowCol', 'out', 'glowRgb', 'a'), + edge('glow', 'out', 'glowRgb', 'b'), + edge('body', 'out', 'lit', 'a'), + edge('glowRgb', 'out', 'lit', 'b'), + edge('lit', 'out', 'out', 'color') + ] + }, + { + key: 'grain-dither', + label: 'Grain & dither', + hint: 'A soft dusk gradient broken into retro dither steps with film grain on top; a pool of light follows your pointer.', + layer: { pointer: true, scroll: false }, + nodes: [ + node('uv', 'aspectUv', 40, 40), + node('chan', 'split', 240, 40), + node('cloud', 'fbm', 240, 220, { scale: 2, octaves: 4, lacunarity: 2, gain: 0.5 }), + node('cloudAmt', 'float', 240, 400, { value: 0.22 }), + node('cloudS', 'multiply', 440, 260), + node('t', 'add', 620, 100), + node('ramp', 'gradient', 800, 80, { colorA: '#2b1b4a', colorB: '#c4577a', colorC: '#ffc98a', mid: 0.42 }), + node('ptr', 'pointer', 40, 560), + node('ptrUv', 'aspectUv', 240, 560), + node('dist', 'distance', 440, 520), + node('spotRise', 'smoothstep', 620, 460, { edge0: 0.0, edge1: 0.35 }), + node('spot', 'oneMinus', 660, 580), + node('spotAmt', 'float', 620, 680, { value: 0.35 }), + node('spotS', 'multiply', 800, 540), + node('lit', 'add', 1000, 200), + node('bayer', 'bayer', 1000, 420, { scale: 2 }), + node('half', 'float', 1000, 560, { value: 0.5 }), + node('centred', 'subtract', 1180, 440), + node('depth', 'float', 1180, 600, { value: 0.16 }), + node('dither', 'multiply', 1360, 460), + node('dithered', 'add', 1360, 220), + node('steps', 'posterize', 1540, 220, { steps: 7 }), + node('grain', 'grain', 1540, 420, { amount: 0.05, size: 2, animate: 1 }), + node('final', 'add', 1740, 260), + node('out', 'backgroundOutput', 1940, 260) + ], + edges: [ + edge('uv', 'out', 'chan', 'value'), + edge('uv', 'out', 'cloud', 'uv'), + edge('cloud', 'out', 'cloudS', 'a'), + edge('cloudAmt', 'out', 'cloudS', 'b'), + edge('chan', 'y', 't', 'a'), + edge('cloudS', 'out', 't', 'b'), + edge('t', 'out', 'ramp', 't'), + edge('ptr', 'position', 'ptrUv', 'uv'), + edge('uv', 'out', 'dist', 'a'), + edge('ptrUv', 'out', 'dist', 'b'), + edge('dist', 'out', 'spotRise', 'a'), + edge('spotRise', 'out', 'spot', 'a'), + edge('spot', 'out', 'spotS', 'a'), + edge('spotAmt', 'out', 'spotS', 'b'), + edge('ramp', 'out', 'lit', 'a'), + edge('spotS', 'out', 'lit', 'b'), + edge('bayer', 'out', 'centred', 'a'), + edge('half', 'out', 'centred', 'b'), + edge('centred', 'out', 'dither', 'a'), + edge('depth', 'out', 'dither', 'b'), + edge('lit', 'out', 'dithered', 'a'), + edge('dither', 'out', 'dithered', 'b'), + edge('dithered', 'out', 'steps', 'a'), + edge('steps', 'out', 'final', 'a'), + edge('grain', 'out', 'final', 'b'), + edge('final', 'out', 'out', 'color') + ] + } +]; + +function files() { + /** @type {Record} */ + const out = {}; + const index = { + format: FORMAT, + title: 'Fluid backgrounds', + license: 'CC0-1.0', + presets: PRESETS.map((p) => ({ key: p.key, label: p.label, hint: p.hint, file: p.key + '.json' })) + }; + out['index.json'] = JSON.stringify(index, null, '\t') + '\n'; + for (const p of PRESETS) { + const doc = { + format: FORMAT, + domain: 'background', + name: p.label, + preset: p.key, + layer: p.layer, + nodes: p.nodes, + edges: p.edges + }; + out[p.key + '.json'] = JSON.stringify(doc, null, '\t') + '\n'; + } + return out; +} + +const check = process.argv.includes('--check'); +let stale = 0; +for (const [name, text] of Object.entries(files())) { + const target = path.join(OUT, name); + if (check) { + const have = fs.existsSync(target) ? fs.readFileSync(target, 'utf8') : ''; + if (have !== text) { + console.log('STALE ' + name); + stale++; + } + } else { + fs.mkdirSync(OUT, { recursive: true }); + fs.writeFileSync(target, text); + console.log('wrote backgrounds/' + name); + } +} +if (check) { + console.log(stale ? stale + ' stale file(s) — run node tools/backgrounds/build.cjs' : 'backgrounds/ up to date'); + process.exit(stale ? 1 : 0); +}