diff --git a/apps/lab/app/components/pixel-noise-shaders-sandbox/ThePixelNoiseShadersSandbox.vue b/apps/lab/app/components/pixel-noise-shaders-sandbox/ThePixelNoiseShadersSandbox.vue
new file mode 100644
index 000000000..f0b88d60c
--- /dev/null
+++ b/apps/lab/app/components/pixel-noise-shaders-sandbox/ThePixelNoiseShadersSandbox.vue
@@ -0,0 +1,217 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/lab/app/components/pixel-noise-shaders-sandbox/index.global.vue b/apps/lab/app/components/pixel-noise-shaders-sandbox/index.global.vue
new file mode 100644
index 000000000..bcb782016
--- /dev/null
+++ b/apps/lab/app/components/pixel-noise-shaders-sandbox/index.global.vue
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/lab/app/components/pixel-noise-shaders-sandbox/shaders/fragment.glsl b/apps/lab/app/components/pixel-noise-shaders-sandbox/shaders/fragment.glsl
new file mode 100644
index 000000000..ffd721a80
--- /dev/null
+++ b/apps/lab/app/components/pixel-noise-shaders-sandbox/shaders/fragment.glsl
@@ -0,0 +1,10 @@
+precision highp float;
+
+varying vec3 vColor;
+
+void main() {
+ vec2 pc = gl_PointCoord;
+ float edge = min(min(pc.x, 1.0 - pc.x), min(pc.y, 1.0 - pc.y));
+ float alpha = smoothstep(0.0, 0.15, edge);
+ gl_FragColor = vec4(vColor, alpha);
+}
diff --git a/apps/lab/app/components/pixel-noise-shaders-sandbox/shaders/vertex.glsl b/apps/lab/app/components/pixel-noise-shaders-sandbox/shaders/vertex.glsl
new file mode 100644
index 000000000..ebe8b63e1
--- /dev/null
+++ b/apps/lab/app/components/pixel-noise-shaders-sandbox/shaders/vertex.glsl
@@ -0,0 +1,103 @@
+uniform vec2 uResolution;
+uniform float uTime;
+uniform sampler2D uDisplacementTexture;
+uniform vec3 uColorPrimary;
+uniform vec3 uColorSecondary;
+uniform float uZoom;
+uniform float uSizeContrast;
+uniform float uSpeed;
+uniform float uDisplacementStrength;
+uniform float uMinCellSize;
+uniform float uMaxCellSize;
+
+attribute float aIntensity;
+attribute float aAngle;
+
+varying vec3 vColor;
+
+// Simplex noise (Ashima Arts)
+vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
+vec2 mod289(vec2 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
+vec3 permute(vec3 x) { return mod289(((x * 34.0) + 10.0) * x); }
+
+float snoise(vec2 v) {
+ const vec4 C = vec4(
+ 0.211324865405187,
+ 0.366025403784439,
+ -0.577350269189626,
+ 0.024390243902439
+ );
+ vec2 i = floor(v + dot(v, C.yy));
+ vec2 x0 = v - i + dot(i, C.xx);
+ vec2 i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
+ vec4 x12 = x0.xyxy + C.xxzz;
+ x12.xy -= i1;
+ i = mod289(i);
+ vec3 p = permute(permute(i.y + vec3(0.0, i1.y, 1.0)) + i.x + vec3(0.0, i1.x, 1.0));
+ vec3 m = max(0.5 - vec3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), 0.0);
+ m = m * m;
+ m = m * m;
+ vec3 x = 2.0 * fract(p * C.www) - 1.0;
+ vec3 h = abs(x) - 0.5;
+ vec3 ox = floor(x + 0.5);
+ vec3 a0 = x - ox;
+ m *= 1.79284291400159 - 0.85373472095314 * (a0 * a0 + h * h);
+ vec3 g;
+ g.x = a0.x * x0.x + h.x * x0.y;
+ g.yz = a0.yz * x12.xz + h.yz * x12.yw;
+ return 130.0 * dot(m, g);
+}
+
+float snoise01(vec2 v) {
+ return (1.0 + snoise(v)) * 0.5;
+}
+
+float noise2d(vec2 st, float t) {
+ return snoise01(vec2(st.x + t * 0.005, st.y - t * 0.008));
+}
+
+// Domain warping — iquilezles.org/articles/warp
+float pattern(vec2 p, float t) {
+ vec2 q = vec2(
+ noise2d(p + vec2(0.0, 0.0), t),
+ noise2d(p + vec2(5.2, 1.3), t)
+ );
+ vec2 r = vec2(
+ noise2d(p + 4.0 * q + vec2(1.7, 9.2), t),
+ noise2d(p + 4.0 * q + vec2(8.3, 2.8), t)
+ );
+ return noise2d(p + 1.0 * r, t);
+}
+
+void main() {
+ vec3 newPosition = position;
+ float t = uTime * uSpeed;
+
+ // --- Fluid wave intensity: domain-warped noise drives point size across the full plane ---
+ float n = pattern(uv * uZoom + t * 0.015, t);
+ float waveIntensity = smoothstep(0.1, 0.85, n);
+
+ // --- Color ---
+ float noiseBlend = snoise01(uv * 3.0 + t * 0.005);
+ vColor = mix(uColorSecondary, uColorPrimary, waveIntensity * noiseBlend);
+ vColor = pow(vColor, vec3(2.0));
+
+ // --- Cursor displacement (subtle) ---
+ float displacementIntensity = texture2D(uDisplacementTexture, uv).r;
+ displacementIntensity = smoothstep(0.1, 0.9, displacementIntensity);
+ vec3 displacement = vec3(cos(aAngle), sin(aAngle), 0.0);
+ displacement *= displacementIntensity * uDisplacementStrength * aIntensity;
+ newPosition += displacement;
+
+ // --- Final position ---
+ vec4 modelPosition = modelMatrix * vec4(newPosition, 1.0);
+ vec4 viewPosition = viewMatrix * modelPosition;
+ vec4 projectedPosition = projectionMatrix * viewPosition;
+ gl_Position = projectedPosition;
+
+ // --- Point size driven by fluid wave ---
+ float sizeWave = pow(waveIntensity, uSizeContrast);
+ gl_PointSize = 0.1 * uResolution.y * sizeWave;
+ gl_PointSize *= (1.0 / -viewPosition.z);
+ gl_PointSize = clamp(gl_PointSize, uMinCellSize, uMaxCellSize);
+}
diff --git a/apps/lab/app/components/pixel-plasma-shader/ThePixelPlasmaShader.vue b/apps/lab/app/components/pixel-plasma-shader/ThePixelPlasmaShader.vue
new file mode 100644
index 000000000..5e6f6101d
--- /dev/null
+++ b/apps/lab/app/components/pixel-plasma-shader/ThePixelPlasmaShader.vue
@@ -0,0 +1,166 @@
+
+
+
+
+
+
+
+
+
diff --git a/apps/lab/app/components/pixel-plasma-shader/index.global.vue b/apps/lab/app/components/pixel-plasma-shader/index.global.vue
new file mode 100644
index 000000000..2cee9c3c6
--- /dev/null
+++ b/apps/lab/app/components/pixel-plasma-shader/index.global.vue
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/lab/app/components/pixel-plasma-shader/shaders/fragment.glsl b/apps/lab/app/components/pixel-plasma-shader/shaders/fragment.glsl
new file mode 100644
index 000000000..4ca41bd71
--- /dev/null
+++ b/apps/lab/app/components/pixel-plasma-shader/shaders/fragment.glsl
@@ -0,0 +1,16 @@
+precision highp float;
+
+varying vec3 vColor;
+varying float vMask;
+
+void main() {
+ vec2 pc = gl_PointCoord; // [0, 1] range
+
+ // Hard square clip using Chebyshev distance
+ vec2 centered = abs(pc - 0.5);
+ float squareDist = max(centered.x, centered.y);
+ if (squareDist >= 0.5) discard;
+
+ float alpha = vMask;
+ gl_FragColor = vec4(vColor, alpha);
+}
diff --git a/apps/lab/app/components/pixel-plasma-shader/shaders/vertex.glsl b/apps/lab/app/components/pixel-plasma-shader/shaders/vertex.glsl
new file mode 100644
index 000000000..4ba0d63ba
--- /dev/null
+++ b/apps/lab/app/components/pixel-plasma-shader/shaders/vertex.glsl
@@ -0,0 +1,111 @@
+#define PI 3.14159265358979
+
+uniform vec2 uResolution;
+uniform float uTime;
+uniform sampler2D uDisplacementTexture;
+uniform vec3 uColorPrimary;
+uniform vec3 uColorSecondary;
+uniform float uZoom;
+uniform float uSizeContrast;
+uniform float uSpeed;
+uniform float uSineFrequency;
+uniform float uSineSpeed;
+uniform float uSineAmplitude;
+uniform float uGridCellSize;
+uniform float uDisplacementStrength;
+uniform float uMinCellSize;
+uniform float uMaxCellSize;
+
+attribute float aIntensity;
+attribute float aAngle;
+
+varying vec3 vColor;
+varying float vMask;
+
+// Ashima Arts simplex noise
+vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
+vec2 mod289(vec2 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
+vec3 permute(vec3 x) { return mod289(((x * 34.0) + 10.0) * x); }
+
+float snoise(vec2 v) {
+ const vec4 C = vec4(
+ 0.211324865405187,
+ 0.366025403784439,
+ -0.577350269189626,
+ 0.024390243902439
+ );
+ vec2 i = floor(v + dot(v, C.yy));
+ vec2 x0 = v - i + dot(i, C.xx);
+ vec2 i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
+ vec4 x12 = x0.xyxy + C.xxzz;
+ x12.xy -= i1;
+ i = mod289(i);
+ vec3 p = permute(permute(i.y + vec3(0.0, i1.y, 1.0)) + i.x + vec3(0.0, i1.x, 1.0));
+ vec3 m = max(0.5 - vec3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), 0.0);
+ m = m * m;
+ m = m * m;
+ vec3 x = 2.0 * fract(p * C.www) - 1.0;
+ vec3 h = abs(x) - 0.5;
+ vec3 ox = floor(x + 0.5);
+ vec3 a0 = x - ox;
+ m *= 1.79284291400159 - 0.85373472095314 * (a0 * a0 + h * h);
+ vec3 g;
+ g.x = a0.x * x0.x + h.x * x0.y;
+ g.yz = a0.yz * x12.xz + h.yz * x12.yw;
+ return 130.0 * dot(m, g);
+}
+
+// FBM: 4 octaves of simplex noise
+float fbm(vec2 p) {
+ float v = 0.0, amp = 0.5, freq = 1.0, normFactor = 0.0;
+ for (int i = 0; i < 4; i++) {
+ v += (snoise(p * freq) * 0.5 + 0.5) * amp;
+ normFactor += amp;
+ amp *= 0.5;
+ freq *= 2.0;
+ }
+ return v / normFactor;
+}
+
+void main() {
+ vec3 newPosition = position;
+ float t = uTime * uSpeed;
+
+ // --- Domain-warped FBM → waveIntensity ---
+ vec2 q = vec2(
+ fbm(uv * 2.0 + t * 0.02),
+ fbm(uv * 2.0 + vec2(5.2, 1.3) + t * 0.02)
+ );
+ vec2 r = vec2(
+ fbm((uv + 2.0 * q) * 2.0 + vec2(1.7, 9.2)),
+ fbm((uv + 2.0 * q) * 2.0 + vec2(8.3, 2.8))
+ );
+ float waveIntensity = smoothstep(0.05, 0.95, fbm((uv + r) * 2.0 * uZoom));
+
+ // --- Color ---
+ float noiseBlend = snoise(uv * 3.0 + t * 0.005) * 0.5 + 0.5;
+ vColor = mix(uColorSecondary, uColorPrimary, waveIntensity * noiseBlend);
+
+ // --- Scrolling sine mask ---
+ float sineEdge = uSineAmplitude * sin(uv.y * uSineFrequency * PI + uTime * uSineSpeed);
+ float maskX = uv.x - (0.45 + sineEdge);
+ vMask = smoothstep(-0.05, 0.08, maskX);
+
+ // --- Cursor displacement (subtle) ---
+ float displacementIntensity = texture2D(uDisplacementTexture, uv).r;
+ displacementIntensity = smoothstep(0.1, 0.9, displacementIntensity);
+ vec3 displacement = vec3(cos(aAngle), sin(aAngle), 0.0);
+ displacement *= displacementIntensity * uDisplacementStrength * aIntensity;
+ newPosition += displacement;
+
+ // --- Final position + point size ---
+ vec4 mvPosition = modelViewMatrix * vec4(newPosition, 1.0);
+ gl_Position = projectionMatrix * mvPosition;
+
+ // Size that exactly fills one grid cell (no overlap)
+ float baseSize = uGridCellSize * projectionMatrix[1][1] * uResolution.y * 0.5 / (-mvPosition.z);
+
+ float sizeWave = pow(waveIntensity, uSizeContrast);
+ float sizeMask = mix(0.05, 1.0, vMask);
+ gl_PointSize = clamp(baseSize * sizeWave * sizeMask, uMinCellSize, uMaxCellSize);
+}
diff --git a/apps/lab/content/experiments/pixel-noise-shaders-sandbox.md b/apps/lab/content/experiments/pixel-noise-shaders-sandbox.md
new file mode 100644
index 000000000..525079742
--- /dev/null
+++ b/apps/lab/content/experiments/pixel-noise-shaders-sandbox.md
@@ -0,0 +1,9 @@
+---
+title: Pixel Noise Shaders Sandbox
+author: alvarosabu
+description: A sandbox for experimenting with pixel noise shaders
+thumbnail: /experiments/pixel-noise-shaders-sandbox.png
+tags: ['shaders', 'noise', 'glsl']
+date: 2026-03-25
+lastUpdated: 2026-03-25
+---
diff --git a/apps/lab/content/experiments/pixel-plasma-shader.md b/apps/lab/content/experiments/pixel-plasma-shader.md
new file mode 100644
index 000000000..601e1e790
--- /dev/null
+++ b/apps/lab/content/experiments/pixel-plasma-shader.md
@@ -0,0 +1,9 @@
+---
+title: Pixel Plasma Shader
+author: alvarosabu
+description: A sandbox for experimenting with pixel plasma shaders
+thumbnail: /experiments/pixel-plasma-shader.png
+tags: ['shaders', 'plasma', 'glsl']
+date: 2026-03-31
+lastUpdated: 2026-03-31
+---
diff --git a/apps/lab/public/experiments/pixel-noise-shaders-sandbox.png b/apps/lab/public/experiments/pixel-noise-shaders-sandbox.png
new file mode 100644
index 000000000..d8e09dfc4
Binary files /dev/null and b/apps/lab/public/experiments/pixel-noise-shaders-sandbox.png differ
diff --git a/apps/lab/public/experiments/pixel-plasma-shader.png b/apps/lab/public/experiments/pixel-plasma-shader.png
new file mode 100644
index 000000000..7fad76a3e
Binary files /dev/null and b/apps/lab/public/experiments/pixel-plasma-shader.png differ
diff --git a/apps/playground/src/pages/leches/advanced/folders-parent-child/TheChild.vue b/apps/playground/src/pages/leches/advanced/folders-parent-child/TheChild.vue
index 52f38d9f3..926320d4a 100644
--- a/apps/playground/src/pages/leches/advanced/folders-parent-child/TheChild.vue
+++ b/apps/playground/src/pages/leches/advanced/folders-parent-child/TheChild.vue
@@ -60,6 +60,11 @@ watch(number, (value) => {
// eslint-disable-next-line no-console
console.log('number', value)
})
+
+watch(paletteControls, (value) => {
+ // eslint-disable-next-line no-console
+ console.log('paletteControls', value)
+})
@@ -68,7 +73,7 @@ watch(number, (value) => {
-
+
diff --git a/packages/leches/src/composables/useControls.ts b/packages/leches/src/composables/useControls.ts
index ed4027e57..ddcec373a 100644
--- a/packages/leches/src/composables/useControls.ts
+++ b/packages/leches/src/composables/useControls.ts
@@ -92,7 +92,7 @@ const createControl = (key: string, value: any, type: LechesControlUnion['type']
if (folderName) {
baseControl.folder = folderName
- baseControl.label = baseControl.label.replace(folderName.replace(/[\u{1F300}-\u{1F9FF}]/gu, '').trim(), '').toLowerCase()
+ baseControl.label = baseControl.label.replace(folderName.replace(/\p{Extended_Pictographic}/gu, '').trim(), '').toLowerCase()
}
// Return the appropriate typed control based on type with all necessary properties
@@ -207,7 +207,7 @@ export const useControls = (
// If the control is part of a folder, prefix the key with the folder name
if (folderName) {
- key = `${folderName.replace(/[\u{1F300}-\u{1F9FF}]/gu, '').trim()}${capitalize(key)}`
+ key = `${folderName.replace(/\p{Extended_Pictographic}/gu, '').trim()}${capitalize(key)}`
}
uniqueKey = `${uuid}-${key}`