PageSourceSearch

https://huegrid.app/_next/static/chunks/c6fd0fb1e905a614.js?dpl=dpl_9xcVgRgoaoGesozZeCVbMcqm8sBe

js huegrid.app collected 2026-10-07 03:12:34 UTC 304,219 bytes, 8,764 lines download raw bytes

1(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,204776,293457,19529,e=>{"use strict";let o=`#version 300 es
2precision highp float;
3
4in vec2 vUv;
5out vec4 fragColor;
6
7uniform float u_time;
8uniform float u_speed;
9uniform vec3 u_colors[12];
10uniform float u_colorPositions[12];
11uniform int u_colorCount;
12uniform float u_direction;
13uniform float u_grain;
14uniform vec2 u_resolution;
15
16// Blend uniforms
17uniform int u_blendMode;
18uniform float u_blendIntensity;
19uniform float u_blendOffset;
20uniform float u_blendRotation;
21uniform float u_blendScale;
22uniform int u_blendAnimRotation;
23uniform int u_blendAnimIntensity;
24uniform int u_blendAnimOffset;
25uniform float u_blendAnimSpeed;
26uniform float u_blendNoiseMask;
27uniform float u_blendNoiseScale;
28uniform float u_blendFalloff;
29uniform int u_blendDualLayer;
30uniform int u_blendMode2;
31uniform float u_blendIntensity2;
32uniform float u_blendOffset2;
33uniform float u_blendRotation2;
34uniform float u_blendScale2;
35
36// Simple mode uniforms
37uniform int u_simpleType;   // 0=linear, 1=radial, 2=geometric
38uniform float u_centerX;
39uniform float u_centerY;
40uniform float u_radius;
41uniform int u_shape;        // 0=diamond, 1=conic, 2=square, 3=burst
42uniform float u_segments;
43uniform float u_rotation;
44uniform float u_scale;
45uniform float u_sharpness;
46uniform float u_distortion;
47uniform float u_swirl;
48
49// Figma-style gradient line endpoints (for linear mode)
50uniform vec2 u_gradientStart;
51uniform vec2 u_gradientEnd;
52
53__COMMON__
54
55#define TAU 6.28318530718
56#define PI 3.14159265359
57
58// Distortion — noise-based UV warp
59vec2 applyDistortion(vec2 uv, float distortion, float t) {
60  float n1 = snoise(uv * 3.0 + t * 0.2);
61  float n2 = snoise(uv * 3.0 + vec2(5.2, 1.3) + t * 0.2);
62  return uv + vec2(n1, n2) * distortion * 0.15;
63}
64
65// Swirl — radial UV twist
66vec2 applySwirl(vec2 uv, float swirl) {
67  vec2 center = vec2(0.5);
68  vec2 delta = uv - center;
69  float dist = length(delta);
70  float angle = swirl * 6.2832 * (1.0 - smoothstep(0.0, 0.5, dist));
71  float s = sin(angle), c = cos(angle);
72  return center + vec2(c * delta.x - s * delta.y, s * delta.x + c * delta.y);
73}
74
75void main() {
76  vec2 uv = vUv;
77  float time = u_time * u_speed;
78  float t = 0.0;
79
80  // Correct for aspect ratio
81  float aspect = u_resolution.x / u_resolution.y;
82  uv = applyMouseWarp(uv, aspect);
83
84  // Apply distortion and swirl
85  if (u_distortion > 0.0) uv = applyDistortion(uv, u_distortion, time);
86  if (u_swirl > 0.0) uv = applySwirl(uv, u_swirl);
87
88  if (u_simpleType == 0) {
89    // === LINEAR (Figma-style two-point) ===
90    vec2 gradLine = u_gradientEnd - u_gradientStart;
91    float gradLen2 = dot(gradLine, gradLine);
92    // Project UV onto gradient line: t=0 at start, t=1 at end, overflows for negative space
93    t = (gradLen2 > 0.00001) ? dot(uv - u_gradientStart, gradLine) / gradLen2 : 0.5;
94
95  } else if (u_simpleType == 1) {
96    // === RADIAL ===
97    vec2 center = vec2(u_centerX, u_centerY);
98    vec2 diff = uv - center;
99    diff.x *= aspect;
100    // Apply direction as rotation for elliptical radial gradients
101    float rAngle = radians(u_direction);
102    float rCs = cos(rAngle);
103    float rSn = sin(rAngle);
104    diff = vec2(diff.x * rCs - diff.y * rSn, diff.x * rSn + diff.y * rCs);
105    float dist = length(diff);
106    t = dist / u_radius;
107
108  } else {
109    // === GEOMETRIC ===
110    vec2 center = vec2(u_centerX, u_centerY);
111    vec2 diff = uv - center;
112    diff.x *= aspect;
113
114    // Apply rotation
115    float rotAngle = radians(u_rotation);
116    float cs = cos(rotAngle);
117    float sn = sin(rotAngle);
118    vec2 rotated = vec2(diff.x * cs - diff.y * sn, diff.x * sn + diff.y * cs);
119
120    // Apply scale
121    rotated /= max(u_scale, 0.01);
122
123    if (u_shape == 0) {
124      // Diamond
125      t = abs(rotated.x) + abs(rotated.y);
126    } else if (u_shape == 1) {
127      // Conic (angular sweep)
128      float angle = atan(rotated.y, rotated.x);
129      t = (angle + PI) / TAU;
130    } else if (u_shape == 2) {
131      // Square
132      t = max(abs(rotated.x), abs(rotated.y));
133    } else {
134      // Burst (radial segments)
135      float angle = atan(rotated.y, rotated.x);
136      float seg = TAU / max(u_segments, 3.0);
137      float angular = abs(mod(angle + seg * 0.5, seg) - seg * 0.5);
138      float dist = length(rotated);
139      t = dist * (1.0 + angular * u_sharpness * 4.0);
140    }
141
142    // Normalize geometric shapes to roughly 0-1 range
143    t = t * 2.0;
144  }
145
146  // Subtle animation displacement when animated
147  if (u_speed > 0.0 && time > 0.0) {
148    t += snoise(uv * 2.0 + time * 0.3) * 0.02;
149  }
150
151  // Linear mode: allow t overflow for negative-space gradients.
152  // Radial/geometric: clamp to [0,1] as they don't support negative space.
153  if (u_simpleType != 0) {
154    t = clamp(t, 0.0, 1.0);
155  }
156
157  // Image-driven gradient map: luminance → t
158  float imgLum = imageLuminance(uv, aspect);
159  if (imgLum >= 0.0) {
160    t = mix(t, imgLum, u_imageIntensity);
161  }
162
163  // Mix sharpness: smooth vs hard edges for geometric
164  if (u_simpleType == 2 && u_sharpness > 0.5) {
165    float sharpFactor = (u_sharpness - 0.5) * 2.0;
166    float steps = mix(1.0, float(u_colorCount), sharpFactor);
167    t = floor(t * steps) / max(steps - 1.0, 1.0);
168    t = clamp(t, 0.0, 1.0);
169  }
170
171  vec3 color = mixColors(u_colors, u_colorPositions, u_colorCount, t);
172  color += grain(uv, time, u_grain);
173
174  // Blend math expects t in [0,1] — linear mode allows overflow for negative
175  // space in the base color, but blend falloff / layering would break with
176  // extreme t values, so we use a clamped copy for those computations.
177  float tBlend = clamp(t, 0.0, 1.0);
178
179  // Self-blend
180  if (u_blendMode > 0) {
181    float bRotation = u_blendAnimRotation > 0 ? animatedBlendRotation(u_blendRotation, time, u_blendAnimSpeed) : u_blendRotation;
182    float bOffset = u_blendAnimOffset > 0 ? animatedBlendOffset(u_blendOffset, time, u_blendAnimSpeed) : u_blendOffset;
183    float bIntensity = u_blendAnimIntensity > 0 ? animatedBlendIntensity(u_blendIntensity, time, u_blendAnimSpeed) : u_blendIntensity;
184    bIntensity = noiseModulatedIntensity(bIntensity, uv, time, u_blendNoiseMask, u_blendNoiseScale);
185    bIntensity = applyBlendFalloff(bIntensity, tBlend, u_blendFalloff);
186    float tB = blendLayerT(uv, tBlend, u_resolution, u_direction, bOffset, bRotation, u_blendScale);
187    vec3 blendCol = mixColors(u_colors, u_colorPositions, u_colorCount, tB);
188    color = applyBlendMode(u_blendMode, color, blendCol, bIntensity);
189    if (u_blendDualLayer > 0 && u_blendMode2 > 0) {
190      float tB2 = blendLayerT(uv, tBlend, u_resolution, u_direction, u_blendOffset2, u_blendRotation2, u_blendScale2);
191      vec3 blendCol2 = mixColors(u_colors, u_colorPositions, u_colorCount, tB2);
192      color = applyBlendMode(u_blendMode2, color, blendCol2, u_blendIntensity2);
193    }
194  }
195
196  fragColor = outputColor(color, gl_FragCoord.xy, 1.0);
197}
198`,t=`#version 300 es
199precision highp float;
200
201in vec2 vUv;
202out vec4 fragColor;
203
204uniform float u_time;
205uniform float u_speed;
206uniform vec3 u_colors[12];
207uniform float u_colorPositions[12];
208uniform int u_colorCount;
209uniform float u_direction;
210uniform float u_grain;
211uniform vec2 u_resolution;
212
213// Blend uniforms
214uniform int u_blendMode;
215uniform float u_blendIntensity;
216uniform float u_blendOffset;
217uniform float u_blendRotation;
218uniform float u_blendScale;
219uniform int u_blendAnimRotation;
220uniform int u_blendAnimIntensity;
221uniform int u_blendAnimOffset;
222uniform float u_blendAnimSpeed;
223uniform float u_blendNoiseMask;
224uniform float u_blendNoiseScale;
225uniform float u_blendFalloff;
226uniform int u_blendDualLayer;
227uniform int u_blendMode2;
228uniform float u_blendIntensity2;
229uniform float u_blendOffset2;
230uniform float u_blendRotation2;
231uniform float u_blendScale2;
232
233uniform float u_frequency;
234uniform int u_octaves;
235uniform float u_amplitude;
236uniform float u_lacunarity;
237uniform float u_seed;
238
239// Domain warp uniforms (Inigo Quilez technique)
240uniform float u_domainWarp;       // 0 = off, >0 = warp strength
241uniform int   u_domainWarpDepth;  // Number of nested warp iterations (1-3)
242uniform float u_domainWarpScale;  // Scale multiplier for warp noise
243uniform float u_chromaticShift;   // Per-channel color offset for prismatic effect
244
245__COMMON__
246
247// Domain-warped FBM (Inigo Quilez "warping" technique)
248// Nested FBM: f(p + fbm(p + fbm(p))) — produces organic, liquid-like patterns
249float domainWarpFbm(vec2 p, float warpStr, int depth, float warpScale) {
250  // First warp layer
251  vec2 q = vec2(
252    fbm(p + vec2(0.0, 0.0), warpScale, u_octaves, u_amplitude, u_lacunarity),
253    fbm(p + vec2(5.2, 1.3), warpScale, u_octaves, u_amplitude, u_lacunarity)
254  );
255
256  if (depth <= 1) {
257    return fbm(p + q * warpStr, 1.0, u_octaves, u_amplitude, u_lacunarity);
258  }
259
260  // Second warp layer — deeper nested warping
261  vec2 r = vec2(
262    fbm(p + q * warpStr + vec2(1.7, 9.2), warpScale, u_octaves, u_amplitude, u_lacunarity),
263    fbm(p + q * warpStr + vec2(8.3, 2.8), warpScale, u_octaves, u_amplitude, u_lacunarity)
264  );
265
266  if (depth <= 2) {
267    return fbm(p + r * warpStr, 1.0, u_octaves, u_amplitude, u_lacunarity);
268  }
269
270  // Third warp layer — maximum depth for ultra-smooth liquid feel
271  vec2 s = vec2(
272    fbm(p + r * warpStr + vec2(3.1, 7.4), warpScale, u_octaves, u_amplitude, u_lacunarity),
273    fbm(p + r * warpStr + vec2(6.5, 4.1), warpScale, u_octaves, u_amplitude, u_lacunarity)
274  );
275
276  return fbm(p + s * warpStr, 1.0, u_octaves, u_amplitude, u_lacunarity);
277}
278
279void main() {
280  vec2 uv = vUv;
281  float time = u_time * u_speed;
282  float aspect = u_resolution.x / max(u_resolution.y, 1.0);
283  uv = applyMouseWarp(uv, aspect);
284
285  // Direction-based base gradient
286  float angle = radians(u_direction);
287  vec2 dir = vec2(cos(angle), sin(angle));
288  float t = dot(uv - 0.5, dir) + 0.5;
289
290  // Noise displacement with time animation
291  vec2 noiseCoord = uv * u_frequency + vec2(u_seed * 0.1);
292  noiseCoord += vec2(time * 0.15, time * 0.1);
293
294  float n;
295  if (u_domainWarp > 0.0) {
296    // Domain-warped FBM — liquid reality effect
297    n = domainWarpFbm(noiseCoord, u_domainWarp * 4.0, u_domainWarpDepth, u_domainWarpScale);
298  } else {
299    // Standard FBM noise
300    n = fbm(noiseCoord, 1.0, u_octaves, u_amplitude, u_lacunarity);
301  }
302
303  // Mix direction gradient with noise
304  t += n * 0.35;
305  t = clamp(t, 0.0, 1.0);
306
307  // Image-driven gradient modulation
308  float imgLum = imageLuminance(uv, aspect);
309  if (imgLum >= 0.0) {
310    t = mix(t, imgLum, u_imageIntensity);
311  }
312
313  vec3 color;
314  if (u_domainWarp > 0.0 && u_chromaticShift > 0.0) {
315    // Chromatic aberration: offset each channel for prismatic liquid look
316    float shift = u_chromaticShift * 0.02;
317    vec2 coordR = noiseCoord + vec2(shift, 0.0);
318    vec2 coordB = noiseCoord - vec2(shift, 0.0);
319    float nR = domainWarpFbm(coordR, u_domainWarp * 4.0, u_domainWarpDepth, u_domainWarpScale);
320    float nB = domainWarpFbm(coordB, u_domainWarp * 4.0, u_domainWarpDepth, u_domainWarpScale);
321    float tR = clamp(dot(uv - 0.5, dir) + 0.5 + nR * 0.35, 0.0, 1.0);
322    float tB = clamp(dot(uv - 0.5, dir) + 0.5 + nB * 0.35, 0.0, 1.0);
323    vec3 colorCenter = mixColors(u_colors, u_colorPositions, u_colorCount, t);
324    vec3 colorR = mixColors(u_colors, u_colorPositions, u_colorCount, tR);
325    vec3 colorB = mixColors(u_colors, u_colorPositions, u_colorCount, tB);
326    color = vec3(colorR.r, colorCenter.g, colorB.b);
327  } else {
328    color = mixColors(u_colors, u_colorPositions, u_colorCount, t);
329  }
330
331  // Add grain
332  color += grain(uv, time, u_grain);
333
334  // Self-blend
335  if (u_blendMode > 0) {
336    float bRotation = u_blendAnimRotation > 0 ? animatedBlendRotation(u_blendRotation, time, u_blendAnimSpeed) : u_blendRotation;
337    float bOffset = u_blendAnimOffset > 0 ? animatedBlendOffset(u_blendOffset, time, u_blendAnimSpeed) : u_blendOffset;
338    float bIntensity = u_blendAnimIntensity > 0 ? animatedBlendIntensity(u_blendIntensity, time, u_blendAnimSpeed) : u_blendIntensity;
339    bIntensity = noiseModulatedIntensity(bIntensity, uv, time, u_blendNoiseMask, u_blendNoiseScale);
340    bIntensity = applyBlendFalloff(bIntensity, t, u_blendFalloff);
341    float tB = blendLayerT(uv, t, u_resolution, u_direction, bOffset, bRotation, u_blendScale);
342    vec3 blendCol = mixColors(u_colors, u_colorPositions, u_colorCount, tB);
343    color = applyBlendMode(u_blendMode, color, blendCol, bIntensity);
344    if (u_blendDualLayer > 0 && u_blendMode2 > 0) {
345      float tB2 = blendLayerT(uv, t, u_resolution, u_direction, u_blendOffset2, u_blendRotation2, u_blendScale2);
346      vec3 blendCol2 = mixColors(u_colors, u_colorPositions, u_colorCount, tB2);
347      color = applyBlendMode(u_blendMode2, color, blendCol2, u_blendIntensity2);
348    }
349  }
350
351  fragColor = outputColor(color, gl_FragCoord.xy, 1.0);
352}
353`;var i=e.i(780371);let n=`#version 300 es
354precision highp float;
355
356in vec2 vUv;
357out vec4 fragColor;
358
359uniform float u_time;
360uniform float u_speed;
361uniform vec3 u_colors[12];
362uniform float u_colorPositions[12];
363uniform int u_colorCount;
364uniform float u_direction;
365uniform float u_grain;
366uniform vec2 u_resolution;
367
368// Blend uniforms
369uniform int u_blendMode;
370uniform float u_blendIntensity;
371uniform float u_blendOffset;
372uniform float u_blendRotation;
373uniform float u_blendScale;
374uniform int u_blendAnimRotation;
375uniform int u_blendAnimIntensity;
376uniform int u_blendAnimOffset;
377uniform float u_blendAnimSpeed;
378uniform float u_blendNoiseMask;
379uniform float u_blendNoiseScale;
380uniform float u_blendFalloff;
381uniform int u_blendDualLayer;
382uniform int u_blendMode2;
383uniform float u_blendIntensity2;
384uniform float u_blendOffset2;
385uniform float u_blendRotation2;
386uniform float u_blendScale2;
387
388uniform float u_centerX;
389uniform float u_centerY;
390uniform float u_radius;
391uniform float u_falloff;
392uniform float u_distortion;
393uniform float u_noiseIntensity;
394
395__COMMON__
396
397void main() {
398  vec2 uv = vUv;
399  float time = u_time * u_speed;
400  float aspect = u_resolution.x / max(u_resolution.y, 1.0);
401  uv = applyMouseWarp(uv, aspect);
402
403  vec2 center = vec2(u_centerX, u_centerY);
404  vec2 p = uv - center;
405  p.x *= aspect;
406
407  // Noise distortion
408  float n = snoise(uv * 3.0 + time * 0.2) * u_distortion;
409  float n2 = snoise(uv * 6.0 - time * 0.15) * u_distortion * 0.5;
410  p += vec2(n, n2);
411
412  // Radial distance
413  float dist = length(p) / u_radius;
414
415  // Falloff curve
416  float t = pow(dist, 1.0 / max(u_falloff, 0.1));
417
418  // Add noise variation
419  t += snoise(uv * 5.0 + time * 0.1) * u_noiseIntensity * 0.2;
420  t = clamp(t, 0.0, 1.0);
421
422  // Image-driven gradient modulation
423  float imgLum = imageLuminance(uv, aspect);
424  if (imgLum >= 0.0) {
425    t = mix(t, imgLum, u_imageIntensity);
426  }
427
428  vec3 color = mixColors(u_colors, u_colorPositions, u_colorCount, t);
429
430  // Grain
431  color += grain(uv, time, u_grain);
432
433  // Self-blend
434  if (u_blendMode > 0) {
435    float bRotation = u_blendAnimRotation > 0 ? animatedBlendRotation(u_blendRotation, time, u_blendAnimSpeed) : u_blendRotation;
436    float bOffset = u_blendAnimOffset > 0 ? animatedBlendOffset(u_blendOffset, time, u_blendAnimSpeed) : u_blendOffset;
437    float bIntensity = u_blendAnimIntensity > 0 ? animatedBlendIntensity(u_blendIntensity, time, u_blendAnimSpeed) : u_blendIntensity;
438    bIntensity = noiseModulatedIntensity(bIntensity, uv, time, u_blendNoiseMask, u_blendNoiseScale);
439    bIntensity = applyBlendFalloff(bIntensity, t, u_blendFalloff);
440    float tB = blendLayerT(uv, t, u_resolution, u_direction, bOffset, bRotation, u_blendScale);
441    vec3 blendCol = mixColors(u_colors, u_colorPositions, u_colorCount, tB);
442    color = applyBlendMode(u_blendMode, color, blendCol, bIntensity);
443    if (u_blendDualLayer > 0 && u_blendMode2 > 0) {
444      float tB2 = blendLayerT(uv, t, u_resolution, u_direction, u_blendOffset2, u_blendRotation2, u_blendScale2);
445      vec3 blendCol2 = mixColors(u_colors, u_colorPositions, u_colorCount, tB2);
446      color = applyBlendMode(u_blendMode2, color, blendCol2, u_blendIntensity2);
447    }
448  }
449
450  fragColor = outputColor(color, gl_FragCoord.xy, 1.0);
451}
452`,l=`#version 300 es
453precision highp float;
454
455in vec2 vUv;
456out vec4 fragColor;
457
458uniform float u_time;
459uniform float u_speed;
460uniform vec3 u_colors[12];
461uniform float u_colorPositions[12];
462uniform int u_colorCount;
463uniform float u_direction;
464uniform float u_grain;
465uniform vec2 u_resolution;
466
467// Blend uniforms
468uniform int u_blendMode;
469uniform float u_blendIntensity;
470uniform float u_blendOffset;
471uniform float u_blendRotation;
472uniform float u_blendScale;
473uniform int u_blendAnimRotation;
474uniform int u_blendAnimIntensity;
475uniform int u_blendAnimOffset;
476uniform float u_blendAnimSpeed;
477uniform float u_blendNoiseMask;
478uniform float u_blendNoiseScale;
479uniform float u_blendFalloff;
480uniform int u_blendDualLayer;
481uniform int u_blendMode2;
482uniform float u_blendIntensity2;
483uniform float u_blendOffset2;
484uniform float u_blendRotation2;
485uniform float u_blendScale2;
486
487uniform int u_ditherTechnique;
488uniform int u_matrixSize;
489uniform int u_quantizationLevels;
490uniform float u_patternScale;
491uniform float u_waveSpeed;
492uniform float u_waveFrequency;
493uniform float u_warpIntensity;
494uniform float u_grainIntensity;
495
496__COMMON__
497
498// ---- Hash for random / stochastic dithering ----
499float hash21(vec2 p) {
500  p = fract(p * vec2(123.34, 456.21));
501  p += dot(p, p + 45.32);
502  return fract(p.x * p.y);
503}
504
505// ---- Clustered-dot threshold ----
506// Radial distance within a grid cell creates round dot clusters
507float clusteredDot(vec2 p, float cellSize) {
508  vec2 cell = floor(p / cellSize);
509  vec2 local = fract(p / cellSize) - 0.5;
510  // Distance from cell center, normalized 0..1
511  float dist = length(local) * 2.0;
512  // Slight rotation per cell for visual variety
513  float angle = hash21(cell) * 6.283;
514  float s = sin(angle), c = cos(angle);
515  vec2 rotated = vec2(c * local.x - s * local.y, s * local.x + c * local.y);
516  dist = length(rotated) * 2.0;
517  return clamp(dist, 0.0, 1.0);
518}
519
520// ---- Blue noise approximation ----
521// Multi-octave simplex noise produces smooth, non-repeating threshold
522float blueNoise(vec2 p) {
523  float n = 0.0;
524  n += snoise(p * 0.8) * 0.35;
525  n += snoise(p * 1.7 + 31.7) * 0.30;
526  n += snoise(p * 3.4 + 73.1) * 0.20;
527  n += snoise(p * 7.1 + 117.3) * 0.15;
528  return n * 0.5 + 0.5; // normalize to [0, 1]
529}
530
531// ---- Floyd-Steinberg approximation ----
532// Fine blue noise + luminance-aware bias → smooth organic dithering
533float floydSteinbergApprox(vec2 p, float lum) {
534  // Fine-grained noise for smooth error-diffusion look
535  float n = snoise(p * 1.2) * 0.3 + snoise(p * 2.8 + 41.0) * 0.25
536          + snoise(p * 5.5 + 89.0) * 0.2 + snoise(p * 11.0 + 137.0) * 0.15;
537  n = n * 0.5 + 0.5;
538  // Luminance-aware bias: push dark areas darker, light areas lighter
539  float bias = (lum - 0.5) * 0.15;
540  return clamp(n + bias, 0.0, 1.0);
541}
542
543// ---- Atkinson approximation ----
544// Coarser, higher contrast — preserves detail via partial error spread (75%)
545float atkinsonApprox(vec2 p, float lum) {
546  // Coarser noise pattern
547  float n = snoise(p * 0.9) * 0.4 + snoise(p * 2.1 + 53.0) * 0.35
548          + snoise(p * 4.0 + 97.0) * 0.25;
549  n = n * 0.5 + 0.5;
550  // Higher contrast threshold — sharper transitions
551  n = smoothstep(0.2, 0.8, n);
552  // Stronger luminance bias for detail preservation
553  float bias = (lum - 0.5) * 0.25;
554  return clamp(n + bias, 0.0, 1.0);
555}
556
557// ---- Jarvis-Judice-Ninke approximation ----
558// Wide kernel averaging → softer, slightly blurred transitions
559float jarvisApprox(vec2 p) {
560  // Average noise over a wider area (5-tap kernel)
561  float n = 0.0;
562  n += snoise(p * 1.0) * 0.2;
563  n += snoise(p * 1.0 + vec2(1.5, 0.0)) * 0.15;
564  n += snoise(p * 1.0 + vec2(-1.5, 0.0)) * 0.15;
565  n += snoise(p * 1.0 + vec2(0.0, 1.5)) * 0.15;
566  n += snoise(p * 1.0 + vec2(0.0, -1.5)) * 0.15;
567  // Additional finer detail
568  n += snoise(p * 2.5 + 67.0) * 0.1;
569  n += snoise(p * 5.0 + 131.0) * 0.1;
570  return n * 0.5 + 0.5;
571}
572
573vec3 quantize(vec3 color, float levels) {
574  return floor(color * levels + 0.5) / levels;
575}
576
577void main() {
578  vec2 uv = vUv;
579  float time = u_time * u_speed * u_waveSpeed;
580  float aspect = u_resolution.x / max(u_resolution.y, 1.0);
581  uv = applyMouseWarp(uv, aspect);
582
583  vec2 p = uv;
584  p.x *= aspect;
585
586  // Domain warping
587  float warp = snoise(p * 2.0 + time * 0.3);
588  p += warp * u_warpIntensity * 0.3;
589
590  // Wave distortion
591  float wave = sin(p.x * u_waveFrequency + time) * 0.1;
592  wave += cos(p.y * u_waveFrequency * 0.7 + time * 0.8) * 0.05;
593
594  // Base gradient
595  float angle = radians(u_direction);
596  vec2 dir = vec2(cos(angle), sin(angle));
597  float t = dot(uv - 0.5 + vec2(wave), dir) + 0.5;
598  t += snoise(p * 3.0 + time * 0.2) * 0.2;
599  t = clamp(t, 0.0, 1.0);
600
601  // Image luminance drives dither pattern
602  float imgLum = imageLuminance(uv, aspect);
603  if (imgLum >= 0.0) {
604    t = mix(t, imgLum, u_imageIntensity);
605  }
606
607  vec3 color = mixColors(u_colors, u_colorPositions, u_colorCount, t);
608
609  // ---- Dithering ----
610  vec2 pixelCoord = gl_FragCoord.xy / u_patternScale;
611  float levels = float(u_quantizationLevels);
612  float threshold;
613  vec3 dithered;
614
615  if (u_ditherTechnique == 0) {
616    // ---- Bayer ordered dithering ----
617    if (u_matrixSize <= 2) {
618      threshold = bayer2(pixelCoord);
619    } else if (u_matrixSize <= 4) {
620      threshold = bayer4(pixelCoord);
621    } else {
622      threshold = bayer8(pixelCoord);
623    }
624    dithered = floor(color * levels + threshold) / levels;
625
626  } else if (u_ditherTechnique == 1) {
627    // ---- Blue noise dithering ----
628    threshold = blueNoise(pixelCoord);
629    dithered = floor(color * levels + threshold) / levels;
630
631  } else if (u_ditherTechnique == 2) {
632    // ---- Clustered dot dithering ----
633    float cellSize = max(u_patternScale * 4.0, 2.0);
634    threshold = clusteredDot(gl_FragCoord.xy, cellSize);
635    dithered = floor(color * levels + threshold) / levels;
636
637  } else if (u_ditherTechnique == 3) {
638    // ---- Floyd-Steinberg approximation ----
639    float lum = sg_luminance(color);
640    threshold = floydSteinbergApprox(pixelCoord, lum);
641    dithered = floor(color * levels + threshold) / levels;
642
643  } else if (u_ditherTechnique == 4) {
644    // ---- Atkinson approximation ----
645    float lum = sg_luminance(color);
646    threshold = atkinsonApprox(pixelCoord, lum);
647    // Atkinson uses ~75% error propagation: slightly fewer effective levels
648    float atkinsonLevels = max(levels * 0.75, 2.0);
649    dithered = floor(color * atkinsonLevels + threshold) / atkinsonLevels;
650
651  } else if (u_ditherTechnique == 5) {
652    // ---- Jarvis-Judice-Ninke approximation ----
653    threshold = jarvisApprox(pixelCoord);
654    dithered = floor(color * levels + threshold) / levels;
655
656  } else {
657    // ---- Random (white noise) dithering ----
658    threshold = hash21(pixelCoord + fract(time * 0.1));
659    dithered = floor(color * levels + threshold) / levels;
660  }
661
662  // Mix original and dithered based on grain
663  color = mix(color, dithered, 0.85 + u_grainIntensity * 0.15);
664
665  // Add subtle grain
666  color += grain(uv, time, u_grain);
667
668  // Self-blend
669  if (u_blendMode > 0) {
670    float bRotation = u_blendAnimRotation > 0 ? animatedBlendRotation(u_blendRotation, time, u_blendAnimSpeed) : u_blendRotation;
671    float bOffset = u_blendAnimOffset > 0 ? animatedBlendOffset(u_blendOffset, time, u_blendAnimSpeed) : u_blendOffset;
672    float bIntensity = u_blendAnimIntensity > 0 ? animatedBlendIntensity(u_blendIntensity, time, u_blendAnimSpeed) : u_blendIntensity;
673    bIntensity = noiseModulatedIntensity(bIntensity, uv, time, u_blendNoiseMask, u_blendNoiseScale);
674    bIntensity = applyBlendFalloff(bIntensity, t, u_blendFalloff);
675    float tB = blendLayerT(uv, t, u_resolution, u_direction, bOffset, bRotation, u_blendScale);
676    vec3 blendCol = mixColors(u_colors, u_colorPositions, u_colorCount, tB);
677    color = applyBlendMode(u_blendMode, color, blendCol, bIntensity);
678    if (u_blendDualLayer > 0 && u_blendMode2 > 0) {
679      float tB2 = blendLayerT(uv, t, u_resolution, u_direction, u_blendOffset2, u_blendRotation2, u_blendScale2);
680      vec3 blendCol2 = mixColors(u_colors, u_colorPositions, u_colorCount, tB2);
681      color = applyBlendMode(u_blendMode2, color, blendCol2, u_blendIntensity2);
682    }
683  }
684
685  fragColor = outputColor(color, gl_FragCoord.xy, 1.0);
686}
687`,a=`#version 300 es
688precision highp float;
689
690in vec2 vUv;
691out vec4 fragColor;
692
693uniform float u_time;
694uniform float u_speed;
695uniform vec3 u_colors[12];
696uniform float u_colorPositions[12];
697uniform int u_colorCount;
698uniform float u_direction;
699uniform float u_grain;
700uniform vec2 u_resolution;
701
702// Blend uniforms
703uniform int u_blendMode;
704uniform float u_blendIntensity;
705uniform float u_blendOffset;
706uniform float u_blendRotation;
707uniform float u_blendScale;
708uniform int u_blendAnimRotation;
709uniform int u_blendAnimIntensity;
710uniform int u_blendAnimOffset;
711uniform float u_blendAnimSpeed;
712uniform float u_blendNoiseMask;
713uniform float u_blendNoiseScale;
714uniform float u_blendFalloff;
715uniform int u_blendDualLayer;
716uniform int u_blendMode2;
717uniform float u_blendIntensity2;
718uniform float u_blendOffset2;
719uniform float u_blendRotation2;
720uniform float u_blendScale2;
721
722uniform float u_dotSize;
723uniform float u_spacing;
724uniform float u_angle;
725uniform int u_shape;
726uniform float u_contrast;
727
728__COMMON__
729
730float halftoneShape(vec2 p, int shape) {
731  if (shape == 1) {
732    // Square
733    vec2 d = abs(p);
734    return max(d.x, d.y);
735  } else if (shape == 2) {
736    // Diamond
737    return abs(p.x) + abs(p.y);
738  }
739  // Circle (default)
740  return length(p);
741}
742
743void main() {
744  vec2 uv = vUv;
745  float time = u_time * u_speed;
746  float aspect = u_resolution.x / max(u_resolution.y, 1.0);
747  uv = applyMouseWarp(uv, aspect);
748
749  // Base gradient
750  float angle = radians(u_direction);
751  vec2 dir = vec2(cos(angle), sin(angle));
752  float t = dot(uv - 0.5, dir) + 0.5;
753  t += snoise(uv * 3.0 + time * 0.15) * 0.15;
754  t = clamp(t, 0.0, 1.0);
755
756  vec3 baseColor = mixColors(u_colors, u_colorPositions, u_colorCount, t);
757  float lum = sg_luminance(baseColor);
758
759  // Image luminance drives dot size
760  float imgLum = imageLuminance(uv, aspect);
761  if (imgLum >= 0.0) {
762    lum = mix(lum, imgLum, u_imageIntensity);
763  }
764
765  // Halftone grid
766  float rotAngle = radians(u_angle);
767  mat2 rot = mat2(cos(rotAngle), -sin(rotAngle), sin(rotAngle), cos(rotAngle));
768
769  vec2 pixelCoord = gl_FragCoord.xy;
770  vec2 rotated = rot * pixelCoord;
771  vec2 cell = floor(rotated / u_spacing);
772  vec2 cellCenter = (cell + 0.5) * u_spacing;
773  vec2 localPos = rotated - cellCenter;
774
775  // Dot size based on luminance
776  float dotRadius = u_dotSize * (1.0 - lum * u_contrast);
777  dotRadius = max(dotRadius, 0.2);
778
779  float dist = halftoneShape(localPos, u_shape);
780  // Analytic AA (fwidth) — crisp dot rims at any zoom instead of a fixed 1px ramp.
781  float dot = aaStep(dotRadius, dist);
782
783  // Color the dots
784  vec3 color = baseColor * dot;
785
786  // Image luminance maps to gradient position for dot color
787  if (imgLum >= 0.0) {
788    float imgT = mix(t, imgLum, u_imageIntensity);
789    vec3 imgColor = mixColors(u_colors, u_colorPositions, u_colorCount, imgT);
790    color = imgColor * dot;
791  }
792
793  // Grain
794  color += grain(uv, time, u_grain);
795
796  // Self-blend
797  if (u_blendMode > 0) {
798    float bRotation = u_blendAnimRotation > 0 ? animatedBlendRotation(u_blendRotation, time, u_blendAnimSpeed) : u_blendRotation;
799    float bOffset = u_blendAnimOffset > 0 ? animatedBlendOffset(u_blendOffset, time, u_blendAnimSpeed) : u_blendOffset;
800    float bIntensity = u_blendAnimIntensity > 0 ? animatedBlendIntensity(u_blendIntensity, time, u_blendAnimSpeed) : u_blendIntensity;
801    bIntensity = noiseModulatedIntensity(bIntensity, uv, time, u_blendNoiseMask, u_blendNoiseScale);
802    bIntensity = applyBlendFalloff(bIntensity, t, u_blendFalloff);
803    float tB = blendLayerT(uv, t, u_resolution, u_direction, bOffset, bRotation, u_blendScale);
804    vec3 blendCol = mixColors(u_colors, u_colorPositions, u_colorCount, tB);
805    color = applyBlendMode(u_blendMode, color, blendCol, bIntensity);
806    if (u_blendDualLayer > 0 && u_blendMode2 > 0) {
807      float tB2 = blendLayerT(uv, t, u_resolution, u_direction, u_blendOffset2, u_blendRotation2, u_blendScale2);
808      vec3 blendCol2 = mixColors(u_colors, u_colorPositions, u_colorCount, tB2);
809      color = applyBlendMode(u_blendMode2, color, blendCol2, u_blendIntensity2);
810    }
811  }
812
813  fragColor = outputColor(color, gl_FragCoord.xy, 1.0);
814}
815`,r=`#version 300 es
816precision highp float;
817
818in vec2 vUv;
819out vec4 fragColor;
820
821uniform float u_time;
822uniform float u_speed;
823uniform vec3 u_colors[12];
824uniform float u_colorPositions[12];
825uniform int u_colorCount;
826uniform float u_direction;
827uniform float u_grain;
828uniform vec2 u_resolution;
829
830// Blend uniforms
831uniform int u_blendMode;
832uniform float u_blendIntensity;
833uniform float u_blendOffset;
834uniform float u_blendRotation;
835uniform float u_blendScale;
836uniform int u_blendAnimRotation;
837uniform int u_blendAnimIntensity;
838uniform int u_blendAnimOffset;
839uniform float u_blendAnimSpeed;
840uniform float u_blendNoiseMask;
841uniform float u_blendNoiseScale;
842uniform float u_blendFalloff;
843uniform int u_blendDualLayer;
844uniform int u_blendMode2;
845uniform float u_blendIntensity2;
846uniform float u_blendOffset2;
847uniform float u_blendRotation2;
848uniform float u_blendScale2;
849
850uniform float u_fontSize;
851uniform float u_density;
852uniform int u_invert;
853uniform int u_overlay;
854uniform int u_characterSet;
855
856__COMMON__
857
858// Procedural character patterns — 5x5 grid encoded as floats
859// Each character is a 5x5 bitmap packed into bits
860float getCharPixel(int charIndex, vec2 pos) {
861  // Characters ordered by density: " .:-=+*#%@"
862  // Standard set (10 levels)
863  int patterns[10] = int[10](
864    0,            // space
865    0x4000,       // .
866    0x4010040,    // :
867    0xA8000,      // -
868    0xA80A8,      // =
869    0x4A904,      // +
870    0x4AE04,      // *
871    0xEA5AE,      // #
872    0x155D57,     // %
873    0x1F7FBE      // @
874  );
875
876  int ix = int(pos.x);
877  int iy = int(pos.y);
878  if (ix < 0 || ix >= 5 || iy < 0 || iy >= 5) return 0.0;
879
880  int idx = min(charIndex, 9);
881  int bit = iy * 5 + ix;
882  return float((patterns[idx] >> bit) & 1);
883}
884
885void main() {
886  vec2 uv = vUv;
887  float time = u_time * u_speed;
888  float aspect = u_resolution.x / max(u_resolution.y, 1.0);
889  uv = applyMouseWarp(uv, aspect);
890
891  // Base gradient
892  float angle = radians(u_direction);
893  vec2 dir = vec2(cos(angle), sin(angle));
894  float t = dot(uv - 0.5, dir) + 0.5;
895  t += snoise(uv * 3.0 + time * 0.15) * 0.15;
896  t = clamp(t, 0.0, 1.0);
897
898  vec3 baseColor = mixColors(u_colors, u_colorPositions, u_colorCount, t);
899
900  // Cell grid
901  float cellSize = u_fontSize * u_density;
902  vec2 cell = floor(gl_FragCoord.xy / cellSize);
903  vec2 cellUV = fract(gl_FragCoord.xy / cellSize) * 5.0;
904
905  // Sample center of cell for luminance
906  vec2 cellCenter = (cell + 0.5) * cellSize / u_resolution;
907  float cellT = dot(cellCenter - 0.5, dir) + 0.5;
908  cellT += snoise(cellCenter * 3.0 + time * 0.15) * 0.15;
909  cellT = clamp(cellT, 0.0, 1.0);
910  vec3 cellColor = mixColors(u_colors, u_colorPositions, u_colorCount, cellT);
911  float lum = sg_luminance(cellColor);
912
913  // Image drives character selection and color
914  float imgLum = imageLuminance(cellCenter, aspect);
915  if (imgLum >= 0.0) {
916    lum = mix(lum, imgLum, u_imageIntensity);
917  }
918
919  if (u_invert == 1) lum = 1.0 - lum;
920
921  // Map luminance to character index (0-9)
922  int charIdx = int(lum * 9.0);
923
924  // Block set uses fewer levels
925  if (u_characterSet == 1) {
926    charIdx = int(lum * 4.0);
927    charIdx = charIdx * 2; // Skip to denser chars
928  }
929  // Minimal set
930  if (u_characterSet == 2) {
931    charIdx = lum > 0.5 ? 9 : (lum > 0.2 ? 4 : 0);
932  }
933
934  float pixel = getCharPixel(charIdx, cellUV);
935
936  vec3 color;
937  if (u_overlay == 1) {
938    // Overlay mode: ASCII pattern over the gradient
939    color = mix(baseColor * 0.3, baseColor, pixel);
940  } else {
941    // Full render mode: pure ASCII look
942    vec3 charColor = cellColor;
943    if (imgLum >= 0.0) {
944      charColor = mixColors(u_colors, u_colorPositions, u_colorCount, imgLum);
945    }
946    color = charColor * pixel;
947  }
948
949  // Grain
950  color += grain(uv, time, u_grain);
951
952  // Self-blend
953  if (u_blendMode > 0) {
954    float bRotation = u_blendAnimRotation > 0 ? animatedBlendRotation(u_blendRotation, time, u_blendAnimSpeed) : u_blendRotation;
955    float bOffset = u_blendAnimOffset > 0 ? animatedBlendOffset(u_blendOffset, time, u_blendAnimSpeed) : u_blendOffset;
956    float bIntensity = u_blendAnimIntensity > 0 ? animatedBlendIntensity(u_blendIntensity, time, u_blendAnimSpeed) : u_blendIntensity;
957    bIntensity = noiseModulatedIntensity(bIntensity, uv, time, u_blendNoiseMask, u_blendNoiseScale);
958    bIntensity = applyBlendFalloff(bIntensity, t, u_blendFalloff);
959    float tB = blendLayerT(uv, t, u_resolution, u_direction, bOffset, bRotation, u_blendScale);
960    vec3 blendCol = mixColors(u_colors, u_colorPositions, u_colorCount, tB);
961    color = applyBlendMode(u_blendMode, color, blendCol, bIntensity);
962    if (u_blendDualLayer > 0 && u_blendMode2 > 0) {
963      float tB2 = blendLayerT(uv, t, u_resolution, u_direction, u_blendOffset2, u_blendRotation2, u_blendScale2);
964      vec3 blendCol2 = mixColors(u_colors, u_colorPositions, u_colorCount, tB2);
965      color = applyBlendMode(u_blendMode2, color, blendCol2, u_blendIntensity2);
966    }
967  }
968
969  fragColor = outputColor(color, gl_FragCoord.xy, 1.0);
970}
971`,s=`#version 300 es
972precision highp float;
973
974in vec2 vUv;
975out vec4 fragColor;
976
977uniform float u_time;
978uniform float u_speed;
979uniform vec3 u_colors[12];
980uniform float u_colorPositions[12];
981uniform int u_colorCount;
982uniform float u_direction;
983uniform float u_grain;
984uniform vec2 u_resolution;
985
986// Blend uniforms
987uniform int u_blendMode;
988uniform float u_blendIntensity;
989uniform float u_blendOffset;
990uniform float u_blendRotation;
991uniform float u_blendScale;
992uniform int u_blendAnimRotation;
993uniform int u_blendAnimIntensity;
994uniform int u_blendAnimOffset;
995uniform float u_blendAnimSpeed;
996uniform float u_blendNoiseMask;
997uniform float u_blendNoiseScale;
998uniform float u_blendFalloff;
999uniform int u_blendDualLayer;
1000uniform int u_blendMode2;
1001uniform float u_blendIntensity2;
1002uniform float u_blendOffset2;
1003uniform float u_blendRotation2;
1004uniform float u_blendScale2;
1005
1006uniform float u_warpIntensity;
1007uniform float u_smoothness;
1008uniform float u_distortion;
1009uniform float u_swirl;
1010
1011// Control points (up to 8)
1012uniform vec2 u_meshPoints[8];
1013uniform int u_meshPointCount;
1014
1015__COMMON__
1016
1017// Distortion — noise-based UV warp
1018vec2 applyDistortion(vec2 uv, float distortion, float t) {
1019  float n1 = snoise(uv * 3.0 + t * 0.2);
1020  float n2 = snoise(uv * 3.0 + vec2(5.2, 1.3) + t * 0.2);
1021  return uv + vec2(n1, n2) * distortion * 0.15;
1022}
1023
1024// Swirl — radial UV twist
1025vec2 applySwirl(vec2 uv, float swirl) {
1026  vec2 center = vec2(0.5);
1027  vec2 delta = uv - center;
1028  float dist = length(delta);
1029  float angle = swirl * 6.2832 * (1.0 - smoothstep(0.0, 0.5, dist));
1030  float s = sin(angle), c = cos(angle);
1031  return center + vec2(c * delta.x - s * delta.y, s * delta.x + c * delta.y);
1032}
1033
1034void main() {
1035  vec2 uv = vUv;
1036  float time = u_time * u_speed;
1037  float aspect = u_resolution.x / max(u_resolution.y, 1.0);
1038  uv = applyMouseWarp(uv, aspect);
1039
1040  // Apply distortion and swirl
1041  if (u_distortion > 0.0) uv = applyDistortion(uv, u_distortion, time);
1042  if (u_swirl > 0.0) uv = applySwirl(uv, u_swirl);
1043
1044  // Apply warp
1045  vec2 p = uv;
1046  float warp = snoise(p * 2.5 + time * 0.2);
1047  float warp2 = snoise(p * 4.0 - time * 0.15);
1048  p += vec2(warp, warp2) * u_warpIntensity * 0.15;
1049
1050  // Inverse Distance Weighting (IDW) interpolation
1051  float smoothExp = 2.0 + u_smoothness * 4.0; // Higher = smoother blending
1052  vec3 colorSum = vec3(0.0);
1053  float weightSum = 0.0;
1054
1055  int count = min(u_meshPointCount, u_colorCount);
1056  count = min(count, 8);
1057
1058  for (int i = 0; i < 8; i++) {
1059    if (i >= count) break;
1060
1061    vec2 point = u_meshPoints[i];
1062    vec2 diff = p - point;
1063    diff.x *= aspect;
1064    float dist = length(diff);
1065
1066    // Avoid division by zero
1067    float weight = 1.0 / (pow(max(dist, 0.001), smoothExp));
1068
1069    vec3 lin = srgbToLinear(u_colors[i]);
1070    vec3 blendColor = linearToOklab(lin);
1071    colorSum += blendColor * weight;
1072    weightSum += weight;
1073  }
1074
1075  vec3 blended = colorSum / max(weightSum, 0.001);
1076  vec3 color = oklabToLinear(blended);
1077
1078  // Image-driven gradient map: luminance picks color from gradient palette
1079  float imgLum = imageLuminance(uv, aspect);
1080  if (imgLum >= 0.0) {
1081    vec3 imgColor = mixColors(u_colors, u_colorPositions, u_colorCount, imgLum);
1082    color = mix(color, imgColor, u_imageIntensity);
1083  }
1084
1085  // Grain
1086  color += grain(uv, time, u_grain);
1087
1088  // Self-blend
1089  if (u_blendMode > 0) {
1090    float bRotation = u_blendAnimRotation > 0 ? animatedBlendRotation(u_blendRotation, time, u_blendAnimSpeed) : u_blendRotation;
1091    float bOffset = u_blendAnimOffset > 0 ? animatedBlendOffset(u_blendOffset, time, u_blendAnimSpeed) : u_blendOffset;
1092    float bIntensity = u_blendAnimIntensity > 0 ? animatedBlendIntensity(u_blendIntensity, time, u_blendAnimSpeed) : u_blendIntensity;
1093    bIntensity = noiseModulatedIntensity(bIntensity, uv, time, u_blendNoiseMask, u_blendNoiseScale);
1094    bIntensity = applyBlendFalloff(bIntensity, uv.x, u_blendFalloff);
1095    float tB = blendLayerT(uv, 0.5, u_resolution, u_direction, bOffset, bRotation, u_blendScale);
1096    vec3 blendCol = mixColors(u_colors, u_colorPositions, u_colorCount, tB);
1097    color = applyBlendMode(u_blendMode, color, blendCol, bIntensity);
1098    if (u_blendDualLayer > 0 && u_blendMode2 > 0) {
1099      float tB2 = blendLayerT(uv, 0.5, u_resolution, u_direction, u_blendOffset2, u_blendRotation2, u_blendScale2);
1100      vec3 blendCol2 = mixColors(u_colors, u_colorPositions, u_colorCount, tB2);
1101      color = applyBlendMode(u_blendMode2, color, blendCol2, u_blendIntensity2);
1102    }
1103  }
1104
1105  fragColor = outputColor(color, gl_FragCoord.xy, 1.0);
1106}
1107`,u=`#version 300 es
1108precision highp float;
1109
1110in vec2 vUv;
1111out vec4 fragColor;
1112
1113uniform float u_time;
1114uniform float u_speed;
1115uniform vec3 u_colors[12];
1116uniform float u_colorPositions[12];
1117uniform int u_colorCount;
1118uniform float u_direction;
1119uniform float u_grain;
1120uniform vec2 u_resolution;
1121
1122// Blend uniforms
1123uniform int u_blendMode;
1124uniform float u_blendIntensity;
1125uniform float u_blendOffset;
1126uniform float u_blendRotation;
1127uniform float u_blendScale;
1128uniform int u_blendAnimRotation;
1129uniform int u_blendAnimIntensity;
1130uniform int u_blendAnimOffset;
1131uniform float u_blendAnimSpeed;
1132uniform float u_blendNoiseMask;
1133uniform float u_blendNoiseScale;
1134uniform float u_blendFalloff;
1135uniform int u_blendDualLayer;
1136uniform int u_blendMode2;
1137uniform float u_blendIntensity2;
1138uniform float u_blendOffset2;
1139uniform float u_blendRotation2;
1140uniform float u_blendScale2;
1141
1142uniform float u_nebulaScale;
1143uniform float u_nebulaIntensity;
1144uniform float u_starDensity;
1145uniform float u_starBrightness;
1146uniform float u_dustAmount;
1147uniform float u_warpStrength;
1148uniform float u_warpCenterX;
1149uniform float u_warpCenterY;
1150uniform float u_glowIntensity;
1151uniform int u_cosmicStyle;
1152
1153__COMMON__
1154
1155float hash21(vec2 p) {
1156  p = fract(p * vec2(123.34, 456.21));
1157  p += dot(p, p + 45.32);
1158  return fract(p.x * p.y);
1159}
1160
1161mat2 rot2(float a) {
1162  float s = sin(a);
1163  float c = cos(a);
1164  return mat2(c, -s, s, c);
1165}
1166
1167vec3 starField(
1168  vec2 uv,
1169  float time,
1170  float scale,
1171  float density,
1172  float brightness,
1173  float seed,
1174  float sparkle
1175) {
1176  density = clamp(density, 0.0, 0.99);
1177  float threshold = 1.0 - density;
1178  vec2 scaled = uv * scale;
1179  vec2 baseCell = floor(scaled);
1180  vec2 localUv = fract(scaled);
1181  vec3 acc = vec3(0.0);
1182
1183  for (int x = -1; x <= 1; x++) {
1184    for (int y = -1; y <= 1; y++) {
1185      vec2 cell = baseCell + vec2(float(x), float(y));
1186      float h = hash21(cell + seed);
1187      if (h < threshold) continue;
1188
1189      vec2 jitter = vec2(
1190        hash21(cell + 17.13 + seed),
1191        hash21(cell + 39.71 + seed)
1192      );
1193      vec2 delta = (vec2(float(x), float(y)) + jitter) - localUv;
1194      float dist = length(delta);
1195
1196      float sizeRand = hash21(cell + 81.91 + seed);
1197      float size = mix(0.011, 0.0026, pow(sizeRand, 1.4));
1198      float core = exp(-pow(dist / max(size, 0.0008), 2.6));
1199      float halo = exp(-pow(dist / max(size * 5.2, 0.0015), 1.25)) * 0.38;
1200      float spike = exp(-abs(delta.x * delta.y) / (size * size * 3.4 + 0.0002)) * sparkle * 0.12;
1201
1202      float twinkleSpeed = 1.2 + hash21(cell + 5.41 + seed) * 4.6;
1203      float twinkle = 0.72 + 0.28 * sin(time * twinkleSpeed + h * 6.2831853);
1204
1205      float temp = hash21(cell + 23.58 + seed);
1206      vec3 coolStar = vec3(0.62, 0.76, 1.0);
1207      vec3 warmStar = vec3(1.0, 0.84, 0.62);
1208      vec3 tint = mix(coolStar, warmStar, temp);
1209
1210      float intensity = (core + halo + spike) * brightness * twinkle;
1211      acc += tint * intensity;
1212    }
1213  }
1214
1215  return acc;
1216}
1217
1218void main() {
1219  vec2 uv = vUv;
1220  float time = u_time * u_speed;
1221  float aspect = u_resolution.x / max(u_resolution.y, 1.0);
1222  uv = applyMouseWarp(uv, aspect);
1223
1224  vec2 center = vec2(u_warpCenterX, u_warpCenterY);
1225  vec2 p = uv - center;
1226  p.x *= aspect;
1227
1228  float radius = length(p);
1229  float theta = atan(p.y, p.x);
1230
1231  // Style profile
1232  float nebulaGain = 1.0;
1233  float starGain = 1.0;
1234  float dustGain = 1.0;
1235  float glowGain = 1.0;
1236  float bandMix = 0.0;
1237  float spiralMix = 0.0;
1238  float clusterMix = 0.0;
1239  float supernovaMix = 0.0;
1240  float darkLaneBoost = 0.35;
1241  float swirlBoost = 0.35;
1242
1243  if (u_cosmicStyle == 1) {
1244    // Deep space
1245    nebulaGain = 0.48;
1246    starGain = 1.45;
1247    dustGain = 1.2;
1248    glowGain = 0.78;
1249    darkLaneBoost = 0.62;
1250  } else if (u_cosmicStyle == 2) {
1251    // Vortex
1252    nebulaGain = 1.18;
1253    starGain = 0.74;
1254    dustGain = 0.82;
1255    glowGain = 1.2;
1256    spiralMix = 0.9;
1257    swirlBoost = 1.15;
1258  } else if (u_cosmicStyle == 3) {
1259    // Milky Way
1260    nebulaGain = 0.95;
1261    starGain = 1.2;
1262    dustGain = 1.1;
1263    glowGain = 0.95;
1264    bandMix = 1.0;
1265    darkLaneBoost = 0.9;
1266  } else if (u_cosmicStyle == 4) {
1267    // Spiral Galaxy
1268    nebulaGain = 0.9;
1269    starGain = 1.05;
1270    dustGain = 0.9;
1271    glowGain = 1.05;
1272    spiralMix = 1.1;
1273    clusterMix = 0.45;
1274    swirlBoost = 0.85;
1275  } else if (u_cosmicStyle == 5) {
1276    // Emission Nebula
1277    nebulaGain = 1.36;
1278    starGain = 0.82;
1279    dustGain = 0.95;
1280    glowGain = 1.42;
1281    bandMix = 0.2;
1282  } else if (u_cosmicStyle == 6) {
1283    // Dark Nebula
1284    nebulaGain = 0.88;
1285    starGain = 0.95;
1286    dustGain = 1.65;
1287    glowGain = 0.82;
1288    darkLaneBoost = 1.35;
1289    bandMix = 0.35;
1290  } else if (u_cosmicStyle == 7) {
1291    // Star Cluster
1292    nebulaGain = 0.62;
1293    starGain = 1.75;
1294    dustGain = 0.68;
1295    glowGain = 1.1;
1296    clusterMix = 1.0;
1297  } else if (u_cosmicStyle == 8) {
1298    // Supernova
1299    nebulaGain = 1.15;
1300    starGain = 0.86;
1301    dustGain = 0.9;
1302    glowGain = 1.65;
1303    supernovaMix = 1.0;
1304    spiralMix = 0.24;
1305    swirlBoost = 0.66;
1306    clusterMix = 0.28;
1307  }
1308
1309  // Domain warp for volumetric structure.
1310  float swirl = u_warpStrength * (0.14 + swirlBoost) / (radius + 0.3);
1311  vec2 flowUv = rot2(swirl + time * (0.08 + 0.09 * swirlBoost)) * p;
1312  flowUv += vec2(
1313    snoise(p * 1.8 + vec2(time * 0.06, -time * 0.05)),
1314    snoise(p * 1.7 - vec2(time * 0.04, time * 0.03))
1315  ) * u_warpStrength * 0.22;
1316
1317  float nA = fbm(flowUv * (u_nebulaScale * 1.1) + vec2(time * 0.05, -time * 0.04), 1.0, 5, 0.62, 2.0);
1318  float nB = fbm(flowUv * (u_nebulaScale * 2.5) - vec2(time * 0.03, time * 0.05), 1.0, 4, 0.5, 2.12);
1319  float nC = fbm(flowUv * (u_nebulaScale * 5.2) + vec2(-time * 0.02, time * 0.015), 1.0, 3, 0.48, 2.25);
1320
1321  nA = nA * 0.5 + 0.5;
1322  nB = nB * 0.5 + 0.5;
1323  nC = nC * 0.5 + 0.5;
1324
1325  float ridged = 1.0 - abs(nB * 2.0 - 1.0);
1326  float filament = smoothstep(0.24, 0.9, nA * 0.62 + ridged * 0.38);
1327  float dust = smoothstep(0.34, 0.94, nC);
1328
1329  // Pattern masks
1330  vec2 bandUv = rot2(radians(24.0)) * p;
1331  float milkyBand = exp(-pow(bandUv.y * 3.4, 2.0));
1332  float laneNoise = fbm(bandUv * 5.0 + vec2(time * 0.02, -time * 0.015), 1.0, 4, 0.55, 2.05) * 0.5 + 0.5;
1333  milkyBand *= mix(0.55, 1.25, smoothstep(0.28, 0.82, laneNoise));
1334
1335  float spiralPhase = theta * 4.2 + log(radius + 1.15) * 11.0 - time * 0.55;
1336  float spiralArms = pow(max(cos(spiralPhase), 0.0), 2.6);
1337  float galaxyDisk = exp(-radius * 2.9);
1338  float spiralMask = spiralArms * galaxyDisk;
1339
1340  float clusterMask = exp(-pow(radius * 3.8, 2.0));
1341
1342  float shellRadius = 0.24 + 0.03 * sin(time * 0.55);
1343  float supernovaShell = exp(-pow(abs(radius - shellRadius) / 0.065, 2.0));
1344  float supernovaCore = exp(-radius * 7.5);
1345  float supernovaMask = supernovaCore * 0.85 + supernovaShell;
1346
1347  float structure = filament;
1348  structure += milkyBand * bandMix * 0.9;
1349  structure += spiralMask * spiralMix * 1.08;
1350  structure += clusterMask * clusterMix * 0.95;
1351  structure += supernovaMask * supernovaMix * 1.2;
1352  structure = clamp(structure, 0.0, 1.65);
1353
1354  vec2 dir = vec2(cos(radians(u_direction)), sin(radians(u_direction)));
1355  float t = dot(uv - 0.5, dir) + 0.5;
1356  t += (structure - 0.45) * (0.38 * u_nebulaIntensity + 0.14);
1357  t = clamp(t, 0.0, 1.0);
1358
1359  // Image-driven gradient modulation
1360  float imgLum = imageLuminance(uv, aspect);
1361  if (imgLum >= 0.0) {
1362    t = mix(t, imgLum, u_imageIntensity);
1363  }
1364
1365  vec3 base = mixColors(u_colors, u_colorPositions, u_colorCount, t);
1366  vec3 hot = mixColors(u_colors, u_colorPositions, u_colorCount, clamp(t + 0.2, 0.0, 1.0));
1367  vec3 cool = mixColors(u_colors, u_colorPositions, u_colorCount, clamp(t - 0.16, 0.0, 1.0));
1368  vec3 nebulaColor = mix(cool, hot, clamp(structure * 0.95 + milkyBand * 0.2, 0.0, 1.0));
1369  vec3 emissionColor = mix(base, hot, 0.68);
1370
1371  vec3 color = vec3(0.0018, 0.0024, 0.0055);
1372  float bgVig = smoothstep(0.32, 1.1, radius);
1373  color *= 1.0 - bgVig * 0.58;
1374
1375  // Volumetric gas + style structures.
1376  float nebulaStrength = (0.2 + u_nebulaIntensity * 1.45) * nebulaGain;
1377  color += nebulaColor * filament * nebulaStrength;
1378  color += nebulaColor * dust * (0.24 + u_dustAmount * 0.54) * dustGain;
1379  color += emissionColor * milkyBand * bandMix * (0.14 + u_nebulaIntensity * 0.45);
1380  color += hot * spiralMask * spiralMix * (0.2 + u_glowIntensity * 0.55);
1381  color += mix(hot, vec3(1.0, 0.9, 0.7), 0.55) * supernovaMask * supernovaMix * (0.25 + u_glowIntensity * 1.05);
1382
1383  // Dark dust lanes for depth.
1384  float darkLane = smoothstep(0.45, 0.9, dust) * smoothstep(0.15, 0.88, filament);
1385  color *= 1.0 - darkLane * darkLaneBoost * (0.22 + u_dustAmount * 0.36);
1386
1387  // Pulsing emissive glow.
1388  float breathe = 0.7 + 0.3 * sin(time * 0.42 + radius * 3.8);
1389  color += nebulaColor * pow(clamp(structure, 0.0, 1.5), 1.3) * (u_glowIntensity * 0.72 * glowGain * breathe);
1390
1391  // Style-specific cores.
1392  if (u_cosmicStyle == 2) {
1393    float core = exp(-radius * 8.5);
1394    color += vec3(1.0, 0.88, 0.72) * core * (0.3 + u_glowIntensity * 1.25);
1395  } else if (u_cosmicStyle == 7) {
1396    float core = exp(-radius * 6.4);
1397    color += vec3(1.0, 0.95, 0.82) * core * (0.18 + u_starBrightness * 0.55);
1398  }
1399
1400  // Multi-layer star fields (reduced "dotty" artifacts, richer depth).
1401  vec2 starWarp = uv + vec2(
1402    snoise(uv * 6.4 + vec2(time * 0.05, -time * 0.04)),
1403    snoise(uv * 6.1 - vec2(time * 0.03, time * 0.02))
1404  ) * (0.008 + u_warpStrength * 0.01);
1405
1406  float denseStars = mix(0.012, 0.11, u_starDensity);
1407  float sparseStars = denseStars * 0.42;
1408  float giantStars = denseStars * 0.12;
1409
1410  vec3 stars = starField(starWarp, time, mix(38.0, 165.0, u_starDensity), denseStars, u_starBrightness * 0.95, 11.7, 0.55);
1411  stars += starField(starWarp + vec2(0.12, -0.07), time * 1.23, mix(16.0, 68.0, u_starDensity), sparseStars, u_starBrightness * 0.72, 57.3, 1.0);
1412  stars += starField(starWarp + vec2(-0.19, 0.09), time * 0.86, mix(8.0, 26.0, u_starDensity), giantStars, u_starBrightness * 0.5, 101.4, 1.35);
1413
1414  float starMask = 1.0 + milkyBand * bandMix * 1.25 + clusterMask * clusterMix * 1.65;
1415  color += stars * starMask * starGain;
1416
1417  float vignette = smoothstep(0.36, 1.0, radius);
1418  float deepSpaceBoost = (u_cosmicStyle == 1 || u_cosmicStyle == 6) ? 0.12 : 0.0;
1419  color *= 1.0 - vignette * (0.6 + deepSpaceBoost);
1420
1421  color += grain(uv, time, u_grain * 0.72);
1422
1423  // Self-blend
1424  if (u_blendMode > 0) {
1425    float bRotation = u_blendAnimRotation > 0 ? animatedBlendRotation(u_blendRotation, time, u_blendAnimSpeed) : u_blendRotation;
1426    float bOffset = u_blendAnimOffset > 0 ? animatedBlendOffset(u_blendOffset, time, u_blendAnimSpeed) : u_blendOffset;
1427    float bIntensity = u_blendAnimIntensity > 0 ? animatedBlendIntensity(u_blendIntensity, time, u_blendAnimSpeed) : u_blendIntensity;
1428    bIntensity = noiseModulatedIntensity(bIntensity, uv, time, u_blendNoiseMask, u_blendNoiseScale);
1429    bIntensity = applyBlendFalloff(bIntensity, uv.x, u_blendFalloff);
1430    float tB = blendLayerT(uv, 0.5, u_resolution, u_direction, bOffset, bRotation, u_blendScale);
1431    vec3 blendCol = mixColors(u_colors, u_colorPositions, u_colorCount, tB);
1432    color = applyBlendMode(u_blendMode, color, blendCol, bIntensity);
1433    if (u_blendDualLayer > 0 && u_blendMode2 > 0) {
1434      float tB2 = blendLayerT(uv, 0.5, u_resolution, u_direction, u_blendOffset2, u_blendRotation2, u_blendScale2);
1435      vec3 blendCol2 = mixColors(u_colors, u_colorPositions, u_colorCount, tB2);
1436      color = applyBlendMode(u_blendMode2, color, blendCol2, u_blendIntensity2);
1437    }
1438  }
1439
1440  fragColor = outputColor(color, gl_FragCoord.xy, 1.25);
1441}
1442`,f=`#version 300 es
1443precision highp float;
1444
1445in vec2 vUv;
1446out vec4 fragColor;
1447
1448uniform float u_time;
1449uniform float u_speed;
1450uniform vec3 u_colors[12];
1451uniform float u_colorPositions[12];
1452uniform int u_colorCount;
1453uniform float u_direction;
1454uniform float u_grain;
1455uniform vec2 u_resolution;
1456
1457// Blend uniforms
1458uniform int u_blendMode;
1459uniform float u_blendIntensity;
1460uniform float u_blendOffset;
1461uniform float u_blendRotation;
1462uniform float u_blendScale;
1463uniform int u_blendAnimRotation;
1464uniform int u_blendAnimIntensity;
1465uniform int u_blendAnimOffset;
1466uniform float u_blendAnimSpeed;
1467uniform float u_blendNoiseMask;
1468uniform float u_blendNoiseScale;
1469uniform float u_blendFalloff;
1470uniform int u_blendDualLayer;
1471uniform int u_blendMode2;
1472uniform float u_blendIntensity2;
1473uniform float u_blendOffset2;
1474uniform float u_blendRotation2;
1475uniform float u_blendScale2;
1476
1477uniform float u_scale;
1478uniform float u_elevation;
1479uniform float u_ridge;
1480uniform float u_terracing;
1481uniform float u_lightAzimuth;
1482uniform float u_lightElevation;
1483uniform float u_ambient;
1484uniform float u_specular;
1485uniform float u_fog;
1486uniform float u_parallax;
1487
1488__COMMON__
1489
1490float terrainHeight(vec2 p, float time) {
1491  vec2 d = p * max(0.2, u_scale);
1492  float base = fbm(d + vec2(time * 0.05, -time * 0.04), 1.0, 5, 0.62, 2.02);
1493  float detail = fbm(d * 2.1 - vec2(time * 0.03, time * 0.02), 1.0, 4, 0.42, 2.18);
1494  float ridged = 1.0 - abs(detail);
1495  float h = base * 0.56 + ridged * (0.28 + u_ridge * 0.72);
1496  h = h * 0.5 + 0.5;
1497
1498  float terraces = mix(3.0, 18.0, u_terracing);
1499  float stepped = floor(h * terraces) / max(terraces, 1.0);
1500  h = mix(h, stepped, u_terracing * 0.85);
1501  return clamp(h, 0.0, 1.0);
1502}
1503
1504mat2 rot2(float a) {
1505  float s = sin(a);
1506  float c = cos(a);
1507  return mat2(c, -s, s, c);
1508}
1509
1510void main() {
1511  vec2 uv = vUv;
1512  float time = u_time * u_speed;
1513  float aspect = u_resolution.x / max(u_resolution.y, 1.0);
1514  uv = applyMouseWarp(uv, aspect);
1515  vec2 p = uv - 0.5;
1516  p.x *= aspect;
1517
1518  vec2 depthUv = p;
1519  float depthPerspective = mix(0.7, 2.2, u_parallax);
1520  depthUv.x /= max(0.25, 1.0 + (uv.y - 0.2) * depthPerspective);
1521  depthUv += vec2(0.0, u_parallax * 0.12);
1522  depthUv = rot2(radians(u_direction * 0.18)) * depthUv;
1523
1524  float h = terrainHeight(depthUv, time) * (0.38 + u_elevation * 1.22);
1525
1526  float eps = 1.6 / max(min(u_resolution.x, u_resolution.y), 1.0);
1527  float hx = terrainHeight(depthUv + vec2(eps, 0.0), time);
1528  float hy = terrainHeight(depthUv + vec2(0.0, eps), time);
1529  vec3 n = normalize(vec3((h - hx) * 14.0, (h - hy) * 14.0, 1.0));
1530
1531  float az = radians(u_lightAzimuth);
1532  float el = radians(clamp(u_lightElevation, 5.0, 85.0));
1533  vec3 l = normalize(vec3(cos(el) * cos(az), sin(el), cos(el) * sin(az)));
1534  vec3 v = normalize(vec3(0.0, 0.0, 1.0));
1535  vec3 hVec = normalize(l + v);
1536
1537  float diff = max(dot(n, l), 0.0);
1538  float spec = pow(max(dot(n, hVec), 0.0), mix(10.0, 120.0, u_specular)) * u_specular;
1539  float rim = pow(1.0 - max(dot(n, v), 0.0), 2.5) * (0.12 + u_specular * 0.25);
1540
1541  vec2 dir = vec2(cos(radians(u_direction)), sin(radians(u_direction)));
1542  float t = dot(uv - 0.5, dir) + 0.5;
1543  t += (h - 0.5) * (0.35 + u_elevation * 0.45);
1544  t += snoise(depthUv * 2.4 + vec2(time * 0.02, -time * 0.03)) * 0.06;
1545  t = clamp(t, 0.0, 1.0);
1546
1547  // Image-driven gradient integration
1548  float imgLum = imageLuminance(uv, aspect);
1549  if (imgLum >= 0.0) {
1550    t = mix(t, imgLum, u_imageIntensity);
1551  }
1552
1553  vec3 low = mixColors(u_colors, u_colorPositions, u_colorCount, clamp(t - 0.22, 0.0, 1.0));
1554  vec3 mid = mixColors(u_colors, u_colorPositions, u_colorCount, t);
1555  vec3 high = mixColors(u_colors, u_colorPositions, u_colorCount, clamp(t + 0.18, 0.0, 1.0));
1556
1557  vec3 color = mix(low, mid, smoothstep(0.12, 0.68, h));
1558  color = mix(color, high, smoothstep(0.48, 1.05, h));
1559
1560  float lightMix = mix(0.25, 1.28, diff);
1561  color *= mix(u_ambient * 0.55, u_ambient + lightMix * (1.15 - u_ambient), 0.88);
1562  color += high * spec * 0.85;
1563  color += mid * rim;
1564
1565  float fogDepth = smoothstep(0.12, 1.06, (uv.y * 0.85 + h * 0.35));
1566  vec3 fogColor = mixColors(u_colors, u_colorPositions, u_colorCount, 0.12) * 0.35;
1567  color = mix(color, fogColor, fogDepth * u_fog * 0.8);
1568
1569  float vignette = smoothstep(0.36, 0.96, length((uv - 0.5) * vec2(1.1, 1.2)));
1570  color *= 1.0 - vignette * 0.55;
1571
1572  color += grain(uv, time, u_grain * 0.75);
1573
1574  // Self-blend
1575  if (u_blendMode > 0) {
1576    float bRotation = u_blendAnimRotation > 0 ? animatedBlendRotation(u_blendRotation, time, u_blendAnimSpeed) : u_blendRotation;
1577    float bOffset = u_blendAnimOffset > 0 ? animatedBlendOffset(u_blendOffset, time, u_blendAnimSpeed) : u_blendOffset;
1578    float bIntensity = u_blendAnimIntensity > 0 ? animatedBlendIntensity(u_blendIntensity, time, u_blendAnimSpeed) : u_blendIntensity;
1579    bIntensity = noiseModulatedIntensity(bIntensity, uv, time, u_blendNoiseMask, u_blendNoiseScale);
1580    bIntensity = applyBlendFalloff(bIntensity, uv.x, u_blendFalloff);
1581    float tB = blendLayerT(uv, 0.5, u_resolution, u_direction, bOffset, bRotation, u_blendScale);
1582    vec3 blendCol = mixColors(u_colors, u_colorPositions, u_colorCount, tB);
1583    color = applyBlendMode(u_blendMode, color, blendCol, bIntensity);
1584    if (u_blendDualLayer > 0 && u_blendMode2 > 0) {
1585      float tB2 = blendLayerT(uv, 0.5, u_resolution, u_direction, u_blendOffset2, u_blendRotation2, u_blendScale2);
1586      vec3 blendCol2 = mixColors(u_colors, u_colorPositions, u_colorCount, tB2);
1587      color = applyBlendMode(u_blendMode2, color, blendCol2, u_blendIntensity2);
1588    }
1589  }
1590
1591  fragColor = outputColor(color, gl_FragCoord.xy, 1.1);
1592}
1593`,c=`#version 300 es
1594precision highp float;
1595
1596in vec2 vUv;
1597out vec4 fragColor;
1598
1599uniform float u_time;
1600uniform float u_speed;
1601uniform vec3 u_colors[12];
1602uniform float u_colorPositions[12];
1603uniform int u_colorCount;
1604uniform float u_direction;
1605uniform float u_grain;
1606uniform vec2 u_resolution;
1607
1608// Blend uniforms
1609uniform int u_blendMode;
1610uniform float u_blendIntensity;
1611uniform float u_blendOffset;
1612uniform float u_blendRotation;
1613uniform float u_blendScale;
1614uniform int u_blendAnimRotation;
1615uniform int u_blendAnimIntensity;
1616uniform int u_blendAnimOffset;
1617uniform float u_blendAnimSpeed;
1618uniform float u_blendNoiseMask;
1619uniform float u_blendNoiseScale;
1620uniform float u_blendFalloff;
1621uniform int u_blendDualLayer;
1622uniform int u_blendMode2;
1623uniform float u_blendIntensity2;
1624uniform float u_blendOffset2;
1625uniform float u_blendRotation2;
1626uniform float u_blendScale2;
1627
1628uniform float u_cellSize;
1629uniform float u_relief;
1630uniform float u_lineThickness;
1631uniform float u_perspective;
1632uniform float u_edgeSoftness;
1633uniform int u_isoPattern;
1634uniform float u_jitter;
1635uniform float u_glow;
1636
1637__COMMON__
1638
1639mat2 rot2(float a) {
1640  float s = sin(a);
1641  float c = cos(a);
1642  return mat2(c, -s, s, c);
1643}
1644
1645void main() {
1646  vec2 uv = vUv;
1647  float time = u_time * u_speed;
1648  float aspect = u_resolution.x / max(u_resolution.y, 1.0);
1649  uv = applyMouseWarp(uv, aspect);
1650  vec2 p = uv - 0.5;
1651  p.x *= aspect;
1652
1653  vec2 iso = vec2(p.x - p.y, (p.x + p.y) * 0.5);
1654  iso = rot2(radians(u_direction * 0.15)) * iso;
1655
1656  float perspectiveScale = mix(0.7, 1.65, u_perspective);
1657  iso.x *= mix(1.0, 0.68, uv.y) * perspectiveScale;
1658
1659  float jitter = snoise(iso * 2.0 + vec2(time * 0.2, -time * 0.15)) * u_jitter * 0.12;
1660  iso += vec2(jitter, -jitter * 0.55);
1661
1662  float cell = max(8.0, u_cellSize);
1663  vec2 g = iso * cell;
1664  vec2 id = floor(g);
1665  vec2 f = fract(g) - 0.5;
1666
1667  float hash = fract(sin(dot(id, vec2(127.1, 311.7))) * 43758.5453123);
1668  float cellDrift = snoise(id * 0.16 + vec2(time * 0.09, time * 0.06));
1669
1670  float diamondDist = abs(f.x) + abs(f.y);
1671  float edgeWidth = mix(0.02, 0.16, u_edgeSoftness);
1672  float tileMask = 1.0 - smoothstep(0.5 - edgeWidth, 0.5 + edgeWidth, diamondDist);
1673  float hexDist = max(abs(f.y), abs(f.x) * 0.8660254 + abs(f.y) * 0.5);
1674  float hexMask = 1.0 - smoothstep(0.5 - edgeWidth, 0.5 + edgeWidth, hexDist);
1675
1676  float height = (0.3 + hash * 0.7 + cellDrift * 0.14) * u_relief;
1677  float steps = floor((hash + 0.15 + cellDrift * 0.2) * (2.0 + u_relief * 8.0)) / (2.0 + u_relief * 8.0);
1678  float prism = smoothstep(-0.45, 0.45, f.y) * (1.0 - abs(f.x) * 1.2);
1679  float weave = 0.5 + 0.5 * sin((id.x - id.y) * 0.8 + time * 0.7 + hash * 6.2831);
1680  float circuit = 1.0 - smoothstep(
1681    0.03,
1682    0.1,
1683    min(
1684      abs(fract((g.x + g.y) * 0.45) - 0.5),
1685      abs(fract((g.x - g.y) * 0.45) - 0.5)
1686    )
1687  );
1688  float skyline = floor((hash + cellDrift * 0.18) * (3.0 + u_relief * 10.0)) / (3.0 + u_relief * 10.0);
1689
1690  float cubeTop = smoothstep(0.0, 0.5, 0.5 - diamondDist);
1691  float topFace = cubeTop;
1692  float leftFace = (1.0 - smoothstep(-0.02, 0.02, f.x + f.y)) * (1.0 - topFace);
1693  float rightFace = (1.0 - smoothstep(-0.02, 0.02, f.x - f.y)) * (1.0 - topFace);
1694
1695  float shape = height;
1696  if (u_isoPattern == 1) {
1697    shape = mix(height, steps, 0.65);
1698  } else if (u_isoPattern == 2) {
1699    shape = mix(height, steps * 1.12, 0.9);
1700  } else if (u_isoPattern == 3) {
1701    shape = mix(height, prism * 1.05, 0.7);
1702  } else if (u_isoPattern == 4) {
1703    shape = mix(height, hexMask * (0.3 + steps * 0.9), 0.82);
1704  } else if (u_isoPattern == 5) {
1705    shape = mix(height, weave * 0.9 + prism * 0.25, 0.76);
1706  } else if (u_isoPattern == 6) {
1707    shape = mix(height, circuit * 1.04 + steps * 0.32, 0.84);
1708  } else if (u_isoPattern == 7) {
1709    shape = mix(height, skyline * (0.55 + u_relief * 0.75), 0.88);
1710  }
1711
1712  vec2 dir = vec2(cos(radians(u_direction)), sin(radians(u_direction)));
1713  float t = dot(uv - 0.5, dir) + 0.5;
1714  t += (shape - 0.5) * 0.35;
1715  t += cellDrift * 0.06;
1716  t = clamp(t, 0.0, 1.0);
1717
1718  // Image-driven gradient integration
1719  float imgLum = imageLuminance(uv, aspect);
1720  if (imgLum >= 0.0) {
1721    t = mix(t, imgLum, u_imageIntensity);
1722  }
1723
1724  vec3 base = mixColors(u_colors, u_colorPositions, u_colorCount, t);
1725  vec3 bright = mixColors(u_colors, u_colorPositions, u_colorCount, clamp(t + 0.18, 0.0, 1.0));
1726  vec3 dark = mixColors(u_colors, u_colorPositions, u_colorCount, clamp(t - 0.2, 0.0, 1.0));
1727
1728  vec3 color = base;
1729
1730  if (u_isoPattern == 1) {
1731    color = topFace * mix(base, bright, 0.34)
1732      + leftFace * mix(dark, base, 0.24)
1733      + rightFace * mix(base, bright, 0.16);
1734  } else if (u_isoPattern == 2) {
1735    float stair = fract((id.x + id.y) * 0.5 + hash);
1736    color = mix(dark, bright, stair);
1737  } else if (u_isoPattern == 3) {
1738    color = mix(dark, bright, clamp(prism + 0.25, 0.0, 1.0));
1739  } else if (u_isoPattern == 4) {
1740    color = mix(dark, bright, clamp(hexMask * 0.7 + steps * 0.2, 0.0, 1.0));
1741  } else if (u_isoPattern == 5) {
1742    color = mix(dark, bright, weave);
1743    color = mix(color, base, prism * 0.25);
1744  } else if (u_isoPattern == 6) {
1745    color = mix(dark, base, 0.62);
1746    color += bright * circuit * (0.25 + u_glow * 0.28);
1747  } else if (u_isoPattern == 7) {
1748    color = mix(dark, bright, skyline);
1749    color = mix(color, base, 0.35 + 0.35 * prism);
1750  }
1751
1752  float gridA = abs(fract(g.x + g.y) - 0.5);
1753  float gridB = abs(fract(g.x - g.y) - 0.5);
1754  float gridC = abs(fract(g.y * 2.0) - 0.5);
1755  float gridDist = min(min(gridA, gridB), gridC);
1756  float lineWidth = mix(0.035, 0.006, u_lineThickness);
1757  float aa = fwidth(gridDist) * 1.5 + 0.002;
1758  float line = 1.0 - smoothstep(lineWidth + aa, lineWidth + aa * 3.0, gridDist);
1759
1760  if (u_isoPattern == 4) {
1761    float hx = abs(fract((g.x + g.y * 0.5) * 0.5) - 0.5);
1762    float hy = abs(fract((g.x - g.y * 0.5) * 0.5) - 0.5);
1763    float hLine = 1.0 - smoothstep(lineWidth + aa, lineWidth + aa * 3.2, min(hx, hy));
1764    line = max(line * 0.6, hLine);
1765  } else if (u_isoPattern == 5) {
1766    line = mix(line, weave, 0.35);
1767  } else if (u_isoPattern == 6) {
1768    line = max(line * 0.55, circuit);
1769  } else if (u_isoPattern == 7) {
1770    line = max(line * 0.5, smoothstep(0.45, 0.9, skyline) * 0.75);
1771  }
1772
1773  vec3 lineColor = mix(dark, bright, 0.55);
1774  color = mix(color, lineColor, line * (0.22 + u_lineThickness * 0.65));
1775
1776  float patternMask = (u_isoPattern == 4) ? hexMask : tileMask;
1777  float glow = line * (0.08 + u_glow * 0.42) * patternMask;
1778  color += bright * glow;
1779
1780  float vignette = smoothstep(0.34, 0.96, length((uv - 0.5) * vec2(1.1, 1.18)));
1781  color *= 1.0 - vignette * 0.5;
1782
1783  color = mix(vec3(0.0), color, patternMask + (1.0 - patternMask) * 0.18);
1784  color += grain(uv, time, u_grain * 0.72);
1785
1786  // Self-blend
1787  if (u_blendMode > 0) {
1788    float bRotation = u_blendAnimRotation > 0 ? animatedBlendRotation(u_blendRotation, time, u_blendAnimSpeed) : u_blendRotation;
1789    float bOffset = u_blendAnimOffset > 0 ? animatedBlendOffset(u_blendOffset, time, u_blendAnimSpeed) : u_blendOffset;
1790    float bIntensity = u_blendAnimIntensity > 0 ? animatedBlendIntensity(u_blendIntensity, time, u_blendAnimSpeed) : u_blendIntensity;
1791    bIntensity = noiseModulatedIntensity(bIntensity, uv, time, u_blendNoiseMask, u_blendNoiseScale);
1792    bIntensity = applyBlendFalloff(bIntensity, uv.x, u_blendFalloff);
1793    float tB = blendLayerT(uv, 0.5, u_resolution, u_direction, bOffset, bRotation, u_blendScale);
1794    vec3 blendCol = mixColors(u_colors, u_colorPositions, u_colorCount, tB);
1795    color = applyBlendMode(u_blendMode, color, blendCol, bIntensity);
1796    if (u_blendDualLayer > 0 && u_blendMode2 > 0) {
1797      float tB2 = blendLayerT(uv, 0.5, u_resolution, u_direction, u_blendOffset2, u_blendRotation2, u_blendScale2);
1798      vec3 blendCol2 = mixColors(u_colors, u_colorPositions, u_colorCount, tB2);
1799      color = applyBlendMode(u_blendMode2, color, blendCol2, u_blendIntensity2);
1800    }
1801  }
1802
1803  fragColor = outputColor(color, gl_FragCoord.xy, 1.0);
1804}
1805`,d=`#version 300 es
1806precision highp float;
1807
1808in vec2 vUv;
1809out vec4 fragColor;
1810
1811uniform float u_time;
1812uniform float u_speed;
1813uniform vec3 u_colors[12];
1814uniform float u_colorPositions[12];
1815uniform int u_colorCount;
1816uniform float u_direction;
1817uniform float u_grain;
1818uniform vec2 u_resolution;
1819
1820// Blend uniforms
1821uniform int u_blendMode;
1822uniform float u_blendIntensity;
1823uniform float u_blendOffset;
1824uniform float u_blendRotation;
1825uniform float u_blendScale;
1826uniform int u_blendAnimRotation;
1827uniform int u_blendAnimIntensity;
1828uniform int u_blendAnimOffset;
1829uniform float u_blendAnimSpeed;
1830uniform float u_blendNoiseMask;
1831uniform float u_blendNoiseScale;
1832uniform float u_blendFalloff;
1833uniform int u_blendDualLayer;
1834uniform int u_blendMode2;
1835uniform float u_blendIntensity2;
1836uniform float u_blendOffset2;
1837uniform float u_blendRotation2;
1838uniform float u_blendScale2;
1839
1840uniform float u_terrainScale;
1841uniform float u_relief;
1842uniform int u_hudPattern;
1843uniform float u_contourDensity;
1844uniform float u_gridStrength;
1845uniform float u_wireframe;
1846uniform float u_scanlineStrength;
1847uniform float u_fogDepth;
1848uniform float u_tilt;
1849uniform float u_horizon;
1850uniform float u_markerGlow;
1851uniform int u_terrainStyle;
1852uniform int u_qualityTier;
1853
1854__COMMON__
1855
1856// ─── Hash helpers ────────────────────────────────────────────
1857float hash21(vec2 p) {
1858  p = fract(p * vec2(123.34, 456.21));
1859  p += dot(p, p + 45.32);
1860  return fract(p.x * p.y);
1861}
1862
1863// ─── Terrain height functions per style ──────────────────────
1864
1865// Style 0: Mountains — ridged multi-fractal with domain warping
1866float terrainMountains(vec2 p, float time, float freq) {
1867  vec2 warp = vec2(
1868    snoise(p * freq * 0.4 + vec2(time * 0.015, 0.0)),
1869    snoise(p * freq * 0.4 + vec2(0.0, time * 0.012))
1870  );
1871  vec2 wp = p + warp * 0.35;
1872  float base = fbm(wp * freq + vec2(time * 0.02, -time * 0.015), 1.0, 5, 0.58, 2.05);
1873  float ridgeVal = 0.0;
1874  float ridgeFreq = freq * 1.8;
1875  float ridgeAmp = 0.5;
1876  vec2 rp = wp + vec2(time * 0.008, time * 0.006);
1877  for (int i = 0; i < 4; i++) {
1878    float n = 1.0 - abs(snoise(rp * ridgeFreq));
1879    n = n * n;
1880    ridgeVal += n * ridgeAmp;
1881    ridgeFreq *= 2.1;
1882    ridgeAmp *= 0.48;
1883    rp *= 2.1;
1884    rp += vec2(1.7, 1.3);
1885  }
1886  return base * 0.5 + ridgeVal * 0.5;
1887}
1888
1889// Style 1: Dunes — smooth rolling shapes with wind-sculpted asymmetry
1890float terrainDunes(vec2 p, float time, float freq) {
1891  float windAngle = time * 0.005;
1892  vec2 windDir = vec2(cos(windAngle), sin(windAngle));
1893  vec2 wp = p + windDir * time * 0.04;
1894  float dune1 = sin(dot(wp, vec2(0.8, 0.6)) * freq * 1.5 + snoise(wp * freq * 0.3) * 2.0);
1895  float dune2 = sin(dot(wp, vec2(0.3, 0.95)) * freq * 2.2 + snoise(wp * freq * 0.5 + 5.0) * 1.5);
1896  float ripple = snoise(wp * freq * 4.0 + vec2(time * 0.03)) * 0.15;
1897  float h = dune1 * 0.4 + dune2 * 0.3 + ripple;
1898  // Asymmetric windward shaping — sharpen one side
1899  h = h * 0.5 + 0.5;
1900  h = pow(h, 0.7 + 0.6 * smoothstep(-0.2, 0.3, dot(normalize(vec2(dune1, dune2)), windDir)));
1901  return h;
1902}
1903
1904// Style 2: Canyons — deep cuts with plateaus and sharp valleys
1905float terrainCanyons(vec2 p, float time, float freq) {
1906  vec2 wp = p + vec2(time * 0.015, -time * 0.01);
1907  float base = fbm(wp * freq * 0.6, 1.0, 4, 0.55, 2.1);
1908  // Plateau clamping — flatten the tops, keep deep cuts
1909  float plateau = smoothstep(0.3, 0.6, base);
1910  // Sharp canyon cracks
1911  float crack = abs(snoise(wp * freq * 1.2 + vec2(3.7)));
1912  crack = smoothstep(0.0, 0.15, crack); // sharp edge
1913  float detail = snoise(wp * freq * 3.0) * 0.1;
1914  float h = mix(base * 0.3, plateau * 0.8, 0.6) * crack + detail;
1915  // Terracing effect
1916  float terraced = floor(h * 6.0) / 6.0;
1917  h = mix(h, terraced, 0.4);
1918  return h;
1919}
1920
1921// Style 3: Volcanic — caldera craters with sharp rims and lava flows
1922float terrainVolcanic(vec2 p, float time, float freq) {
1923  vec2 wp = p + vec2(time * 0.01, time * 0.008);
1924  float base = fbm(wp * freq * 0.5, 1.0, 4, 0.6, 2.0);
1925  // Create crater shapes using distance from noise-positioned centers
1926  vec2 craterCenter = floor(wp * freq * 0.3) + 0.5;
1927  float craterR = length(fract(wp * freq * 0.3) - 0.5);
1928  float rim = smoothstep(0.25, 0.32, craterR) * smoothstep(0.45, 0.35, craterR);
1929  float caldera = smoothstep(0.22, 0.0, craterR) * 0.6;
1930  // Sharp volcanic ridges
1931  float ridge = 1.0 - abs(snoise(wp * freq * 1.5));
1932  ridge = pow(ridge, 3.0) * 0.4;
1933  // Lava glow in low areas
1934  float h = base * 0.3 + rim * 0.7 - caldera + ridge;
1935  // Add heat shimmer displacement
1936  h += sin(p.x * freq * 8.0 + time * 2.0) * sin(p.y * freq * 8.0 + time * 1.7) * 0.02;
1937  return clamp(h, 0.0, 1.5);
1938}
1939
1940// Style 4: Ocean — animated wave surface with swells and chop
1941float terrainOcean(vec2 p, float time, float freq) {
1942  vec2 wp = p;
1943  // Large swells
1944  float swell1 = sin(dot(wp, vec2(0.7, 0.3)) * freq * 1.2 + time * 0.8) * 0.35;
1945  float swell2 = sin(dot(wp, vec2(-0.4, 0.9)) * freq * 0.9 + time * 0.6) * 0.25;
1946  float swell3 = sin(dot(wp, vec2(0.9, -0.5)) * freq * 1.6 + time * 1.1) * 0.15;
1947  // Wind chop — higher frequency detail
1948  float chop = snoise(wp * freq * 3.0 + vec2(time * 0.4, time * 0.3)) * 0.12;
1949  float chop2 = snoise(wp * freq * 6.0 + vec2(-time * 0.5, time * 0.6)) * 0.05;
1950  float h = swell1 + swell2 + swell3 + chop + chop2;
1951  h = h * 0.5 + 0.35; // bias up so water surface is visible
1952  return h;
1953}
1954
1955// Style 5: Glacial — smooth ice shelf with crevasse lines
1956float terrainGlacial(vec2 p, float time, float freq) {
1957  vec2 wp = p + vec2(time * 0.005, -time * 0.003);
1958  // Very smooth base — low octave, high persistence for gentle undulation
1959  float base = fbm(wp * freq * 0.4, 1.0, 3, 0.7, 1.8);
1960  base = base * base; // extra smoothing by squaring
1961  // Crevasse lines — sharp cracks in the ice
1962  float crv1 = abs(snoise(wp * freq * 1.0 + vec2(2.3, 4.1)));
1963  float crv2 = abs(snoise(wp * freq * 1.5 + vec2(7.1, 1.3)));
1964  float crevasse = min(crv1, crv2);
1965  crevasse = smoothstep(0.0, 0.08, crevasse); // sharp edges
1966  // Seracs — pressure ridges
1967  float ridge = pow(1.0 - abs(snoise(wp * freq * 0.7)), 4.0) * 0.3;
1968  float h = base * 0.6 + ridge;
1969  h *= crevasse; // cut crevasses into surface
1970  // Subtle ice crystal shimmer
1971  h += sin(p.x * freq * 20.0 + p.y * freq * 15.0) * 0.005;
1972  return h * 0.8 + 0.1;
1973}
1974
1975// ─── Dispatch to active terrain style ────────────────────────
1976float terrainHeight(vec2 p) {
1977  float time = u_time * u_speed;
1978  float freq = 0.6 + u_terrainScale * 0.8;
1979  float h;
1980  if (u_terrainStyle == 1) {
1981    h = terrainDunes(p, time, freq);
1982  } else if (u_terrainStyle == 2) {
1983    h = terrainCanyons(p, time, freq);
1984  } else if (u_terrainStyle == 3) {
1985    h = terrainVolcanic(p, time, freq);
1986  } else if (u_terrainStyle == 4) {
1987    h = terrainOcean(p, time, freq);
1988  } else if (u_terrainStyle == 5) {
1989    h = terrainGlacial(p, time, freq);
1990  } else {
1991    h = terrainMountains(p, time, freq);
1992  }
1993  float amplitude = 0.2 + u_relief * 1.8;
1994  return h * amplitude;
1995}
1996
1997// ─── Normal via central differences ──────────────────────────
1998vec3 terrainNormal(vec2 p, float eps) {
1999  float hc = terrainHeight(p);
2000  float hx = terrainHeight(p + vec2(eps, 0.0));
2001  float hz = terrainHeight(p + vec2(0.0, eps));
2002  return normalize(vec3(hc - hx, eps, hc - hz));
2003}
2004
2005// ─── Ray-terrain intersection ────────────────────────────────
2006// March a ray against the heightfield. Returns (distance, height) or (-1, 0) on miss.
2007vec2 rayMarchTerrain(vec3 ro, vec3 rd, int maxSteps, float maxDist) {
2008  float dt = maxDist / float(maxSteps);
2009  float t = 0.01;
2010  float lastH = 0.0;
2011  float lastY = ro.y;
2012
2013  for (int i = 0; i < 128; i++) { // loop ceiling — actual steps limited by maxSteps
2014    if (i >= maxSteps) break;
2015    vec3 p = ro + rd * t;
2016    float h = terrainHeight(p.xz);
2017
2018    if (p.y < h) {
2019      // Binary refinement — 6 steps
2020      float tA = t - dt;
2021      float tB = t;
2022      for (int j = 0; j < 6; j++) {
2023        float tM = (tA + tB) * 0.5;
2024        vec3 pm = ro + rd * tM;
2025        float hm = terrainHeight(pm.xz);
2026        if (pm.y < hm) {
2027          tB = tM;
2028        } else {
2029          tA = tM;
2030        }
2031      }
2032      t = (tA + tB) * 0.5;
2033      vec3 hitP = ro + rd * t;
2034      return vec2(t, terrainHeight(hitP.xz));
2035    }
2036
2037    // Adaptive step size: step bigger when far above terrain
2038    float aboveGround = p.y - h;
2039    dt = max(0.004, min(aboveGround * 0.6, maxDist / float(maxSteps)));
2040    t += dt;
2041
2042    if (t > maxDist) break;
2043    lastH = h;
2044    lastY = p.y;
2045  }
2046  return vec2(-1.0, 0.0);
2047}
2048
2049// ─── Soft shadows ────────────────────────────────────────────
2050float softShadow(vec3 ro, vec3 lightDir, int steps, float maxDist, float k) {
2051  float res = 1.0;
2052  float t = 0.02;
2053  float dt = maxDist / float(steps);
2054  for (int i = 0; i < 32; i++) {
2055    if (i >= steps) break;
2056    vec3 p = ro + lightDir * t;
2057    float h = terrainHeight(p.xz);
2058    float d = p.y - h;
2059    if (d < 0.001) return 0.0;
2060    res = min(res, k * d / t);
2061    t += max(dt, d * 0.4);
2062    if (t > maxDist) break;
2063  }
2064  return clamp(res, 0.0, 1.0);
2065}
2066
2067// ─── Ambient occlusion ──────────────────────────────────────
2068float ambientOcclusion(vec3 pos, vec3 normal) {
2069  float ao = 0.0;
2070  float weight = 1.0;
2071  for (int i = 1; i <= 4; i++) {
2072    float dist = float(i) * 0.08;
2073    vec3 samplePos = pos + normal * dist;
2074    float h = terrainHeight(samplePos.xz);
2075    float diff = samplePos.y - h;
2076    ao += weight * clamp(diff / dist, 0.0, 1.0);
2077    weight *= 0.6;
2078  }
2079  return clamp(ao / 1.92, 0.0, 1.0);
2080}
2081
2082// ─── HUD Overlay Functions ───────────────────────────────────
2083
2084// Pattern 0: Wiregrid — world-space grid lines on terrain
2085float hudWiregrid(vec2 worldPos, float dist) {
2086  float gridScale = mix(4.0, 30.0, u_gridStrength);
2087  vec2 gUV = worldPos * gridScale;
2088  float gx = abs(fract(gUV.x) - 0.5);
2089  float gy = abs(fract(gUV.y) - 0.5);
2090  float gxAA = fwidth(gUV.x) * 0.8 + 0.01;
2091  float gyAA = fwidth(gUV.y) * 0.8 + 0.01;
2092  float wireX = 1.0 - smoothstep(0.015 + gxAA, 0.06 + gxAA * 2.0, gx);
2093  float wireY = 1.0 - smoothstep(0.015 + gyAA, 0.06 + gyAA * 2.0, gy);
2094  float wire = max(wireX, wireY);
2095  // Fade with distance
2096  wire *= smoothstep(20.0, 2.0, dist);
2097  return wire * u_wireframe;
2098}
2099
2100// Pattern 1: Contours — height-based contour lines
2101float hudContours(float height) {
2102  float contourScale = mix(8.0, 80.0, u_contourDensity);
2103  float contourField = height * contourScale;
2104  float contour = abs(fract(contourField) - 0.5);
2105  float contourAA = fwidth(contourField) * 0.8 + 0.015;
2106  float contourLine = 1.0 - smoothstep(0.06 + contourAA, 0.14 + contourAA * 2.0, contour);
2107  return contourLine;
2108}
2109
2110// Pattern 2: Topography — major/minor contour lines with elevation bands
2111float hudTopography(float height, vec2 worldPos) {
2112  float contourScale = mix(6.0, 60.0, u_contourDensity);
2113
2114  // Minor contour lines
2115  float minorField = height * contourScale;
2116  float minorContour = abs(fract(minorField) - 0.5);
2117  float minorAA = fwidth(minorField) * 0.8 + 0.01;
2118  float minorLine = 1.0 - smoothstep(0.06 + minorAA, 0.15 + minorAA * 2.0, minorContour);
2119
2120  // Major contour lines (every 5th)
2121  float majorField = height * contourScale * 0.2;
2122  float majorContour = abs(fract(majorField) - 0.5);
2123  float majorAA = fwidth(majorField) * 0.8 + 0.01;
2124  float majorLine = 1.0 - smoothstep(0.04 + majorAA, 0.10 + majorAA * 2.0, majorContour);
2125
2126  return minorLine * 0.5 + majorLine * 1.2;
2127}
2128
2129// Pattern 3: Sonar — animated concentric rings with fading trail sweep
2130float hudSonar(vec2 worldPos, float time) {
2131  vec2 center = worldPos - vec2(0.0, 2.0);
2132  float r = length(center);
2133  float ringSpacing = mix(0.5, 2.5, u_contourDensity);
2134
2135  // Concentric rings expanding outward — clear line rings
2136  float ringField = (r - time * 0.4) / ringSpacing;
2137  float rings = abs(fract(ringField) - 0.5);
2138  float ringsAA = fwidth(ringField) * 0.8 + 0.01;
2139  float ringLine = 1.0 - smoothstep(0.04 + ringsAA, 0.10 + ringsAA * 2.0, rings);
2140  // Fade rings with distance
2141  ringLine *= smoothstep(15.0, 1.0, r);
2142
2143  // Sweep beam — wide angular fade, not a point
2144  float angle = atan(center.y, center.x);
2145  float sweepAngle = time * 1.2;
2146  float angleDiff = mod(angle - sweepAngle, 6.2831853);
2147  // Wide trailing glow arc (120 degrees = ~2.09 rad)
2148  float sweep = smoothstep(2.1, 0.0, angleDiff) * smoothstep(6.28, 5.0, angleDiff);
2149  sweep *= smoothstep(12.0, 0.5, r) * 0.4;
2150
2151  return ringLine * 0.8 + sweep;
2152}
2153
2154// Pattern 4: Ridgeflow — flow lines following terrain gradient
2155float hudRidgeflow(vec2 worldPos, float height, float time) {
2156  float eps = 0.05;
2157  float hx = terrainHeight(worldPos + vec2(eps, 0.0));
2158  float hz = terrainHeight(worldPos + vec2(0.0, eps));
2159  vec2 gradient = vec2(height - hx, height - hz) / eps;
2160  float gradLen = length(gradient);
2161
2162  // Flow line via directional pattern
2163  float flowField = dot(worldPos, normalize(gradient + vec2(0.001))) * mix(3.0, 20.0, u_contourDensity);
2164  flowField += time * 0.3;
2165  float flow = abs(fract(flowField) - 0.5);
2166  float flowAA = fwidth(flowField) * 0.8 + 0.01;
2167  float flowLine = 1.0 - smoothstep(0.06 + flowAA, 0.14 + flowAA * 2.0, flow);
2168
2169  // Stronger on steep slopes
2170  flowLine *= smoothstep(0.01, 0.15, gradLen);
2171
2172  // Dashed pattern
2173  float dash = smoothstep(0.45, 0.55, fract(flowField * 0.15 - time * 0.2));
2174  flowLine *= dash;
2175
2176  return flowLine;
2177}
2178
2179void main() {
2180  vec2 uv = vUv;
2181  float time = u_time * u_speed;
2182  float aspect = u_resolution.x / max(u_resolution.y, 1.0);
2183  uv = applyMouseWarp(uv, aspect);
2184
2185  // ── Quality-adaptive step counts ──
2186  int marchSteps = u_qualityTier == 0 ? 32 : u_qualityTier == 1 ? 48 : u_qualityTier == 2 ? 64 : 80;
2187  int shadowSteps = u_qualityTier == 0 ? 0 : u_qualityTier == 1 ? 4 : u_qualityTier == 2 ? 8 : 12;
2188
2189  // ── Camera setup ──
2190  float camHeight = 0.8 + u_horizon * 3.0;
2191  float camPitch = mix(-0.25, -0.85, u_tilt);
2192
2193  vec3 ro = vec3(time * 0.15, camHeight, time * 0.08);
2194  vec3 forward = normalize(vec3(0.0, camPitch, 1.0));
2195  vec3 right = normalize(cross(forward, vec3(0.0, 1.0, 0.0)));
2196  vec3 up = cross(right, forward);
2197
2198  float fov = 1.2;
2199  vec2 screenUV = (uv - 0.5) * vec2(aspect, 1.0) * fov;
2200  vec3 rd = normalize(forward + screenUV.x * right + screenUV.y * up);
2201
2202  float maxDist = 25.0;
2203
2204  // ── Raymarch terrain ──
2205  vec2 hit = rayMarchTerrain(ro, rd, marchSteps, maxDist);
2206
2207  // ── Sky color (above horizon / miss) ──
2208  vec3 skyLow = mixColors(u_colors, u_colorPositions, u_colorCount, 0.0);
2209  vec3 skyHigh = mixColors(u_colors, u_colorPositions, u_colorCount, 0.15);
2210  vec3 skyColor = mix(skyLow, skyHigh, clamp(rd.y * 2.0 + 0.5, 0.0, 1.0)) * 0.15;
2211
2212  // Horizon glow in sky
2213  float horizonGlow = exp(-pow(rd.y * 8.0, 2.0));
2214  vec3 glowColor = mixColors(u_colors, u_colorPositions, u_colorCount, 0.85);
2215  skyColor += glowColor * horizonGlow * (0.06 + u_markerGlow * 0.2);
2216
2217  vec3 color;
2218  float dist = maxDist;
2219
2220  if (hit.x < 0.0) {
2221    // No terrain hit — render sky
2222    color = skyColor;
2223  } else {
2224    dist = hit.x;
2225    vec3 hitPos = ro + rd * dist;
2226    float h = hit.y;
2227
2228    // ── Normal computation ──
2229    float normalEps = max(0.005, dist * 0.002);
2230    vec3 normal = terrainNormal(hitPos.xz, normalEps);
2231
2232    // ── Lighting ──
2233    float lightAngle = radians(u_direction);
2234    vec3 lightDir = normalize(vec3(cos(lightAngle) * 0.7, 0.55, sin(lightAngle) * 0.7));
2235
2236    // Diffuse
2237    float diff = max(dot(normal, lightDir), 0.0);
2238
2239    // Specular (Blinn-Phong)
2240    vec3 viewDir = -rd;
2241    vec3 halfVec = normalize(lightDir + viewDir);
2242    float spec = pow(max(dot(normal, halfVec), 0.0), 64.0);
2243
2244    // Soft shadows
2245    float shadow = 1.0;
2246    if (shadowSteps > 0) {
2247      shadow = softShadow(hitPos + normal * 0.02, lightDir, shadowSteps, 6.0, 12.0);
2248    }
2249
2250    // Ambient occlusion
2251    float ao = ambientOcclusion(hitPos, normal);
2252
2253    // ── Color from palette mapped to height + slope ──
2254    float slope = 1.0 - normal.y; // 0 = flat, 1 = cliff
2255    float heightNorm = clamp(h / (0.2 + u_relief * 1.8), 0.0, 1.0);
2256    float colorT = heightNorm * 0.75 + slope * 0.25;
2257    colorT = clamp(colorT, 0.0, 1.0);
2258
2259    // Image-driven gradient integration
2260    float imgLum = imageLuminance(gl_FragCoord.xy / u_resolution.xy, aspect);
2261    if (imgLum >= 0.0) {
2262      colorT = mix(colorT, imgLum, u_imageIntensity);
2263    }
2264
2265    vec3 baseColor = mixColors(u_colors, u_colorPositions, u_colorCount, colorT);
2266    vec3 darkColor = mixColors(u_colors, u_colorPositions, u_colorCount, clamp(colorT - 0.25, 0.0, 1.0));
2267    vec3 lightColor = mixColors(u_colors, u_colorPositions, u_colorCount, clamp(colorT + 0.2, 0.0, 1.0));
2268
2269    // Compose lighting
2270    float ambient = 0.2;
2271    vec3 litColor = baseColor * (ambient + diff * shadow * 0.7);
2272    litColor += lightColor * spec * shadow * 0.35;
2273    litColor *= (0.5 + ao * 0.5);
2274
2275    // Rim/fresnel for edge highlights
2276    float fresnel = pow(1.0 - max(dot(normal, viewDir), 0.0), 3.0);
2277    litColor += lightColor * fresnel * 0.12;
2278
2279    color = litColor;
2280
2281    // ── HUD Overlay ──
2282    float hudValue = 0.0;
2283    vec3 hudColor = lightColor;
2284
2285    if (u_hudPattern == 0) {
2286      // Wiregrid
2287      hudValue = hudWiregrid(hitPos.xz, dist);
2288    } else if (u_hudPattern == 1) {
2289      // Contours
2290      hudValue = hudContours(h) * (0.3 + u_contourDensity * 0.7);
2291    } else if (u_hudPattern == 2) {
2292      // Topography
2293      hudValue = hudTopography(h, hitPos.xz) * (0.25 + u_contourDensity * 0.6);
2294    } else if (u_hudPattern == 3) {
2295      // Sonar
2296      hudValue = hudSonar(hitPos.xz, time) * (0.3 + u_gridStrength * 0.5);
2297    } else if (u_hudPattern == 4) {
2298      // Ridgeflow
2299      hudValue = hudRidgeflow(hitPos.xz, h, time) * (0.3 + u_gridStrength * 0.5);
2300    }
2301
2302    color += hudColor * hudValue * 0.2;
2303
2304    // Additional wireframe overlay (global, per gridStrength)
2305    if (u_hudPattern != 0) {
2306      float wireOverlay = hudWiregrid(hitPos.xz, dist) * 0.35;
2307      color += hudColor * wireOverlay * u_gridStrength;
2308    }
2309
2310    // ── Fog ──
2311    float fogFactor = 1.0 - exp(-dist * (0.03 + u_fogDepth * 0.12));
2312    vec3 fogColor = mix(darkColor, vec3(0.02, 0.03, 0.05), 0.7);
2313    fogColor += glowColor * horizonGlow * (0.03 + u_markerGlow * 0.1);
2314    color = mix(color, fogColor, fogFactor);
2315  }
2316
2317  // ── Horizon atmosphere band ──
2318  // Replaces dot markers with an atmospheric glow band at the horizon
2319  // that pulses and shifts with the palette, controlled by u_markerGlow
2320  if (u_markerGlow > 0.01) {
2321    float horizBand = exp(-pow((uv.y - 0.35) * 6.0, 2.0));
2322    float pulse = 0.7 + 0.3 * sin(time * 0.8);
2323    vec3 atmColor = mixColors(u_colors, u_colorPositions, u_colorCount, 0.7 + 0.15 * sin(time * 0.3));
2324    color += atmColor * horizBand * u_markerGlow * pulse * 0.25;
2325
2326    // Subtle animated light streaks at horizon
2327    float streak1 = exp(-pow((uv.y - 0.33 + sin(uv.x * 4.0 + time * 0.5) * 0.03) * 14.0, 2.0));
2328    float streak2 = exp(-pow((uv.y - 0.37 + sin(uv.x * 6.0 - time * 0.4) * 0.02) * 18.0, 2.0));
2329    vec3 streakColor = mixColors(u_colors, u_colorPositions, u_colorCount, 0.9);
2330    color += streakColor * (streak1 + streak2) * u_markerGlow * 0.1;
2331  }
2332
2333  // ── Scanlines ──
2334  float scan = sin(uv.y * u_resolution.y * 0.9 + time * 3.0) * 0.5 + 0.5;
2335  float scanStrength = u_scanlineStrength * (u_hudPattern == 3 ? 1.2 : 1.0);
2336  color *= mix(1.0, 0.82 + scan * 0.18, clamp(scanStrength, 0.0, 1.0));
2337
2338  // ── Vignette ──
2339  float vignette = smoothstep(0.35, 0.97, length((uv - 0.5) * vec2(1.12, 1.18)));
2340  color *= 1.0 - vignette * 0.55;
2341
2342  // ── Grain (only when explicitly set, no default noise for terrain) ──
2343  if (u_grain > 0.001) {
2344    color += grain(uv, time, u_grain);
2345  }
2346
2347  // ── Self-blend ──
2348  if (u_blendMode > 0) {
2349    float bRotation = u_blendAnimRotation > 0 ? animatedBlendRotation(u_blendRotation, time, u_blendAnimSpeed) : u_blendRotation;
2350    float bOffset = u_blendAnimOffset > 0 ? animatedBlendOffset(u_blendOffset, time, u_blendAnimSpeed) : u_blendOffset;
2351    float bIntensity = u_blendAnimIntensity > 0 ? animatedBlendIntensity(u_blendIntensity, time, u_blendAnimSpeed) : u_blendIntensity;
2352    bIntensity = noiseModulatedIntensity(bIntensity, uv, time, u_blendNoiseMask, u_blendNoiseScale);
2353    bIntensity = applyBlendFalloff(bIntensity, uv.x, u_blendFalloff);
2354    float tB = blendLayerT(uv, 0.5, u_resolution, u_direction, bOffset, bRotation, u_blendScale);
2355    vec3 blendCol = mixColors(u_colors, u_colorPositions, u_colorCount, tB);
2356    color = applyBlendMode(u_blendMode, color, blendCol, bIntensity);
2357    if (u_blendDualLayer > 0 && u_blendMode2 > 0) {
2358      float tB2 = blendLayerT(uv, 0.5, u_resolution, u_direction, u_blendOffset2, u_blendRotation2, u_blendScale2);
2359      vec3 blendCol2 = mixColors(u_colors, u_colorPositions, u_colorCount, tB2);
2360      color = applyBlendMode(u_blendMode2, color, blendCol2, u_blendIntensity2);
2361    }
2362  }
2363
2364  fragColor = outputColor(color, gl_FragCoord.xy, 1.15);
2365}
2366`,m=`#version 300 es
2367precision highp float;
2368
2369in vec2 vUv;
2370out vec4 fragColor;
2371
2372uniform float u_time;
2373uniform float u_speed;
2374uniform vec3 u_colors[12];
2375uniform float u_colorPositions[12];
2376uniform int u_colorCount;
2377uniform float u_direction;
2378uniform float u_grain;
2379uniform vec2 u_resolution;
2380
2381// Blend uniforms
2382uniform int u_blendMode;
2383uniform float u_blendIntensity;
2384uniform float u_blendOffset;
2385uniform float u_blendRotation;
2386uniform float u_blendScale;
2387uniform int u_blendAnimRotation;
2388uniform int u_blendAnimIntensity;
2389uniform int u_blendAnimOffset;
2390uniform float u_blendAnimSpeed;
2391uniform float u_blendNoiseMask;
2392uniform float u_blendNoiseScale;
2393uniform float u_blendFalloff;
2394uniform int u_blendDualLayer;
2395uniform int u_blendMode2;
2396uniform float u_blendIntensity2;
2397uniform float u_blendOffset2;
2398uniform float u_blendRotation2;
2399uniform float u_blendScale2;
2400
2401uniform float u_beamWidth;
2402uniform float u_beamSoftness;
2403uniform float u_beamIntensity;
2404uniform float u_beamCount;
2405uniform float u_beamSpread;
2406uniform float u_secondaryIntensity;
2407uniform int u_pattern;
2408uniform float u_patternDrift;
2409uniform float u_glow;
2410uniform float u_drift;
2411uniform float u_vignette;
2412uniform float u_falloff;
2413uniform float u_halo;
2414
2415__COMMON__
2416
2417float lineDistance(vec2 p, vec2 dir) {
2418  return dot(p, vec2(-dir.y, dir.x));
2419}
2420
2421mat2 rot2(float a) {
2422  float s = sin(a);
2423  float c = cos(a);
2424  return mat2(c, -s, s, c);
2425}
2426
2427void main() {
2428  vec2 uv = vUv;
2429  float time = u_time * u_speed;
2430  float aspect = u_resolution.x / max(u_resolution.y, 1.0);
2431  uv = applyMouseWarp(uv, aspect);
2432
2433  vec2 p = uv - 0.5;
2434  p.x *= aspect;
2435
2436  float angle = radians(u_direction);
2437  vec2 dir = normalize(vec2(cos(angle), sin(angle)));
2438  vec2 ortho = vec2(-dir.y, dir.x);
2439
2440  // Base domain warp to avoid static/flat beams.
2441  float driftNoise = snoise(p * 2.7 + vec2(time * 0.18, -time * 0.13));
2442  float driftWave = sin(dot(p, ortho) * 9.0 + time * 0.7);
2443  vec2 warped = p + ortho * driftNoise * u_drift * 0.06 + dir * driftWave * u_drift * 0.025;
2444
2445  float coreWidth = 0.002 + u_beamWidth * 0.045;
2446  float softWidth = coreWidth + 0.02 + u_beamSoftness * 0.2;
2447  float spreadPx = (0.02 + u_beamSpread * 0.32) * (0.75 + u_beamWidth * 0.6);
2448
2449  int beamCount = int(clamp(floor(u_beamCount + 0.5), 1.0, 6.0));
2450  vec3 color = vec3(0.0);
2451  float envelope = 0.0;
2452  float mainCore = 0.0;
2453  float phase = time * (0.35 + u_patternDrift * 1.35);
2454
2455  for (int i = 0; i < 6; i++) {
2456    if (i >= beamCount) break;
2457
2458    float fi = float(i);
2459    float centered = fi - float(beamCount - 1) * 0.5;
2460    float norm = (beamCount > 1) ? centered / max(float(beamCount - 1), 1.0) : 0.0;
2461
2462    float angleOffset = 0.0;
2463    float lateral = centered * spreadPx;
2464    float axial = 0.0;
2465
2466    if (u_pattern == 1) {
2467      // Parallel stack
2468      lateral += sin(phase + centered * 1.9) * (0.01 + 0.06 * u_patternDrift);
2469    } else if (u_pattern == 2) {
2470      // Cross-light composition
2471      float signV = centered == 0.0 ? (mod(fi, 2.0) == 0.0 ? -1.0 : 1.0) : sign(centered);
2472      angleOffset = signV * mix(0.08, 0.72, u_beamSpread) * (0.35 + abs(norm));
2473      lateral *= 0.55;
2474    } else if (u_pattern == 3) {
2475      // Fan spread
2476      angleOffset = norm * mix(0.08, 0.9, u_beamSpread);
2477      lateral *= 0.62;
2478    } else if (u_pattern == 4) {
2479      // Interference / moire beam weave
2480      angleOffset = sin(phase * 0.55 + norm * 9.0) * (0.08 + 0.35 * u_beamSpread);
2481      lateral += sin((dot(warped, ortho) + norm * 2.8) * 6.0 + phase * 1.8) * (0.025 + 0.14 * u_beamSpread);
2482      axial += cos((dot(warped, dir) + norm * 3.4) * 4.2 + phase * 1.2) * 0.04 * u_patternDrift;
2483    }
2484
2485    vec2 beamDir = normalize(rot2(angleOffset) * dir);
2486    vec2 beamOrtho = vec2(-beamDir.y, beamDir.x);
2487    vec2 q = warped + beamOrtho * lateral + beamDir * axial;
2488
2489    float d = lineDistance(q, beamDir);
2490    float along = dot(q, beamDir);
2491    float core = exp(-pow(abs(d) / coreWidth, 1.35));
2492    float soft = exp(-pow(abs(d) / softWidth, 1.05));
2493    float halo = exp(-pow(abs(d) / (softWidth * 2.8 + 0.02), 0.9));
2494
2495    float alongMaskA = smoothstep(-0.98, -0.18, along);
2496    float alongMaskB = 1.0 - smoothstep(0.32, 1.02, along);
2497    float alongMask = clamp(alongMaskA * alongMaskB, 0.0, 1.0);
2498
2499    float tBase = clamp(along * 0.5 + 0.5 + soft * 0.22 - halo * 0.1 + norm * 0.05, 0.0, 1.0);
2500    vec3 base = mixColors(u_colors, u_colorPositions, u_colorCount, tBase);
2501    vec3 warm = mixColors(u_colors, u_colorPositions, u_colorCount, clamp(tBase + 0.16, 0.0, 1.0));
2502    vec3 cool = mixColors(u_colors, u_colorPositions, u_colorCount, clamp(tBase - 0.14, 0.0, 1.0));
2503
2504    float weight = (i == 0) ? 1.0 : u_secondaryIntensity * exp(-abs(centered) * 0.42);
2505    float coreEnergy = core * (0.6 + 2.0 * u_beamIntensity);
2506    float softEnergy = soft * (0.48 + 0.7 * u_glow) * alongMask;
2507    float haloEnergy = halo * (0.15 + 0.48 * u_halo);
2508
2509    color += cool * haloEnergy * u_falloff * weight;
2510    color += base * softEnergy * weight;
2511    color += warm * coreEnergy * weight;
2512    color += vec3(1.0, 0.95, 0.88) * core * (0.18 + 0.95 * u_halo) * weight;
2513
2514    float lobeA = exp(-pow(abs(d - softWidth * 1.65) / (softWidth * 1.55 + 0.01), 2.0));
2515    float lobeB = exp(-pow(abs(d + softWidth * 1.35) / (softWidth * 1.65 + 0.01), 2.0));
2516    color += base * (lobeA * 0.13 + lobeB * 0.11) * u_glow * weight;
2517
2518    envelope += soft * alongMask * weight;
2519    mainCore = max(mainCore, core * weight);
2520  }
2521
2522  // Image-driven beam energy modulation
2523  float imgMask = imageLuminance(uv, aspect);
2524  if (imgMask >= 0.0) {
2525    color *= mix(1.0, imgMask, u_imageIntensity);
2526  }
2527
2528  float patternHighlight = smoothstep(0.22, 1.1, envelope);
2529  vec3 warmEdge = mixColors(u_colors, u_colorPositions, u_colorCount, 0.86);
2530  vec3 coolEdge = mixColors(u_colors, u_colorPositions, u_colorCount, 0.18);
2531
2532  // Corner halation and ambient haze.
2533  float cornerA = exp(-length((uv - vec2(0.18, 0.24)) * vec2(1.4, 1.0)) * 4.0);
2534  float cornerB = exp(-length((uv - vec2(0.82, 0.78)) * vec2(1.3, 1.0)) * 4.5);
2535  float haze = exp(-length(p * vec2(1.1, 0.9)) * 2.0);
2536  color += warmEdge * cornerA * (0.08 + 0.2 * patternHighlight) * u_glow;
2537  color += coolEdge * cornerB * (0.07 + 0.16 * patternHighlight) * u_glow;
2538  color += mix(coolEdge, warmEdge, 0.5) * haze * (0.04 + 0.14 * u_glow);
2539  color += vec3(1.0, 0.96, 0.9) * mainCore * (0.06 + 0.28 * u_halo);
2540
2541  // Strong vignette to keep a cinematic frame.
2542  float r = length((uv - 0.5) * vec2(1.15, 1.2));
2543  float vignette = smoothstep(0.35, 0.95, r);
2544  color *= mix(1.0, 1.0 - 0.86 * vignette, u_vignette);
2545
2546  color += grain(uv, time, u_grain * (0.75 + 0.15 * patternHighlight));
2547
2548  // Self-blend
2549  if (u_blendMode > 0) {
2550    float bRotation = u_blendAnimRotation > 0 ? animatedBlendRotation(u_blendRotation, time, u_blendAnimSpeed) : u_blendRotation;
2551    float bOffset = u_blendAnimOffset > 0 ? animatedBlendOffset(u_blendOffset, time, u_blendAnimSpeed) : u_blendOffset;
2552    float bIntensity = u_blendAnimIntensity > 0 ? animatedBlendIntensity(u_blendIntensity, time, u_blendAnimSpeed) : u_blendIntensity;
2553    bIntensity = noiseModulatedIntensity(bIntensity, uv, time, u_blendNoiseMask, u_blendNoiseScale);
2554    bIntensity = applyBlendFalloff(bIntensity, uv.x, u_blendFalloff);
2555    float tB = blendLayerT(uv, 0.5, u_resolution, u_direction, bOffset, bRotation, u_blendScale);
2556    vec3 blendCol = mixColors(u_colors, u_colorPositions, u_colorCount, tB);
2557    color = applyBlendMode(u_blendMode, color, blendCol, bIntensity);
2558    if (u_blendDualLayer > 0 && u_blendMode2 > 0) {
2559      float tB2 = blendLayerT(uv, 0.5, u_resolution, u_direction, u_blendOffset2, u_blendRotation2, u_blendScale2);
2560      vec3 blendCol2 = mixColors(u_colors, u_colorPositions, u_colorCount, tB2);
2561      color = applyBlendMode(u_blendMode2, color, blendCol2, u_blendIntensity2);
2562    }
2563  }
2564
2565  fragColor = outputColor(color, gl_FragCoord.xy, 1.2);
2566}
2567`,_=`#version 300 es
2568precision highp float;
2569
2570in vec2 vUv;
2571out vec4 fragColor;
2572
2573/* -- Uniforms -------------------------------------------------- */
2574uniform float u_time;
2575uniform float u_speed;
2576uniform float u_grain;
2577uniform vec2 u_resolution;
2578
2579// 4 anchor colors (sRGB, sorted dark -> light on CPU)
2580uniform vec3 u_colorA;   // darkest  -- base / trough shadows
2581uniform vec3 u_colorB;   // dark-mid -- primary fill
2582uniform vec3 u_colorC;   // light-mid-- accent color
2583uniform vec3 u_colorD;   // lightest -- glow, rim, specular
2584
2585// Glass / flute controls
2586uniform float u_density;
2587uniform float u_ridgeAngle;
2588uniform float u_distortion;
2589uniform float u_blur;
2590uniform float u_dispersion;
2591uniform float u_gain;
2592uniform float u_vignette;
2593uniform float u_tone;
2594uniform float u_specIntensity;
2595uniform float u_ridgeShape;
2595     // 0=sine, 1=chevron, 2=square, 3=sawtooth
2596uniform float u_waviness;       // 0 = perfectly straight, 2+ = wavy
2597uniform float u_transmission;   // base brightness (glass transparency)
2598uniform float u_extraDistort;   // extra refraction offset strength
2599uniform float u_noiseScale;     // FBM noise frequency in background
2600uniform float u_animSpeed;      // animation speed multiplier
2601uniform float u_surfaceRough;   // surface grain / specular spread
2602uniform float u_envBoost;       // glow / accent brightness
2603
2604/* -- Hash / noise / FBM ---------------------------------------- */
2605
2606float hash12(vec2 p) {
2607  vec3 p3 = fract(vec3(p.xyx) * 0.1031);
2608  p3 += dot(p3, p3.yzx + 33.33);
2609  return fract((p3.x + p3.y) * p3.z);
2610}
2611
2612float noise(vec2 p) {
2613  vec2 i = floor(p);
2614  vec2 f = fract(p);
2615  float a = hash12(i);
2616  float b = hash12(i + vec2(1.0, 0.0));
2617  float c = hash12(i + vec2(0.0, 1.0));
2618  float d = hash12(i + vec2(1.0, 1.0));
2619  vec2 u = f * f * (3.0 - 2.0 * f);
2620  return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
2621}
2622
2623float fbm(vec2 p) {
2624  float v = 0.0;
2625  float a = 0.5;
2626  for (int i = 0; i < 6; i++) {
2627    v += a * noise(p);
2628    p = p * 2.02 + vec2(11.7, 4.9);
2629    a *= 0.52;
2630  }
2631  return v;
2632}
2633
2634/* -- Scene color: FBM-warped 4-color field --------------------- */
2635
2636vec3 sceneColor(vec2 uv) {
2637  float t = u_time * u_speed * 0.1 * u_animSpeed;
2638  float aspect = u_resolution.x / u_resolution.y;
2639
2640  vec2 p = uv;
2641  vec2 warp = vec2(
2642    fbm(p * 3.3 * u_noiseScale + vec2(t, -t * 0.55)),
2643    fbm(p * 3.1 * u_noiseScale + vec2(-t * 0.65, t * 0.7))
2644  );
2645  p += (warp - 0.5) * 0.44;
2646
2647  float n0 = fbm(p * 2.4 * u_noiseScale + vec2(t * 0.4, 0.0));
2648  float n1 = fbm(p * 5.2 * u_noiseScale - vec2(0.0, t * 0.3));
2649  float blend = smoothstep(0.1, 0.95, n0 * 0.7 + n1 * 0.5);
2650
2651  vec3 darkBase = u_colorA * 0.08;
2652  vec3 richB = u_colorB * 1.25;
2653
2654  vec3 col = mix(darkBase, richB, blend);
2655  col = mix(col, u_colorC * 1.15, smoothstep(0.25, 0.85, n1));
2656
2657  float radial = length((p - 0.5) * vec2(aspect, 1.0));
2658  float glow = pow(max(0.0, 1.0 - radial * 1.1), 2.0);
2659  col += u_colorD * glow * 0.45 * u_envBoost;
2660
2661  float beams = pow(max(0.0, sin((p.x * 1.2 + p.y * 0.9 + t * 0.9) * 6.28318)), 5.0);
2662  col += u_colorC * beams * 0.12 * u_envBoost;
2663
2664  return col;
2665}
2666
2667/* -- Main ------------------------------------------------------ */
2668
2669void main() {
2670  vec2 uv = vUv;
2671  float aspect = u_resolution.x / u_resolution.y;
2672
2673  // 1. Rotate coordinates for diagonal flutes
2674  vec2 centered = (uv - 0.5) * vec2(aspect, 1.0);
2675  float ca = cos(u_ridgeAngle);
2676  float sa = sin(u_ridgeAngle);
2677  vec2 rot = vec2(
2678    ca * centered.x - sa * centered.y,
2679    sa * centered.x + ca * centered.y
2680  );
2681
2682  // 2. Flute wave: shape x waviness
2683  float t = u_time * u_speed;
2684  float fractal = fbm(vec2(rot.x * 0.85, rot.y * 2.6 + t * 0.15));
2685  float phase = rot.x * u_density * 6.2831853 + (fractal - 0.5) * u_waviness;
2686
2687  // Wave shape selection
2688  float rawWave, rawSlope;
2689
2690  if (u_ridgeShape < 0.5) {
2691    // Sine: classic smooth curves
2692    rawWave = sin(phase);
2693    rawSlope = cos(phase);
2694  } else if (u_ridgeShape < 1.5) {
2695    // Chevron: triangle wave -- sharp V-shaped ridges
2696    float triP = mod(phase / 6.2831853 + 0.25, 1.0);
2697    rawWave = 4.0 * abs(triP - 0.5) - 1.0;
2698    rawSlope = mix(4.0, -4.0, step(0.5, triP));
2699  } else if (u_ridgeShape < 2.5) {
2700    // Square: flat ridges with hard clean edges
2701    rawWave = clamp(sin(phase) * 8.0, -1.0, 1.0);
2702    float edgeMask = max(0.0, 1.0 - rawWave * rawWave);
2703    rawSlope = cos(phase) * 8.0 * edgeMask;
2704  } else {
2705    // Sawtooth: Fourier series (7 harmonics) — one sharp edge per period
2706    rawWave  = sin(phase)         - sin(2.0*phase)/2.0
2707             + sin(3.0*phase)/3.0 - sin(4.0*phase)/4.0
2708             + sin(5.0*phase)/5.0 - sin(6.0*phase)/6.0
2709             + sin(7.0*phase)/7.0;
2710    rawWave *= 0.64;  // 2/pi normalize to ~[-1,1]
2711    rawSlope = cos(phase)         - cos(2.0*phase)
2712             + cos(3.0*phase)     - cos(4.0*phase)
2713             + cos(5.0*phase)     - cos(6.0*phase)
2714             + cos(7.0*phase);
2715    rawSlope *= 0.64;
2716  }
2717
2718  float sharpK = max(1.0, 10.0 / max(u_density, 1.0));
2719  float wave = clamp(rawWave * sharpK, -1.0, 1.0);
2720  float slope = clamp(rawSlope * min(sharpK, 2.5), -1.0, 1.0);
2721
2722  // 3. Refraction + 9-tap anisotropic blur + dispersion
2723  vec2 axis = normalize(vec2(ca / aspect, sa));
2724  vec2 tangent = normalize(vec2(-sa / aspect, ca));
2725
2726  float wobble = sin(rot.y * 17.0 + t * 0.45 + fractal * 8.0) * 0.2 * min(u_waviness, 1.0);
2727  vec2 refractOffset = axis * (wave * u_distortion * 0.08 * (1.0 + u_extraDistort * 3.0) + wobble * 0.0025);
2728
2729  vec3 refracted = vec3(0.0);
2730  float total = 0.0;
2731
2732  float blurDensityScale = clamp(u_density / 8.0, 0.15, 1.0);
2733  float chromaDensityScale = clamp(u_density / 6.0, 0.25, 1.0);
2734  vec2 chroma = axis * (u_dispersion / u_resolution.y) * chromaDensityScale;
2735
2736  for (int i = -4; i <= 4; i++) {
2737    float fi = float(i);
2738    float w = 1.0 - abs(fi) / 5.4;
2739    vec2 blurStep = tangent * fi * (u_blur / u_resolution.y) * 0.82 * blurDensityScale;
2740    vec2 suv = uv + refractOffset + blurStep;
2741
2742    vec3 cr = sceneColor(suv + chroma);
2743    vec3 cg = sceneColor(suv);
2744    vec3 cb = sceneColor(suv - chroma);
2745    refracted += vec3(cr.r, cg.g, cb.b) * w;
2746    total += w;
2747  }
2748  refracted /= max(total, 0.0001);
2749
2750  // 4. Glass lighting
2751  vec3 N = normalize(vec3(-slope * u_distortion * 5.0, (fractal - 0.5) * 0.8, 1.0));
2752  vec3 L1 = normalize(vec3(-0.55, 0.4, 1.0));
2753  vec3 L2 = normalize(vec3(0.66, -0.25, 1.0));
2754  float diff = max(dot(N, L1), 0.0);
2755  float specPow1 = max(10.0, 48.0 - u_surfaceRough * 40.0);
2756  float specPow2 = max(16.0, 72.0 - u_surfaceRough * 55.0);
2757  float spec = pow(max(dot(N, L1), 0.0), specPow1) + pow(max(dot(N, L2), 0.0), specPow2) * 0.9;
2758
2759  float glassThickness = clamp(u_tone * 10.0, 0.0, 1.0);
2760  float crestExp = mix(24.0, 6.0, glassThickness);
2761  float crest = pow(1.0 - abs(wave), crestExp);
2762  float brightRim = pow(max(0.0, slope), 10.0) * mix(0.6, 1.0, glassThickness);
2763  float troughCoeff = mix(0.35, 0.65, glassThickness);
2764  float troughShadow = pow(abs(wave), 1.7) * troughCoeff;
2765
2766  vec3 color = refracted;
2767  color *= (0.4 + u_transmission * 0.25) + diff * 0.5;
2768  color += spec * (u_colorD * 0.6 + vec3(0.62)) * u_specIntensity;
2769  color += crest * vec3(1.0) * 0.28;
2770  color += brightRim * u_colorD * 0.32;
2771  color -= troughShadow * 1.15;
2772
2773  // 5. Polish: grain, vignette, tone, gain, gamma
2774  float grain = (hash12(gl_FragCoord.xy + vec2(t * 120.0)) - 0.5) * (0.085 + u_grain * 0.12 + u_surfaceRough * 0.08);
2775  color += grain;
2776
2777  float vig = smoothstep(1.3, 0.18, length((uv - 0.5) * vec2(aspect, 1.0)));
2778  color *= mix(0.35, 1.18, pow(vig, u_vignette));
2779
2780  float luma = dot(color, vec3(0.299, 0.587, 0.114));
2781  color = mix(color, vec3(luma), u_tone);
2782  color *= u_gain;
2783  color = pow(max(color, vec3(0.0)), vec3(0.95));
2784
2785  fragColor = vec4(color, 1.0);
2786}
2787`,p=`#version 300 es
2788precision highp float;
2789
2790in vec2 vUv;
2791out vec4 fragColor;
2792
2793/* -- Standard color uniforms ----------------------------------- */
2794uniform float u_time;
2795uniform float u_speed;
2796uniform float u_direction;
2797uniform float u_grain;
2798uniform vec2 u_resolution;
2799uniform vec3 u_colors[12];
2800uniform float u_colorPositions[12];
2801uniform int u_colorCount;
2802
2803/* -- Fractal glass uniforms ------------------------------------ */
2804uniform float u_ridgeCount;         // number of ridges (4-60)
2805uniform int   u_ridgeShape;
2805         // 0=sine, 1=chevron, 2=square, 3=sawtooth
2806uniform int   u_ridgePattern;       // 0=lines, 1=waves, 2=zigzag
2807uniform int   u_ridgeOrientation;   // 0=vertical, 1=horizontal
2808uniform float u_ridgeDepth;         // 0-1, strength of ridge effect
2809uniform float u_transmission;       // base brightness
2810uniform float u_thickness;          // glass thickness → lighting depth
2811uniform float u_chromaticAberration;// color separation
2812uniform float u_clearcoat;          // specular intensity
2813uniform float u_ior;                // index of refraction
2814uniform float u_roughness;          // surface roughness (blurs highlights)
2815
2816__COMMON__
2817
2818void main() {
2819  vec2 uv = vUv;
2820  float aspect = u_resolution.x / u_resolution.y;
2821
2822  /* 1. Ridge angle from orientation */
2823  float ridgeAngle = u_ridgeOrientation == 0 ? -0.85 : -0.85 + 1.5708;
2824
2825  /* 2. Rotate coordinates for diagonal flutes */
2826  vec2 centered = (uv - 0.5) * vec2(aspect, 1.0);
2827  float ca = cos(ridgeAngle);
2828  float sa = sin(ridgeAngle);
2829  vec2 rot = vec2(
2830    ca * centered.x - sa * centered.y,
2831    sa * centered.x + ca * centered.y
2832  );
2833
2834  /* 3. Pattern-based waviness (cheap deterministic, no FBM) */
2835  float waveOffset = 0.0;
2836  if (u_ridgePattern == 1) {
2837    /* waves: sinusoidal cross-coordinate offset */
2838    waveOffset = sin(rot.y * 5.0) * 0.08;
2839  } else if (u_ridgePattern == 2) {
2840    /* zigzag: triangle cross-coordinate offset */
2841    waveOffset = (abs(fract(rot.y * 3.0) - 0.5) - 0.25) * 0.24;
2842  }
2843  float phase = (rot.x + waveOffset) * u_ridgeCount * 6.2831853;
2844
2845  /* 4. Wave shape selection */
2846  float rawWave, rawSlope;
2847
2848  if (u_ridgeShape == 0) {
2849    /* Sine */
2850    rawWave = sin(phase);
2851    rawSlope = cos(phase);
2852  } else if (u_ridgeShape == 1) {
2853    /* Chevron (triangle wave) */
2854    float triP = mod(phase / 6.2831853 + 0.25, 1.0);
2855    rawWave = 4.0 * abs(triP - 0.5) - 1.0;
2856    rawSlope = mix(4.0, -4.0, step(0.5, triP));
2857  } else if (u_ridgeShape == 2) {
2858    /* Square */
2859    rawWave = clamp(sin(phase) * 8.0, -1.0, 1.0);
2860    float edgeMask = max(0.0, 1.0 - rawWave * rawWave);
2861    rawSlope = cos(phase) * 8.0 * edgeMask;
2862  } else {
2863    /* Sawtooth (Fourier 7 harmonics) */
2864    rawWave  = sin(phase)         - sin(2.0*phase)/2.0
2865             + sin(3.0*phase)/3.0 - sin(4.0*phase)/4.0
2866             + sin(5.0*phase)/5.0 - sin(6.0*phase)/6.0
2867             + sin(7.0*phase)/7.0;
2868    rawWave *= 0.64;
2869    rawSlope = cos(phase)         - cos(2.0*phase)
2870             + cos(3.0*phase)     - cos(4.0*phase)
2871             + cos(5.0*phase)     - cos(6.0*phase)
2872             + cos(7.0*phase);
2873    rawSlope *= 0.64;
2874  }
2875
2876  /* 5. Sharpen + clamp */
2877  float sharpK = max(1.0, 10.0 / max(u_ridgeCount, 1.0));
2878  float wave = clamp(rawWave * sharpK, -1.0, 1.0);
2879  float slope = clamp(rawSlope * min(sharpK, 2.5), -1.0, 1.0);
2880
2881  /* 6. Refraction offset (simplified single-tap per channel) */
2882  vec2 axis = normalize(vec2(ca / aspect, sa));
2883  float refrStrength = u_ridgeDepth * 0.06 * max(u_ior - 1.0, 0.1);
2884  vec2 refractOffset = axis * wave * refrStrength;
2885
2886  /* 7. Sample gradient with chromatic aberration (3 taps: R, G, B) */
2887  float gAngle = radians(u_direction);
2888  vec2 gdir = vec2(cos(gAngle), sin(gAngle));
2889  vec2 chroma = axis * u_chromaticAberration * 0.015;
2890
2891  float tR = dot((uv + refractOffset + chroma) - 0.5, gdir) + 0.5;
2892  float tG = dot((uv + refractOffset) - 0.5, gdir) + 0.5;
2893  float tB = dot((uv + refractOffset - chroma) - 0.5, gdir) + 0.5;
2894
2895  vec3 colR = mixColors(u_colors, u_colorPositions, u_colorCount, fract(tR));
2896  vec3 colG = mixColors(u_colors, u_colorPositions, u_colorCount, fract(tG));
2897  vec3 colB = mixColors(u_colors, u_colorPositions, u_colorCount, fract(tB));
2898  vec3 color = vec3(colR.r, colG.g, colB.b);
2899
2900  /* 8. Glass lighting */
2901  float glassThickness = clamp(u_thickness * 2.0, 0.0, 1.0);
2902  float crestExp = mix(24.0, 6.0, glassThickness);
2903  float crest = pow(1.0 - abs(wave), crestExp);
2904  float brightRim = pow(max(0.0, slope), 10.0) * mix(0.6, 1.0, glassThickness);
2905  float troughCoeff = mix(0.35, 0.65, glassThickness);
2906  float troughShadow = pow(abs(wave), 1.7) * troughCoeff;
2907
2908  /* Diffuse + specular */
2909  float diff = max(slope * 0.5 + 0.5, 0.0);
2910  float specPow = max(8.0, 48.0 - u_roughness * 40.0);
2911  float spec = pow(max(slope, 0.0), specPow) * u_clearcoat;
2912
2913  color *= (0.4 + u_transmission * 0.25) + diff * 0.3;
2914  color += spec * vec3(0.8) * 0.5;
2915  color += crest * vec3(1.0) * 0.2;
2916  color += brightRim * vec3(1.0) * 0.25;
2917  color -= troughShadow * 0.8;
2918
2919  fragColor = outputColor(color, gl_FragCoord.xy, 1.0);
2920}
2921`;e.s(["FRACTAL_GLASS_FRAGMENT",0,_,"FRACTAL_GLASS_THUMBNAIL_FRAGMENT",0,p,"RIDGE_ORIENTATION_MAP",0,{vertical:-.85,horizontal:-.85+Math.PI/2},"RIDGE_PATTERN_MAP",0,{lines:.3,waves:2.4,zigzag:1.5},"RIDGE_SHAPE_MAP",0,{sine:0,chevron:1,square:2,sawtooth:3}],293457);let v=`#version 300 es
2922precision highp float;
2923
2924in vec2 vUv;
2925out vec4 fragColor;
2926
2927uniform float u_time;
2928uniform float u_speed;
2929uniform vec3 u_colors[12];
2930uniform float u_colorPositions[12];
2931uniform int u_colorCount;
2932uniform float u_direction;
2933uniform float u_grain;
2934uniform vec2 u_resolution;
2935
2936// Blend uniforms
2937uniform int u_blendMode;
2938uniform float u_blendIntensity;
2939uniform float u_blendOffset;
2940uniform float u_blendRotation;
2941uniform float u_blendScale;
2942uniform int u_blendAnimRotation;
2943uniform int u_blendAnimIntensity;
2944uniform int u_blendAnimOffset;
2945uniform float u_blendAnimSpeed;
2946uniform float u_blendNoiseMask;
2947uniform float u_blendNoiseScale;
2948uniform float u_blendFalloff;
2949uniform int u_blendDualLayer;
2950uniform int u_blendMode2;
2951uniform float u_blendIntensity2;
2952uniform float u_blendOffset2;
2953uniform float u_blendRotation2;
2954uniform float u_blendScale2;
2955
2956// Mode-specific uniforms
2957uniform int u_surfaceShape;
2958uniform float u_shapeScale;
2959uniform float u_roundness;
2960uniform float u_twist;
2961uniform float u_cameraDistance;
2962uniform float u_cameraPitch;
2963uniform float u_cameraYaw;
2964uniform float u_cameraFov;
2965uniform int u_autoRotate;
2966uniform float u_autoRotateSpeed;
2967uniform int u_gradientMapping;
2968uniform float u_gradientScale;
2969uniform float u_diffuse;
2970uniform float u_specularStrength;
2971uniform float u_specularPower;
2972uniform float u_fresnel;
2973uniform float u_lightX;
2974uniform float u_lightY;
2975uniform float u_lightZ;
2976uniform float u_ambient;
2977uniform float u_shadowSoftness;
2978uniform float u_aoStrength;
2979uniform float u_aoRadius;
2980uniform float u_bgGradient;
2981uniform float u_bgBlur;
2982uniform float u_reflectivity;
2983uniform int u_csgOp;
2984uniform int u_csgShape;
2985uniform float u_csgBlend;
2986uniform int u_transparentBg;
2987
2988// Quality tier: 0=low, 1=medium, 2=high, 3=ultra
2989uniform int u_qualityTier;
2990
2991// PBR Material uniforms
2992uniform float u_pbrMetallic;
2993uniform float u_pbrRoughness;
2994uniform float u_pbrAnisotropy;
2995
2996// HDRI environment map
2997uniform samplerCube u_envMap;
2998uniform int u_useEnvMap;
2999uniform float u_envIntensity;
3000uniform float u_envRotation;
3001
3002// Enhanced point lights (up to 4)
3003uniform vec3 u_pointLights[4];
3004uniform vec3 u_pointLightColors[4];
3005uniform int u_numPointLights;
3006
3007__COMMON__
3008
3009// ---- Rotation helpers ----
3010mat3 rotX(float a) {
3011  float s = sin(a), c = cos(a);
3012  return mat3(1.0, 0.0, 0.0, 0.0, c, -s, 0.0, s, c);
3013}
3014mat3 rotY(float a) {
3015  float s = sin(a), c = cos(a);
3016  return mat3(c, 0.0, s, 0.0, 1.0, 0.0, -s, 0.0, c);
3017}
3018mat2 rot2(float a) {
3019  float s = sin(a), c = cos(a);
3020  return mat2(c, -s, s, c);
3021}
3022
3023// ---- SDF primitives ----
3024float sdSphere(vec3 p) {
3025  return length(p) - 1.0;
3026}
3027
3028float sdTorus(vec3 p) {
3029  vec2 q = vec2(length(p.xz) - 0.6, p.y);
3030  return length(q) - 0.4;
3031}
3032
3033float sdBox(vec3 p, vec3 b) {
3034  vec3 q = abs(p) - b;
3035  return length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0);
3036}
3037
3038float sdCube(vec3 p) {
3039  return sdBox(p, vec3(0.8)) - u_roundness * 0.2;
3040}
3041
3042float sdOctahedron(vec3 p) {
3043  p = abs(p);
3044  float m = p.x + p.y + p.z - 1.0;
3045  vec3 q;
3046  if (3.0 * p.x < m) q = p.xyz;
3047  else if (3.0 * p.y < m) q = p.yzx;
3048  else if (3.0 * p.z < m) q = p.zxy;
3049  else return m * 0.57735027;
3050  float k = clamp(0.5 * (q.z - q.y + 1.0), 0.0, 1.0);
3051  return length(vec3(q.x, q.y - 1.0 + k, q.z - k));
3052}
3053
3054float sdCylinder(vec3 p) {
3055  vec2 d = abs(vec2(length(p.xz), p.y)) - vec2(0.6, 1.0);
3056  return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - u_roundness * 0.1;
3057}
3058
3059float sdCone(vec3 p) {
3060  float q = length(p.xz);
3061  return max(dot(vec2(0.6, 0.8), vec2(q, -p.y)), p.y - 1.0);
3062}
3063
3064float sdCapsule(vec3 p) {
3065  p.y -= clamp(p.y, -0.8, 0.8);
3066  return length(p) - 0.5;
3067}
3068
3069float sdKnot(vec3 p) {
3070  // Trefoil knot approximation via closest distance to parametric curve
3071  float bestD = 1e10;
3072  for (int i = 0; i < 48; i++) {
3073    float t = float(i) / 48.0 * 6.28318;
3074    float r = 0.6 + 0.3 * cos(3.0 * t);
3075    vec3 c = vec3(r * cos(2.0 * t), 0.3 * sin(3.0 * t), r * sin(2.0 * t));
3076    bestD = min(bestD, length(p - c));
3077  }
3078  return bestD - 0.15;
3079}
3080
3081float sdMobius(vec3 p) {
3082  // Mobius strip approximation
3083  float a = atan(p.z, p.x);
3084  float r = length(p.xz) - 0.7;
3085  float ha = a * 0.5;
3086  float cosHa = cos(ha), sinHa = sin(ha);
3087  float y2 = p.y * cosHa - r * sinHa;
3088  float r2 = p.y * sinHa + r * cosHa;
3089  return length(vec2(r2, y2)) - 0.15;
3090}
3091
3092float sdStar(vec3 p) {
3093  // Extruded 5-pointed star
3094  float d2 = abs(p.y) - 0.3;
3095  vec2 q = p.xz;
3096  float a = atan(q.y, q.x);
3097  float r = length(q);
3098  float star = r - 0.6 + 0.3 * cos(5.0 * a);
3099  return max(star, d2) - u_roundness * 0.05;
3100}
3101
3102float sdHeart(vec3 p) {
3103  p.y -= 0.2;
3104  p *= 1.2;
3105  float x2 = p.x * p.x;
3106  float y = p.y;
3107  float z2 = p.z * p.z;
3108  float cube = x2 + 2.25 * y * y + z2 - 1.0;
3109  return (cube * cube * cube - (x2 + 0.075 * z2) * y * y * y) * 0.5;
3110}
3111
3112float sdGyroid(vec3 p) {
3113  p *= 3.14159;
3114  return (sin(p.x) * cos(p.y) + sin(p.y) * cos(p.z) + sin(p.z) * cos(p.x)) / 3.14159 * 0.35;
3115}
3116
3117// ---- Smooth CSG operators (needed by SDF helpers below) ----
3118float smin(float a, float b, float k) {
3119  float h = clamp(0.5 + 0.5 * (b - a) / max(k, 0.001), 0.0, 1.0);
3120  return mix(b, a, h) - k * h * (1.0 - h);
3121}
3122
3123float smax(float a, float b, float k) {
3124  return -smin(-a, -b, k);
3125}
3126
3127// ---- Diamond / faceted gem ----
3128float sdDiamond(vec3 p) {
3129  // Octahedron variant: top pyramid (short) + bottom pyramid (tall)
3130  float top = (abs(p.x) + abs(p.z)) * 0.707 + p.y * 0.8 - 0.5;
3131  float bottom = (abs(p.x) + abs(p.z)) * 0.707 - p.y * 0.5 - 0.7;
3132  return max(top, bottom) - u_roundness * 0.05;
3133}
3134
3135// ---- Teardrop / water drop ----
3136float sdTeardrop(vec3 p) {
3137  vec3 q = p;
3138  q.y -= 0.2;
3139  float body = length(q) - 0.7;
3140  float tip = length(vec3(q.x * 0.5, q.y - 0.7, q.z * 0.5)) - 0.15;
3141  return smin(body, tip, 0.4);
3142}
3143
3144// ---- Double helix / DNA ----
3145float sdHelix(vec3 p) {
3146  float bestD = 1e10;
3147  for (int i = 0; i < 64; i++) {
3148    float t = float(i) / 64.0 * 12.566; // 2 full turns
3149    float h = t / 6.283 - 1.0;
3150    vec3 c1 = vec3(0.5 * cos(t), h, 0.5 * sin(t));
3151    vec3 c2 = vec3(0.5 * cos(t + 3.14159), h, 0.5 * sin(t + 3.14159));
3152    bestD = min(bestD, min(length(p - c1), length(p - c2)));
3153  }
3154  return bestD - 0.09 - u_roundness * 0.04;
3155}
3156
3157// ---- Pyramid (square base) ----
3158float sdPyramid(vec3 p) {
3159  float h = 1.2;
3160  vec3 q = abs(p);
3161  float base = max(q.x, q.z) - 0.8 + p.y * 0.65;
3162  float cap = p.y - h * 0.5;
3163  float floor_ = -p.y - h * 0.3;
3164  return max(max(base, cap), floor_) - u_roundness * 0.05;
3165}
3166
3167// ---- Helical spring / coil ----
3168float sdSpring(vec3 p) {
3169  float bestD = 1e10;
3170  for (int i = 0; i < 48; i++) {
3171    float t = float(i) / 48.0 * 18.8496; // 3 coils
3172    float h = t / 6.28318 * 0.4 - 0.9;
3173    vec3 c = vec3(0.5 * cos(t), h, 0.5 * sin(t));
3174    bestD = min(bestD, length(p - c));
3175  }
3176  return bestD - 0.1 - u_roundness * 0.04;
3177}
3178
3179// ---- (3,2) torus knot ----
3180float sdTorusKnot(vec3 p) {
3181  float bestD = 1e10;
3182  for (int i = 0; i < 64; i++) {
3183    float t = float(i) / 64.0 * 6.28318;
3184    float r = 0.6 + 0.25 * cos(3.0 * t);
3185    vec3 c = vec3(r * cos(2.0 * t), 0.25 * sin(3.0 * t), r * sin(2.0 * t));
3186    bestD = min(bestD, length(p - c));
3187  }
3188  return bestD - 0.1 - u_roundness * 0.03;
3189}
3190
3191// ---- Nautilus shell (logarithmic spiral) ----
3192float sdShell(vec3 p) {
3193  float bestD = 1e10;
3194  for (int i = 0; i < 64; i++) {
3195    float t = float(i) / 64.0 * 9.4248; // 1.5 turns
3196    float growthRate = exp(0.18 * t);
3197    float r = 0.08 * growthRate;
3198    float cr = max(r * 0.35, 0.04);
3199    vec3 c = vec3(r * cos(t), t * 0.06 - 0.4, r * sin(t));
3200    bestD = min(bestD, length(p - c) - cr);
3201  }
3202  return bestD;
3203}
3204
3205// ---- 3D cross ----
3206float sdCross(vec3 p) {
3207  vec3 q = abs(p);
3208  float dx = max(q.y, q.z) - 0.3;
3209  float dy = max(q.x, q.z) - 0.3;
3210  float dz = max(q.x, q.y) - 0.3;
3211  float d = min(min(
3212    max(dx, q.x - 1.0),
3213    max(dy, q.y - 1.0)),
3214    max(dz, q.z - 1.0));
3215  return d - u_roundness * 0.08;
3216}
3217
3218// ---- Shape dispatcher ----
3219float evalShape(vec3 p, int shape) {
3220  // Apply twist
3221  if (abs(u_twist) > 0.001) {
3222    p.xz = rot2(p.y * u_twist) * p.xz;
3223  }
3224  p /= max(u_shapeScale, 0.01);
3225
3226  float d;
3227  if      (shape == 0)  d = sdSphere(p);
3228  else if (shape == 1)  d = sdTorus(p);
3229  else if (shape == 2)  d = sdCube(p);
3230  else if (shape == 3)  d = sdOctahedron(p);
3231  else if (shape == 4)  d = sdCylinder(p);
3232  else if (shape == 5)  d = sdCone(p);
3233  else if (shape == 6)  d = sdCapsule(p);
3234  else if (shape == 7)  d = sdKnot(p);
3235  else if (shape == 8)  d = sdMobius(p);
3236  else if (shape == 9)  d = sdHeart(p);
3237  else if (shape == 10) d = sdStar(p);
3238  else if (shape == 11) d = sdGyroid(p);
3239  else if (shape == 12) d = sdDiamond(p);
3240  else if (shape == 13) d = sdTeardrop(p);
3241  else if (shape == 14) d = sdHelix(p);
3242  else if (shape == 15) d = sdPyramid(p);
3243  else if (shape == 16) d = sdSpring(p);
3244  else if (shape == 17) d = sdTorusKnot(p);
3245  else if (shape == 18) d = sdShell(p);
3246  else                  d = sdCross(p);
3247
3248  return d * u_shapeScale;
3249}
3250
3251// ---- Scene SDF ----
3252float sceneSDF(vec3 p) {
3253  float d1 = evalShape(p, u_surfaceShape);
3254
3255  if (u_csgOp > 0) {
3256    float d2 = evalShape(p, u_csgShape);
3257    float k = u_csgBlend;
3258    if      (u_csgOp == 1) d1 = smin(d1, d2, k);  // union
3259    else if (u_csgOp == 2) d1 = smax(d1, -d2, k);  // subtract
3260    else if (u_csgOp == 3) d1 = smax(d1, d2, k);   // intersect
3261  }
3262
3263  return d1;
3264}
3265
3266// ---- Normal via central differences ----
3267vec3 calcNormal(vec3 p) {
3268  const float h = 0.001;
3269  vec2 e = vec2(h, 0.0);
3270  return normalize(vec3(
3271    sceneSDF(p + e.xyy) - sceneSDF(p - e.xyy),
3272    sceneSDF(p + e.yxy) - sceneSDF(p - e.yxy),
3273    sceneSDF(p + e.yyx) - sceneSDF(p - e.yyx)
3274  ));
3275}
3276
3277// ---- Soft shadows (quality-adaptive) ----
3278float softShadow(vec3 ro, vec3 rd, float tMin, float tMax) {
3279  float res = 1.0;
3280  float t = tMin;
3281  float k = u_shadowSoftness;
3282  int maxSteps = u_qualityTier == 0 ? 8 : u_qualityTier == 1 ? 16 : u_qualityTier == 2 ? 24 : 32;
3283  for (int i = 0; i < 32; i++) {
3284    if (i >= maxSteps || t >= tMax) break;
3285    float d = sceneSDF(ro + rd * t);
3286    if (d < 0.001) return 0.0;
3287    res = min(res, k * d / t);
3288    t += clamp(d, 0.02, 0.2);
3289  }
3290  return clamp(res, 0.0, 1.0);
3291}
3292
3293// ---- Ambient occlusion (quality-adaptive) ----
3294float calcAO(vec3 p, vec3 n) {
3295  float occ = 0.0;
3296  float w = 1.0;
3297  int aoSamples = u_qualityTier == 0 ? 2 : u_qualityTier == 1 ? 3 : u_qualityTier == 2 ? 4 : 5;
3298  for (int i = 0; i < 5; i++) {
3299    if (i >= aoSamples) break;
3300    float dist = u_aoRadius * (1.0 + float(i));
3301    float d = sceneSDF(p + n * dist);
3302    occ += (dist - d) * w;
3303    w *= 0.5;
3304  }
3305  return clamp(1.0 - u_aoStrength * occ * 3.0, 0.0, 1.0);
3306}
3307
3308// ---- PBR Functions (Cook-Torrance BRDF, ported from metallic-3d) ----
3309#define PI 3.14159265359
3310
3311// GGX Normal Distribution Function
3312float D_GGX(float NdotH, float roughness) {
3313  float a = roughness * roughness;
3314  float a2 = a * a;
3315  float NdotH2 = NdotH * NdotH;
3316  float denom = NdotH2 * (a2 - 1.0) + 1.0;
3317  return a2 / (PI * denom * denom + 0.0001);
3318}
3319
3320// Anisotropic GGX (Burley/Disney)
3321float D_GGX_Aniso(float NdotH, float TdotH, float BdotH, float ax, float ay) {
3322  float a2 = ax * ay;
3323  vec3 v = vec3(ay * TdotH, ax * BdotH, a2 * NdotH);
3324  float v2 = dot(v, v);
3325  float w2 = a2 / max(v2, 1e-7);
3326  return a2 * w2 * w2 / PI;
3327}
3328
3329// Smith GGX Geometry (height-correlated)
3330float G_Smith(float NdotV, float NdotL, float roughness) {
3331  float a = roughness * roughness;
3332  float a2 = a * a;
3333  float ggxV = NdotL * sqrt(NdotV * NdotV * (1.0 - a2) + a2);
3334  float ggxL = NdotV * sqrt(NdotL * NdotL * (1.0 - a2) + a2);
3335  return 0.5 / max(ggxV + ggxL, 0.001);
3336}
3337
3338// Schlick Fresnel with vec3 F0 (metals have colored F0)
3339vec3 F_Schlick(float cosTheta, vec3 F0) {
3340  return F0 + (vec3(1.0) - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
3341}
3342
3343// Procedural environment map for PBR reflections
3344vec3 proceduralEnv(vec3 dir, float roughness) {
3345  // Apply environment rotation around Y axis
3346  float rotAngle = u_envRotation * 3.14159 / 180.0;
3347  float cr = cos(rotAngle), sr = sin(rotAngle);
3348  dir = vec3(cr * dir.x + sr * dir.z, dir.y, -sr * dir.x + cr * dir.z);
3349
3350  // Use real HDRI cubemap when available
3351  if (u_useEnvMap == 1) {
3352    vec3 hdriColor = texture(u_envMap, dir).rgb;
3353    // Poor-man's roughness blur: sample nearby directions and average
3354    vec3 blurred = texture(u_envMap, normalize(dir + vec3(0.2, 0.1, -0.15))).rgb;
3355    blurred += texture(u_envMap, normalize(dir + vec3(-0.15, 0.2, 0.1))).rgb;
3356    blurred += texture(u_envMap, normalize(dir + vec3(0.1, -0.15, 0.2))).rgb;
3357    blurred *= 0.333;
3358    vec3 env = mix(hdriColor, blurred, roughness * 0.7);
3359    return env * u_envIntensity;
3360  }
3361
3362  float yFactor = dir.y * 0.5 + 0.5;
3363
3364  // Sky gradient
3365  vec3 sky = mix(vec3(0.01, 0.01, 0.02), vec3(0.4, 0.5, 0.7), pow(yFactor, 0.6));
3366
3367  // Ground
3368  vec3 ground = mix(vec3(0.02, 0.02, 0.03), vec3(0.1, 0.08, 0.06), max(-dir.y, 0.0));
3369
3370  // Horizon glow
3371  float horizon = pow(1.0 - abs(dir.y), 12.0) * 0.6;
3372
3373  // Sun highlight (sharp for low roughness, soft for high)
3374  vec3 sunDir = normalize(vec3(u_lightX, u_lightY, u_lightZ));
3375  float sunDot = max(dot(dir, sunDir), 0.0);
3376  float sunPow = mix(512.0, 4.0, roughness);
3377  float sunSpec = pow(sunDot, sunPow);
3378  vec3 sunColor = vec3(1.0, 0.95, 0.85) * sunSpec * mix(3.0, 0.5, roughness);
3379
3380  // Secondary light (rim fill from opposite side)
3381  vec3 rimDir = normalize(vec3(-u_lightX, u_lightY * 0.5, -u_lightZ));
3382  float rimDot = max(dot(dir, rimDir), 0.0);
3383  float rimSpec = pow(rimDot, mix(128.0, 4.0, roughness)) * 0.3;
3384
3385  vec3 env = mix(ground, sky, step(0.0, dir.y));
3386  env += horizon;
3387  env += sunColor;
3388  env += vec3(0.7, 0.8, 1.0) * rimSpec;
3389
3390  // Soften based on roughness (blur approximation)
3391  env = mix(env, vec3(dot(env, vec3(0.2126, 0.7152, 0.0722))), roughness * 0.5);
3392
3393  return env;
3394}
3395
3396// ---- Gradient mapping ----
3397float gradientT(vec3 p, vec3 n, vec3 viewDir, float rayDist) {
3398  float t;
3399  if (u_gradientMapping == 0) {
3400    // Position: map along world Y
3401    t = p.y * 0.5 + 0.5;
3402  } else if (u_gradientMapping == 1) {
3403    // Normal: dot with up
3404    t = dot(n, vec3(0.0, 1.0, 0.0)) * 0.5 + 0.5;
3405  } else if (u_gradientMapping == 2) {
3406    // Fresnel
3407    t = 1.0 - abs(dot(n, viewDir));
3408  } else if (u_gradientMapping == 3) {
3409    // UV: spherical theta/phi
3410    float theta = atan(p.z, p.x) / 6.28318 + 0.5;
3411    float phi = asin(clamp(p.y / max(length(p), 0.001), -1.0, 1.0)) / 3.14159 + 0.5;
3412    t = mix(theta, phi, 0.5);
3413  } else if (u_gradientMapping == 4) {
3414    // Distance from camera
3415    t = rayDist / 10.0;
3416  } else {
3417    // Metallic: iridescent color shift from specular + fresnel + environment
3418    vec3 l = normalize(vec3(u_lightX, u_lightY, u_lightZ) - p);
3419    vec3 h = normalize(l + viewDir);
3420    float spec = pow(max(dot(n, h), 0.0), 32.0);
3421    float fres = pow(1.0 - max(dot(n, viewDir), 0.0), 3.0);
3422    vec3 refl = reflect(-viewDir, n);
3423    float env = refl.y * 0.5 + 0.5;
3424    t = mix(mix(fres, spec, 0.4), env, 0.3);
3425  }
3426  return clamp(t * u_gradientScale, 0.0, 1.0);
3427}
3428
3429void main() {
3430  float time = u_time * u_speed;
3431  float aspect = u_resolution.x / max(u_resolution.y, 1.0);
3432
3433  // Camera setup
3434  float yaw = radians(u_cameraYaw);
3435  float pitch = radians(u_cameraPitch);
3436  if (u_autoRotate > 0) {
3437    yaw += time * u_autoRotateSpeed * 0.5;
3438  }
3439
3440  vec3 camPos = vec3(
3441    u_cameraDistance * cos(pitch) * sin(yaw),
3442    u_cameraDistance * sin(pitch),
3443    u_cameraDistance * cos(pitch) * cos(yaw)
3444  );
3445  vec3 target = vec3(0.0);
3446  vec3 forward = normalize(target - camPos);
3447  vec3 worldUp = vec3(0.0, 1.0, 0.0);
3448  vec3 right = normalize(cross(forward, worldUp));
3449  vec3 up = cross(right, forward);
3450
3451  // Ray direction from UV + FOV
3452  float fovScale = tan(radians(u_cameraFov) * 0.5);
3453  vec2 warpedUv = applyMouseWarp(vUv, aspect);
3454  vec2 screenUV = (warpedUv - 0.5) * 2.0;
3455  screenUV.x *= aspect;
3456  vec3 rd = normalize(forward + screenUV.x * fovScale * right + screenUV.y * fovScale * up);
3457
3458  // Ray march (quality-adaptive steps)
3459  int maxSteps = u_qualityTier == 0 ? 40 : u_qualityTier == 1 ? 60 : u_qualityTier == 2 ? 80 : 100;
3460  float t = 0.0;
3461  float d;
3462  bool hit = false;
3463  for (int i = 0; i < 100; i++) {
3464    if (i >= maxSteps) break;
3465    vec3 p = camPos + rd * t;
3466    d = sceneSDF(p);
3467    if (d < 0.001) { hit = true; break; }
3468    if (t > 20.0) break;
3469    t += d;
3470  }
3471
3472  vec3 color;
3473
3474  if (hit) {
3475    vec3 p = camPos + rd * t;
3476    vec3 n = calcNormal(p);
3477    vec3 viewDir = -rd;
3478
3479    // Light direction
3480    vec3 lightPos = vec3(u_lightX, u_lightY, u_lightZ);
3481    vec3 l = normalize(lightPos - p);
3482    vec3 h = normalize(l + viewDir);
3483
3484    // Gradient color from mapping
3485    float gT = gradientT(p, n, viewDir, t);
3486
3487    // Image-driven gradient integration
3488    float imgLum = imageLuminance(gl_FragCoord.xy / u_resolution.xy, aspect);
3489    if (imgLum >= 0.0) {
3490      gT = mix(gT, imgLum, u_imageIntensity);
3491    }
3492
3493    vec3 albedo = mixColors(u_colors, u_colorPositions, u_colorCount, gT);
3494
3495    // Soft shadows
3496    float shadow = softShadow(p + n * 0.01, l, 0.02, 10.0);
3497
3498    // Ambient occlusion
3499    float ao = calcAO(p, n);
3500
3501    if (u_pbrMetallic > 0.001) {
3502      // ---- Cook-Torrance PBR lighting path ----
3503      float NdotV = max(dot(n, viewDir), 0.001);
3504      float NdotL = max(dot(n, l), 0.0);
3505      float NdotH = max(dot(n, h), 0.0);
3506      float VdotH = max(dot(h, viewDir), 0.0);
3507
3508      float roughness = max(u_pbrRoughness, 0.04);
3509      vec3 F0 = mix(vec3(0.04), albedo, u_pbrMetallic);
3510
3511      // Normal Distribution
3512      float D;
3513      if (u_pbrAnisotropy > 0.001) {
3514        vec3 T = normalize(cross(n, abs(n.y) < 0.999 ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0)));
3515        vec3 B = cross(n, T);
3516        float anisAspect = sqrt(1.0 - 0.9 * u_pbrAnisotropy);
3517        float ax = roughness / anisAspect;
3518        float ay = roughness * anisAspect;
3519        float TdotH = dot(T, h);
3520        float BdotH = dot(B, h);
3521        D = D_GGX_Aniso(NdotH, TdotH, BdotH, ax, ay);
3522      } else {
3523        D = D_GGX(NdotH, roughness);
3524      }
3525
3526      // Geometry
3527      float G = G_Smith(NdotV, NdotL, roughness);
3528
3529      // Fresnel
3530      vec3 F = F_Schlick(VdotH, F0);
3531
3532      // Specular BRDF
3533      vec3 specular = (D * G) * F;
3534
3535      // Energy conservation
3536      vec3 kD = (vec3(1.0) - F) * (1.0 - u_pbrMetallic);
3537      vec3 diffusePBR = kD * albedo / PI;
3538
3539      // Direct lighting (primary directional)
3540      vec3 Lo = (diffusePBR + specular) * NdotL * shadow;
3541
3542      // ---- Point light contributions ----
3543      for (int pl = 0; pl < 4; pl++) {
3544        if (pl >= u_numPointLights) break;
3545        vec3 plDir = u_pointLights[pl] - p;
3546        float plDist = length(plDir);
3547        plDir /= plDist;
3548        float plAtten = 1.0 / (1.0 + plDist * plDist * 0.1);
3549
3550        // Shadow ray from point light
3551        float plShadow = softShadow(p + n * 0.01, plDir, 0.02, plDist);
3552
3553        float plNdotL = max(dot(n, plDir), 0.0);
3554        vec3 plH = normalize(plDir + viewDir);
3555        float plNdotH = max(dot(n, plH), 0.0);
3556        float plVdotH = max(dot(plH, viewDir), 0.0);
3557
3558        float plD = D_GGX(plNdotH, roughness);
3559        float plG = G_Smith(NdotV, plNdotL, roughness);
3560        vec3 plF = F_Schlick(plVdotH, F0);
3561        vec3 plSpec = (plD * plG) * plF;
3562        vec3 plKd = (vec3(1.0) - plF) * (1.0 - u_pbrMetallic);
3563
3564        Lo += (plKd * albedo / PI + plSpec) * plNdotL * plAtten * u_pointLightColors[pl] * plShadow;
3565      }
3566
3567      // Environment reflections
3568      vec3 reflDir = reflect(-viewDir, n);
3569      vec3 envSpec = proceduralEnv(reflDir, roughness);
3570      vec3 envF = F_Schlick(NdotV, F0);
3571      vec3 envReflection = envSpec * envF;
3572
3573      // Diffuse IBL
3574      vec3 envDiffuse = proceduralEnv(n, 1.0) * kD * albedo;
3575
3576      // Combine
3577      vec3 ambientPBR = albedo * u_ambient * ao;
3578      color = ambientPBR + Lo + (envReflection + envDiffuse) * ao;
3579    } else {
3580      // ---- Classic Blinn-Phong lighting path ----
3581      float diff = max(dot(n, l), 0.0) * u_diffuse;
3582      float spec = pow(max(dot(n, h), 0.0), u_specularPower) * u_specularStrength;
3583      float fres = u_fresnel * pow(1.0 - max(dot(n, viewDir), 0.0), 5.0);
3584
3585      // Combine lighting
3586      vec3 ambient = albedo * u_ambient;
3587      vec3 diffCol = albedo * diff * shadow;
3588      vec3 specCol = vec3(spec) * shadow;
3589      vec3 fresnelCol = mix(albedo, vec3(1.0), 0.5) * fres;
3590
3591      // ---- Point light contributions (Blinn-Phong) ----
3592      for (int pl = 0; pl < 4; pl++) {
3593        if (pl >= u_numPointLights) break;
3594        vec3 plDir = u_pointLights[pl] - p;
3595        float plDist = length(plDir);
3596        plDir /= plDist;
3597        float plAtten = 1.0 / (1.0 + plDist * plDist * 0.1);
3598        float plShadow = softShadow(p + n * 0.01, plDir, 0.02, plDist);
3599        float plDiff = max(dot(n, plDir), 0.0) * u_diffuse;
3600        vec3 plH = normalize(plDir + viewDir);
3601        float plSpec = pow(max(dot(n, plH), 0.0), u_specularPower) * u_specularStrength;
3602        diffCol += albedo * plDiff * plAtten * u_pointLightColors[pl] * plShadow;
3603        specCol += vec3(plSpec) * plAtten * u_pointLightColors[pl] * plShadow;
3604      }
3605
3606      // Reflectivity: blend in a reflection-like highlight using the environment
3607      vec3 reflDir = reflect(-viewDir, n);
3608      float envRefl = max(dot(reflDir, vec3(0.0, 1.0, 0.0)) * 0.5 + 0.5, 0.0
3608);
3609      vec3 reflCol = mixColors(u_colors, u_colorPositions, u_colorCount, envRefl) * u_reflectivity * fres;
3610
3611      color = (ambient + diffCol + specCol + fresnelCol + reflCol) * ao;
3612    }
3613
3614    // Grain — only inside shape
3615    color += grain(vUv, time, u_grain * 0.75);
3616
3617    // Self-blend — only inside shape
3618    if (u_blendMode > 0) {
3619      float bRotation = u_blendAnimRotation > 0 ? animatedBlendRotation(u_blendRotation, time, u_blendAnimSpeed) : u_blendRotation;
3620      float bOffset = u_blendAnimOffset > 0 ? animatedBlendOffset(u_blendOffset, time, u_blendAnimSpeed) : u_blendOffset;
3621      float bIntensity = u_blendAnimIntensity > 0 ? animatedBlendIntensity(u_blendIntensity, time, u_blendAnimSpeed) : u_blendIntensity;
3622      bIntensity = noiseModulatedIntensity(bIntensity, vUv, time, u_blendNoiseMask, u_blendNoiseScale);
3623      bIntensity = applyBlendFalloff(bIntensity, vUv.x, u_blendFalloff);
3624      float tB = blendLayerT(vUv, 0.5, u_resolution, u_direction, bOffset, bRotation, u_blendScale);
3625      vec3 blendCol = mixColors(u_colors, u_colorPositions, u_colorCount, tB);
3626      color = applyBlendMode(u_blendMode, color, blendCol, bIntensity);
3627      if (u_blendDualLayer > 0 && u_blendMode2 > 0) {
3628        float tB2 = blendLayerT(vUv, 0.5, u_resolution, u_direction, u_blendOffset2, u_blendRotation2, u_blendScale2);
3629        vec3 blendCol2 = mixColors(u_colors, u_colorPositions, u_colorCount, tB2);
3630        color = applyBlendMode(u_blendMode2, color, blendCol2, u_blendIntensity2);
3631      }
3632    }
3633
3634    fragColor = outputColor(color, gl_FragCoord.xy, 1.1);
3635  } else {
3636    // Background — transparent for export, dark for viewport
3637    if (u_transparentBg == 1) {
3638      fragColor = vec4(0.0);
3639    } else {
3640      float vignette = 1.0 - 0.3 * length(vUv - 0.5);
3641      vec3 bgColor = vec3(0.03 * u_bgGradient * vignette);
3642      fragColor = outputColor(bgColor, gl_FragCoord.xy, 1.1);
3643    }
3644  }
3645}
3646`,b=`#version 300 es
3647precision highp float;
3648
3649in vec2 vUv;
3650out vec4 fragColor;
3651
3652uniform float u_time;
3653uniform float u_speed;
3654uniform vec3 u_colors[12];
3655uniform float u_colorPositions[12];
3656uniform int u_colorCount;
3657uniform float u_direction;
3658uniform float u_grain;
3659uniform vec2 u_resolution;
3660
3661// Blend uniforms
3662uniform int u_blendMode;
3663uniform float u_blendIntensity;
3664uniform float u_blendOffset;
3665uniform float u_blendRotation;
3666uniform float u_blendScale;
3667uniform int u_blendAnimRotation;
3668uniform int u_blendAnimIntensity;
3669uniform int u_blendAnimOffset;
3670uniform float u_blendAnimSpeed;
3671uniform float u_blendNoiseMask;
3672uniform float u_blendNoiseScale;
3673uniform float u_blendFalloff;
3674uniform int u_blendDualLayer;
3675uniform int u_blendMode2;
3676uniform float u_blendIntensity2;
3677uniform float u_blendOffset2;
3678uniform float u_blendRotation2;
3679uniform float u_blendScale2;
3680
3681// Shape / camera / CSG uniforms (shared with surfaces3d)
3682uniform int u_surfaceShape;
3683uniform float u_shapeScale;
3684uniform float u_roundness;
3685uniform float u_twist;
3686uniform float u_cameraDistance;
3687uniform float u_cameraPitch;
3688uniform float u_cameraYaw;
3689uniform float u_cameraFov;
3690uniform int u_autoRotate;
3691uniform float u_autoRotateSpeed;
3692
3693// PBR Material
3694uniform float u_metallic;
3695uniform float u_roughness;
3696uniform float u_anisotropy;
3697uniform float u_anisotropyAngle;
3698
3699// Coating
3700uniform float u_iridescence;
3701uniform float u_iridescenceIOR;
3702uniform float u_clearcoat;
3703uniform float u_clearcoatRoughness;
3704
3705// Environment
3706uniform float u_envIntensity;
3707uniform float u_envRotation;
3708uniform samplerCube u_envMap;
3709uniform int u_useEnvMap;
3710
3711// Lighting
3712uniform float u_lightX;
3713uniform float u_lightY;
3714uniform float u_lightZ;
3715uniform float u_ambient;
3716uniform float u_shadowSoftness;
3717uniform float u_aoStrength;
3718uniform float u_aoRadius;
3719
3720// Background
3721uniform float u_bgGradient;
3722uniform int u_transparentBg;
3723
3724// Flow (curl noise animation)
3725uniform float u_flowIntensity;
3726uniform float u_flowSpeed;
3727uniform float u_flowScale;
3728
3729// CSG
3730uniform int u_csgOp;
3731uniform int u_csgShape;
3732
3733// Quality tier: 0=low, 1=medium, 2=high, 3=ultra
3734uniform int u_qualityTier;
3735uniform float u_csgBlend;
3736
3737__COMMON__
3738
3739// ---- Rotation helpers ----
3740mat3 rotX(float a) {
3741  float s = sin(a), c = cos(a);
3742  return mat3(1.0, 0.0, 0.0, 0.0, c, -s, 0.0, s, c);
3743}
3744mat3 rotY(float a) {
3745  float s = sin(a), c = cos(a);
3746  return mat3(c, 0.0, s, 0.0, 1.0, 0.0, -s, 0.0, c);
3747}
3748mat2 rot2(float a) {
3749  float s = sin(a), c = cos(a);
3750  return mat2(c, -s, s, c);
3751}
3752
3753// ---- SDF primitives ----
3754float sdSphere(vec3 p) {
3755  return length(p) - 1.0;
3756}
3757
3758float sdTorus(vec3 p) {
3759  vec2 q = vec2(length(p.xz) - 0.6, p.y);
3760  return length(q) - 0.4;
3761}
3762
3763float sdBox(vec3 p, vec3 b) {
3764  vec3 q = abs(p) - b;
3765  return length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0);
3766}
3767
3768float sdCube(vec3 p) {
3769  return sdBox(p, vec3(0.8)) - u_roundness * 0.2;
3770}
3771
3772float sdOctahedron(vec3 p) {
3773  p = abs(p);
3774  float m = p.x + p.y + p.z - 1.0;
3775  vec3 q;
3776  if (3.0 * p.x < m) q = p.xyz;
3777  else if (3.0 * p.y < m) q = p.yzx;
3778  else if (3.0 * p.z < m) q = p.zxy;
3779  else return m * 0.57735027;
3780  float k = clamp(0.5 * (q.z - q.y + 1.0), 0.0, 1.0);
3781  return length(vec3(q.x, q.y - 1.0 + k, q.z - k));
3782}
3783
3784float sdCylinder(vec3 p) {
3785  vec2 d = abs(vec2(length(p.xz), p.y)) - vec2(0.6, 1.0);
3786  return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - u_roundness * 0.1;
3787}
3788
3789float sdCone(vec3 p) {
3790  float q = length(p.xz);
3791  return max(dot(vec2(0.6, 0.8), vec2(q, -p.y)), p.y - 1.0);
3792}
3793
3794float sdCapsule(vec3 p) {
3795  p.y -= clamp(p.y, -0.8, 0.8);
3796  return length(p) - 0.5;
3797}
3798
3799float sdKnot(vec3 p) {
3800  // Trefoil knot approximation via closest distance to parametric curve
3801  float bestD = 1e10;
3802  for (int i = 0; i < 48; i++) {
3803    float t = float(i) / 48.0 * 6.28318;
3804    float r = 0.6 + 0.3 * cos(3.0 * t);
3805    vec3 c = vec3(r * cos(2.0 * t), 0.3 * sin(3.0 * t), r * sin(2.0 * t));
3806    bestD = min(bestD, length(p - c));
3807  }
3808  return bestD - 0.15;
3809}
3810
3811float sdMobius(vec3 p) {
3812  // Mobius strip approximation
3813  float a = atan(p.z, p.x);
3814  float r = length(p.xz) - 0.7;
3815  float ha = a * 0.5;
3816  float cosHa = cos(ha), sinHa = sin(ha);
3817  float y2 = p.y * cosHa - r * sinHa;
3818  float r2 = p.y * sinHa + r * cosHa;
3819  return length(vec2(r2, y2)) - 0.15;
3820}
3821
3822float sdStar(vec3 p) {
3823  // Extruded 5-pointed star
3824  float d2 = abs(p.y) - 0.3;
3825  vec2 q = p.xz;
3826  float a = atan(q.y, q.x);
3827  float r = length(q);
3828  float star = r - 0.6 + 0.3 * cos(5.0 * a);
3829  return max(star, d2) - u_roundness * 0.05;
3830}
3831
3832float sdHeart(vec3 p) {
3833  p.y -= 0.2;
3834  p *= 1.2;
3835  float x2 = p.x * p.x;
3836  float y = p.y;
3837  float z2 = p.z * p.z;
3838  float cube = x2 + 2.25 * y * y + z2 - 1.0;
3839  return (cube * cube * cube - (x2 + 0.075 * z2) * y * y * y) * 0.5;
3840}
3841
3842float sdGyroid(vec3 p) {
3843  p *= 3.14159;
3844  return (sin(p.x) * cos(p.y) + sin(p.y) * cos(p.z) + sin(p.z) * cos(p.x)) / 3.14159 * 0.35;
3845}
3846
3847// ---- Smooth CSG operators ----
3848float smin(float a, float b, float k) {
3849  float h = clamp(0.5 + 0.5 * (b - a) / max(k, 0.001), 0.0, 1.0);
3850  return mix(b, a, h) - k * h * (1.0 - h);
3851}
3852
3853float smax(float a, float b, float k) {
3854  return -smin(-a, -b, k);
3855}
3856
3857// ---- Diamond / faceted gem ----
3858float sdDiamond(vec3 p) {
3859  float top = (abs(p.x) + abs(p.z)) * 0.707 + p.y * 0.8 - 0.5;
3860  float bottom = (abs(p.x) + abs(p.z)) * 0.707 - p.y * 0.5 - 0.7;
3861  return max(top, bottom) - u_roundness * 0.05;
3862}
3863
3864// ---- Teardrop / water drop ----
3865float sdTeardrop(vec3 p) {
3866  vec3 q = p;
3867  q.y -= 0.2;
3868  float body = length(q) - 0.7;
3869  float tip = length(vec3(q.x * 0.5, q.y - 0.7, q.z * 0.5)) - 0.15;
3870  return smin(body, tip, 0.4);
3871}
3872
3873// ---- Double helix / DNA ----
3874float sdHelix(vec3 p) {
3875  float bestD = 1e10;
3876  for (int i = 0; i < 64; i++) {
3877    float t = float(i) / 64.0 * 12.566; // 2 full turns
3878    float h = t / 6.283 - 1.0;
3879    vec3 c1 = vec3(0.5 * cos(t), h, 0.5 * sin(t));
3880    vec3 c2 = vec3(0.5 * cos(t + 3.14159), h, 0.5 * sin(t + 3.14159));
3881    bestD = min(bestD, min(length(p - c1), length(p - c2)));
3882  }
3883  return bestD - 0.09 - u_roundness * 0.04;
3884}
3885
3886// ---- Pyramid (square base) ----
3887float sdPyramid(vec3 p) {
3888  float h = 1.2;
3889  vec3 q = abs(p);
3890  float base = max(q.x, q.z) - 0.8 + p.y * 0.65;
3891  float cap = p.y - h * 0.5;
3892  float floor_ = -p.y - h * 0.3;
3893  return max(max(base, cap), floor_) - u_roundness * 0.05;
3894}
3895
3896// ---- Helical spring / coil ----
3897float sdSpring(vec3 p) {
3898  float bestD = 1e10;
3899  for (int i = 0; i < 48; i++) {
3900    float t = float(i) / 48.0 * 18.8496; // 3 coils
3901    float h = t / 6.28318 * 0.4 - 0.9;
3902    vec3 c = vec3(0.5 * cos(t), h, 0.5 * sin(t));
3903    bestD = min(bestD, length(p - c));
3904  }
3905  return bestD - 0.1 - u_roundness * 0.04;
3906}
3907
3908// ---- (3,2) torus knot ----
3909float sdTorusKnot(vec3 p) {
3910  float bestD = 1e10;
3911  for (int i = 0; i < 64; i++) {
3912    float t = float(i) / 64.0 * 6.28318;
3913    float r = 0.6 + 0.25 * cos(3.0 * t);
3914    vec3 c = vec3(r * cos(2.0 * t), 0.25 * sin(3.0 * t), r * sin(2.0 * t));
3915    bestD = min(bestD, length(p - c));
3916  }
3917  return bestD - 0.1 - u_roundness * 0.03;
3918}
3919
3920// ---- Nautilus shell (logarithmic spiral) ----
3921float sdShell(vec3 p) {
3922  float bestD = 1e10;
3923  for (int i = 0; i < 64; i++) {
3924    float t = float(i) / 64.0 * 9.4248; // 1.5 turns
3925    float growthRate = exp(0.18 * t);
3926    float r = 0.08 * growthRate;
3927    float cr = max(r * 0.35, 0.04);
3928    vec3 c = vec3(r * cos(t), t * 0.06 - 0.4, r * sin(t));
3929    bestD = min(bestD, length(p - c) - cr);
3930  }
3931  return bestD;
3932}
3933
3934// ---- 3D cross ----
3935float sdCross(vec3 p) {
3936  vec3 q = abs(p);
3937  float dx = max(q.y, q.z) - 0.3;
3938  float dy = max(q.x, q.z) - 0.3;
3939  float dz = max(q.x, q.y) - 0.3;
3940  float d = min(min(
3941    max(dx, q.x - 1.0),
3942    max(dy, q.y - 1.0)),
3943    max(dz, q.z - 1.0));
3944  return d - u_roundness * 0.08;
3945}
3946
3947// ---- Shape dispatcher ----
3948float evalShape(vec3 p, int shape) {
3949  // Apply twist
3950  if (abs(u_twist) > 0.001) {
3951    p.xz = rot2(p.y * u_twist) * p.xz;
3952  }
3953  p /= max(u_shapeScale, 0.01);
3954
3955  float d;
3956  if      (shape == 0)  d = sdSphere(p);
3957  else if (shape == 1)  d = sdTorus(p);
3958  else if (shape == 2)  d = sdCube(p);
3959  else if (shape == 3)  d = sdOctahedron(p);
3960  else if (shape == 4)  d = sdCylinder(p);
3961  else if (shape == 5)  d = sdCone(p);
3962  else if (shape == 6)  d = sdCapsule(p);
3963  else if (shape == 7)  d = sdKnot(p);
3964  else if (shape == 8)  d = sdMobius(p);
3965  else if (shape == 9)  d = sdHeart(p);
3966  else if (shape == 10) d = sdStar(p);
3967  else if (shape == 11) d = sdGyroid(p);
3968  else if (shape == 12) d = sdDiamond(p);
3969  else if (shape == 13) d = sdTeardrop(p);
3970  else if (shape == 14) d = sdHelix(p);
3971  else if (shape == 15) d = sdPyramid(p);
3972  else if (shape == 16) d = sdSpring(p);
3973  else if (shape == 17) d = sdTorusKnot(p);
3974  else if (shape == 18) d = sdShell(p);
3975  else                  d = sdCross(p);
3976
3977  return d * u_shapeScale;
3978}
3979
3980// ---- Scene SDF ----
3981float sceneSDF(vec3 p) {
3982  float d1 = evalShape(p, u_surfaceShape);
3983
3984  if (u_csgOp > 0) {
3985    float d2 = evalShape(p, u_csgShape);
3986    float k = u_csgBlend;
3987    if      (u_csgOp == 1) d1 = smin(d1, d2, k);  // union
3988    else if (u_csgOp == 2) d1 = smax(d1, -d2, k);  // subtract
3989    else if (u_csgOp == 3) d1 = smax(d1, d2, k);   // intersect
3990  }
3991
3992  return d1;
3993}
3994
3995// ---- Normal via central differences ----
3996vec3 calcNormal(vec3 p) {
3997  const float h = 0.001;
3998  vec2 e = vec2(h, 0.0);
3999  return normalize(vec3(
4000    sceneSDF(p + e.xyy) - sceneSDF(p - e.xyy),
4001    sceneSDF(p + e.yxy) - sceneSDF(p - e.yxy),
4002    sceneSDF(p + e.yyx) - sceneSDF(p - e.yyx)
4003  ));
4004}
4005
4006// ---- Soft shadows (quality-adaptive) ----
4007float softShadow(vec3 ro, vec3 rd, float tMin, float tMax) {
4008  float res = 1.0;
4009  float t = tMin;
4010  float k = u_shadowSoftness;
4011  int maxSteps = u_qualityTier == 0 ? 8 : u_qualityTier == 1 ? 16 : u_qualityTier == 2 ? 24 : 32;
4012  for (int i = 0; i < 32; i++) {
4013    if (i >= maxSteps || t >= tMax) break;
4014    float d = sceneSDF(ro + rd * t);
4015    if (d < 0.001) return 0.0;
4016    res = min(res, k * d / t);
4017    t += clamp(d, 0.02, 0.2);
4018  }
4019  return clamp(res, 0.0, 1.0);
4020}
4021
4022// ---- Ambient occlusion (quality-adaptive) ----
4023float calcAO(vec3 p, vec3 n) {
4024  float occ = 0.0;
4025  float w = 1.0;
4026  int aoSamples = u_qualityTier == 0 ? 2 : u_qualityTier == 1 ? 3 : u_qualityTier == 2 ? 4 : 5;
4027  for (int i = 0; i < 5; i++) {
4028    if (i >= aoSamples) break;
4029    float dist = u_aoRadius * (1.0 + float(i));
4030    float d = sceneSDF(p + n * dist);
4031    occ += (dist - d) * w;
4032    w *= 0.5;
4033  }
4034  return clamp(1.0 - u_aoStrength * occ * 3.0, 0.0, 1.0);
4035}
4036
4037// ---- Simplex 3D noise (Ashima / Stefan Gustavson) ----
4038vec4 mod289_4(vec4 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
4039vec3 mod289_3n(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
4040vec4 permute4(vec4 x) { return mod289_4(((x * 34.0) + 1.0) * x); }
4041vec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; }
4042
4043float snoise(vec3 v) {
4044  const vec2 C = vec2(1.0 / 6.0, 1.0 / 3.0);
4045  const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);
4046  vec3 i  = floor(v + dot(v, C.yyy));
4047  vec3 x0 = v - i + dot(i, C.xxx);
4048  vec3 g = step(x0.yzx, x0.xyz);
4049  vec3 l = 1.0 - g;
4050  vec3 i1 = min(g.xyz, l.zxy);
4051  vec3 i2 = max(g.xyz, l.zxy);
4052  vec3 x1 = x0 - i1 + C.xxx;
4053  vec3 x2 = x0 - i2 + C.yyy;
4054  vec3 x3 = x0 - D.yyy;
4055  i = mod289_3n(i);
4056  vec4 p = permute4(permute4(permute4(
4057    i.z + vec4(0.0, i1.z, i2.z, 1.0))
4058  + i.y + vec4(0.0, i1.y, i2.y, 1.0))
4059  + i.x + vec4(0.0, i1.x, i2.x, 1.0));
4060  float n_ = 0.142857142857;
4061  vec3 ns = n_ * D.wyz - D.xzx;
4062  vec4 j = p - 49.0 * floor(p * ns.z * ns.z);
4063  vec4 x_ = floor(j * ns.z);
4064  vec4 y_ = floor(j - 7.0 * x_);
4065  vec4 x = x_ * ns.x + ns.yyyy;
4066  vec4 y = y_ * ns.x + ns.yyyy;
4067  vec4 h = 1.0 - abs(x) - abs(y);
4068  vec4 b0 = vec4(x.xy, y.xy);
4069  vec4 b1 = vec4(x.zw, y.zw);
4070  vec4 s0 = floor(b0) * 2.0 + 1.0;
4071  vec4 s1 = floor(b1) * 2.0 + 1.0;
4072  vec4 sh = -step(h, vec4(0.0));
4073  vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy;
4074  vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww;
4075  vec3 p0 = vec3(a0.xy, h.x);
4076  vec3 p1 = vec3(a0.zw, h.y);
4077  vec3 p2 = vec3(a1.xy, h.z);
4078  vec3 p3 = vec3(a1.zw, h.w);
4079  vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2,p2), dot(p3,p3)));
4080  p0 *= norm.x; p1 *= norm.y; p2 *= norm.z; p3 *= norm.w;
4081  vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
4082  m = m * m;
4083  return 42.0 * dot(m * m, vec4(dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3)));
4084}
4085
4086// ---- Curl noise flow field ----
4087vec3 curlNoise(vec3 p) {
4088  const float e = 0.001;
4089  vec3 dx = vec3(e, 0.0, 0.0);
4090  vec3 dy = vec3(0.0, e, 0.0);
4091  vec3 dz = vec3(0.0, 0.0, e);
4092
4093  float x = snoise(p + dy) - snoise(p - dy)
4094          - snoise(p + dz) + snoise(p - dz);
4095  float y = snoise(p + dz) - snoise(p - dz)
4096          - snoise(p + dx) + snoise(p - dx);
4097  float z = snoise(p + dx) - snoise(p - dx)
4098          - snoise(p + dy) + snoise(p - dy);
4099
4100  return vec3(x, y, z) / (2.0 * e);
4101}
4102
4103vec3 perturbNormal(vec3 n, vec3 p, float time) {
4104  vec3 flow = curlNoise(p * u_flowScale + time * u_flowSpeed * 0.1);
4105  float strength = u_flowIntensity;
4106
4107  // Build tangent frame
4108  vec3 tangent = normalize(cross(n, vec3(0.0, 1.0, 0.0)));
4109  if (length(tangent) < 0.001) tangent = normalize(cross(n, vec3(1.0, 0.0, 0.0)));
4110  vec3 bitangent = cross(n, tangent);
4111
4112  // Perturb normal along tangent plane
4113  n += (tangent * flow.x + bitangent * flow.y) * strength;
4114  return normalize(n);
4115}
4116
4117// ---- PBR Functions ----
4118#define PI 3.14159265359
4119
4120// GGX Normal Distribution Function
4121float D_GGX(float NdotH, float roughness) {
4122  float a = roughness * roughness;
4123  float a2 = a * a;
4124  float NdotH2 = NdotH * NdotH;
4125  float denom = NdotH2 * (a2 - 1.0) + 1.0;
4126  return a2 / (PI * denom * denom + 0.0001);
4127}
4128
4129// Anisotropic GGX (Burley/Disney)
4130float D_GGX_Aniso(float NdotH, float TdotH, float BdotH, float ax, float ay) {
4131  float a2 = ax * ay;
4132  vec3 v = vec3(ay * TdotH, ax * BdotH, a2 * NdotH);
4133  float v2 = dot(v, v);
4134  float w2 = a2 / max(v2, 1e-7);
4135  return a2 * w2 * w2 / PI;
4136}
4137
4138// Smith GGX Geometry (height-correlated)
4139float G_Smith(float NdotV, float NdotL, float roughness) {
4140  float a = roughness * roughness;
4141  float a2 = a * a;
4142  float ggxV = NdotL * sqrt(NdotV * NdotV * (1.0 - a2) + a2);
4143  float ggxL = NdotV * sqrt(NdotL * NdotL * (1.0 - a2) + a2);
4144  return 0.5 / max(ggxV + ggxL, 0.001);
4145}
4146
4147// Schlick Fresnel with vec3 F0 (metals have colored F0)
4148vec3 F_Schlick(float cosTheta, vec3 F0) {
4149  return F0 + (vec3(1.0) - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
4150}
4151
4152// Energy compensation for multiscatter GGX (Kulla-Conty 2017)
4153float E_GGX(float NdotV, float roughness) {
4154  float a = roughness;
4155  float t = 1.0 - NdotV;
4156  return 1.0 - 1.259 * a * t - 0.233 * a * a - 0.0391 * t * t
4157       + 0.423 * a * t * t + 0.137 * a * a * t;
4158}
4159
4160vec3 multiscatterCompensation(vec3 F0, float NdotV, float NdotL, float roughness) {
4161  float Eo = E_GGX(NdotV, roughness);
4162  float Ei = E_GGX(NdotL, roughness);
4163  vec3 Favg = F0 + (1.0 - F0) / 21.0;
4164  vec3 fms = (1.0 - Eo) * (1.0 - Ei) / (PI * (1.0 - Favg * (1.0 - Eo)));
4165  return fms * Favg;
4166}
4167
4168// Thin-film iridescence (simplified interference)
4169vec3 thinFilmIridescence(float cosTheta, float ior, float thickness) {
4170  float delta = 2.0 * ior * thickness * cosTheta;
4171  vec3 wavelengths = vec3(650.0, 510.0, 475.0); // R, G, B
4172  vec3 phase = 2.0 * PI * delta / wavelengths;
4173  return 0.5 + 0.5 * cos(phase);
4174}
4175
4176// Procedural environment map — multi-light studio HDRI approximation
4177// Falls back to procedural when HDRI cubemap is not available
4178vec3 proceduralEnv(vec3 dir, float roughness) {
4179  // Apply environment rotation
4180  float rotRad = radians(u_envRotation);
4181  dir.xz = mat2(cos(rotRad), -sin(rotRad), sin(rotRad), cos(rotRad)) * dir.xz;
4182
4183  // Use real HDRI cubemap when available
4184  if (u_useEnvMap == 1) {
4185    // Sample HDRI at multiple LODs to approximate roughness blur
4186    // textureCube LOD 0 = sharp, higher LODs = blurrier (mipmapped)
4187    vec3 hdriColor = texture(u_envMap, dir).rgb;
4188    // Approximate roughness blur by blending with a heavily averaged sample
4189    vec3 blurred = texture(u_envMap, normalize(dir + vec3(0.2, 0.1, -0.15))).rgb;
4190    blurred += texture(u_envMap, normalize(dir + vec3(-0.15, 0.2, 0.1))).rgb;
4191    blurred += texture(u_envMap, normalize(dir + vec3(0.1, -0.15, 0.2))).rgb;
4192    blurred *= 0.333;
4193    vec3 env = mix(hdriColor, blurred, roughness * 0.7);
4194    return env * u_envIntensity;
4195  }
4196
4197  // Fallback: procedural studio setup with 3 area lights + ambient dome
4198
4199  // Key light (large soft area light, upper-right)
4200  vec3 keyDir = normalize(vec3(1.0, 2.0, 0.5));
4201  float keyAngle = max(dot(dir, keyDir), 0.0);
4202  float keySize = mix(0.3, 0.95, roughness);
4203  vec3 keyLight = vec3(1.2, 1.15, 1.1) * smoothstep(1.0 - keySize, 1.0, keyAngle);
4204
4205  // Fill light (medium area light, left)
4206  vec3 fillDir = normalize(vec3(-1.5, 0.5, 1.0));
4207  float fillAngle = max(dot(dir, fillDir), 0.0);
4208  float fillSize = mix(0.2, 0.8, roughness);
4209  vec3 fillLight = vec3(0.4, 0.45, 0.55) * smoothstep(1.0 - fillSize, 1.0, fillAngle);
4210
4211  // Rim/back light (narrow, behind)
4212  vec3 rimDir = normalize(vec3(0.0, 0.3, -1.5));
4213  float rimAngle = max(dot(dir, rimDir), 0.0);
4214  float rimSize = mix(0.05, 0.4, roughness);
4215  vec3 rimLight = vec3(0.8, 0.85, 1.0) * smoothstep(1.0 - rimSize, 1.0, rimAngle);
4216
4217  // Ambient dome (gradient from dark floor to bright ceiling)
4218  float skyGrad = dir.y * 0.5 + 0.5;
4219  vec3 dome = mix(vec3(0.02, 0.02, 0.03), vec3(0.15, 0.15, 0.18), skyGrad);
4220
4221  // Ground plane reflection (dark with slight warm tint)
4222  vec3 ground = vec3(0.03, 0.025, 0.02) * max(-dir.y, 0.0);
4223
4224  vec3 env = dome + keyLight + fillLight + rimLight + ground;
4225
4226  // Roughness blur (smooth out detail for rough surfaces)
4227  env = mix(env, vec3(dot(env, vec3(0.333))), roughness * 0.4);
4228
4229  return env * u_envIntensity;
4230}
4231
4232void main() {
4233  float time = u_time * u_speed;
4234  float aspect = u_resolution.x / max(u_resolution.y, 1.0);
4235
4236  // Camera setup
4237  float yaw = radians(u_cameraYaw);
4238  float pitch = radians(u_cameraPitch);
4239  if (u_autoRotate > 0) {
4240    yaw += time * u_autoRotateSpeed * 0.5;
4241  }
4242
4243  vec3 camPos = vec3(
4244    u_cameraDistance * cos(pitch) * sin(yaw),
4245    u_cameraDistance * sin(pitch),
4246    u_cameraDistance * cos(pitch) * cos(yaw)
4247  );
4248  vec3 target = vec3(0.0);
4249  vec3 forward = normalize(target - camPos);
4250  vec3 worldUp = vec3(0.0, 1.0, 0.0);
4251  vec3 right = normalize(cross(forward, worldUp));
4252  vec3 up = cross(right, forward);
4253
4254  // Ray direction from UV + FOV
4255  float fovScale = tan(radians(u_cameraFov) * 0.5);
4256  vec2 warpedUv = applyMouseWarp(vUv, aspect);
4257  vec2 screenUV = (warpedUv - 0.5) * 2.0;
4258  screenUV.x *= aspect;
4259  vec3 rd = normalize(forward + screenUV.x * fovScale * right + screenUV.y * fovScale * up);
4260
4261  // Ray march (quality-adaptive steps)
4262  int maxSteps = u_qualityTier == 0 ? 40 : u_qualityTier == 1 ? 60 : u_qualityTier == 2 ? 80 : 100;
4263  float t = 0.0;
4264  float d;
4265  bool hit = false;
4266  for (int i = 0; i < 100; i++) {
4267    if (i >= maxSteps) break;
4268    vec3 p = camPos + rd * t;
4269    d = sceneSDF(p);
4270    if (d < 0.001) { hit = true; break; }
4271    if (t > 20.0) break;
4272    t += d;
4273  }
4274
4275  vec3 color;
4276
4277  if (hit) {
4278    vec3 p = camPos + rd * t;
4279    vec3 n = calcNormal(p);
4280    if (u_flowIntensity > 0.001) {
4281      n = perturbNormal(n, p, time);
4282    }
4283    vec3 viewDir = -rd;
4284
4285    // Light direction
4286    vec3 lightPos = vec3(u_lightX, u_lightY, u_lightZ);
4287    vec3 l = normalize(lightPos - p);
4288    vec3 h = normalize(l + viewDir);
4289
4290    // Dot products
4291    float NdotV = max(dot(n, viewDir), 0.001);
4292    float NdotL = max(dot(n, l), 0.0);
4293    float NdotH = max(dot(n, h), 0.0);
4294    float VdotH = max(dot(h, viewDir), 0.0);
4295
4296    // Gradient color -> albedo
4297    float gT = clamp(dot(n, viewDir) * 0.5 + p.y * 0.3 + 0.5, 0.0, 1.0);
4298
4299    // Image-driven gradient integration
4300    float imgLum = imageLuminance(gl_FragCoord.xy / u_resolution.xy, aspect);
4301    if (imgLum >= 0.0) {
4302      gT = mix(gT, imgLum, u_imageIntensity);
4303    }
4304
4305    vec3 albedo = mixColors(u_colors, u_colorPositions, u_colorCount, gT);
4306
4307    // PBR material
4308    float roughness = max(u_roughness, 0.04);
4309    vec3 F0 = mix(vec3(0.04), albedo, u_metallic);
4310
4311    // Tangent frame for anisotropy
4312    vec3 T = normalize(cross(n, abs(n.y) < 0.999 ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0)));
4313    vec3 B = cross(n, T);
4314
4315    // Rotate tangent by anisotropy angle
4316    if (u_anisotropy > 0.001) {
4317      float aa = radians(u_anisotropyAngle);
4318      float cosA = cos(aa), sinA = sin(aa);
4319      vec3 T2 = T * cosA + B * sinA;
4320      B = -T * sinA + B * cosA;
4321      T = T2;
4322    }
4323
4324    // Normal Distribution
4325    float D;
4326    if (u_anisotropy > 0.001) {
4327      float anisAspect = sqrt(1.0 - 0.9 * u_anisotropy);
4328      float ax = roughness / anisAspect;
4329      float ay = roughness * anisAspect;
4330      float TdotH = dot(T, h);
4331      float BdotH = dot(B, h);
4332      D = D_GGX_Aniso(NdotH, TdotH, BdotH, ax, ay);
4333    } else {
4334      D = D_GGX(NdotH, roughness);
4335    }
4336
4337    // Geometry
4338    float G = G_Smith(NdotV, NdotL, roughness);
4339
4340    // Fresnel
4341    vec3 F = F_Schlick(VdotH, F0);
4342
4343    // Specular BRDF (denominator baked into G_Smith)
4344    vec3 singleScatter = (D * G) * F;
4345    vec3 multiScatter = multiscatterCompensation(F0, NdotV, NdotL, roughness);
4346    vec3 specular = singleScatter + multiScatter;
4347
4348    // Energy conservation
4349    vec3 kD = (vec3(1.0) - F) * (1.0 - u_metallic);
4350    vec3 diffuse = kD * albedo / PI;
4351
4352    // Shadows & AO
4353    float shadow = softShadow(p + n * 0.01, l, 0.02, 10.0);
4354    float ao = calcAO(p, n);
4355
4356    // Direct lighting
4357    vec3 Lo = (diffuse + specular) * NdotL * vec3(1.0) * shadow;
4358
4359    // Environment reflections
4360    vec3 reflDir = reflect(-viewDir, n);
4361    vec3 envSpec = proceduralEnv(reflDir, roughness);
4362    vec3 envF = F_Schlick(NdotV, F0);
4363    vec3 envReflection = envSpec * envF;
4364
4365    // Diffuse IBL
4366    vec3 envDiffuse = proceduralEnv(n, 1.0) * kD * albedo;
4367
4368    // Iridescence
4369    if (u_iridescence > 0.001) {
4370      float thickness = 400.0 + 300.0 * (1.0 - NdotV);
4371      vec3 iriColor = thinFilmIridescence(NdotV, u_iridescenceIOR, thickness);
4372      envReflection = mix(envReflection, envReflection * iriColor * 2.0, u_iridescence);
4373      Lo = mix(Lo, Lo * iriColor, u_iridescence * 0.5);
4374    }
4375
4376    // Clearcoat
4377    if (u_clearcoat > 0.001) {
4378      float ccRough = max(u_clearcoatRoughness, 0.04);
4379      float Dc = D_GGX(NdotH, ccRough);
4380      float Gc = G_Smith(NdotV, NdotL, ccRough);
4381      float Fc = 0.04 + 0.96 * pow(1.0 - VdotH, 5.0);
4382      vec3 ccSpec = vec3(Dc * Gc * Fc);
4383      Lo += ccSpec * u_clearcoat * NdotL * shadow;
4384      envReflection += proceduralEnv(reflDir, ccRough) * Fc * u_clearcoat;
4385    }
4386
4387    // Combine
4388    vec3 ambient = albedo * u_ambient * ao;
4389    color = ambient + Lo + (envReflection + envDiffuse) * ao;
4390
4391    // Grain — only inside shape
4392    color += grain(vUv, time, u_grain * 0.75);
4393
4394    // Self-blend — only inside shape
4395    if (u_blendMode > 0) {
4396      float bRotation = u_blendAnimRotation > 0 ? animatedBlendRotation(u_blendRotation, time, u_blendAnimSpeed) : u_blendRotation;
4397      float bOffset = u_blendAnimOffset > 0 ? animatedBlendOffset(u_blendOffset, time, u_blendAnimSpeed) : u_blendOffset;
4398      float bIntensity = u_blendAnimIntensity > 0 ? animatedBlendIntensity(u_blendIntensity, time, u_blendAnimSpeed) : u_blendIntensity;
4399      bIntensity = noiseModulatedIntensity(bIntensity, vUv, time, u_blendNoiseMask, u_blendNoiseScale);
4400      bIntensity = applyBlendFalloff(bIntensity, vUv.x, u_blendFalloff);
4401      float tB = blendLayerT(vUv, 0.5, u_resolution, u_direction, bOffset, bRotation, u_blendScale);
4402      vec3 blendCol = mixColors(u_colors, u_colorPositions, u_colorCount, tB);
4403      color = applyBlendMode(u_blendMode, color, blendCol, bIntensity);
4404      if (u_blendDualLayer > 0 && u_blendMode2 > 0) {
4405        float tB2 = blendLayerT(vUv, 0.5, u_resolution, u_direction, u_blendOffset2, u_blendRotation2, u_blendScale2);
4406        vec3 blendCol2 = mixColors(u_colors, u_colorPositions, u_colorCount, tB2);
4407        color = applyBlendMode(u_blendMode2, color, blendCol2, u_blendIntensity2);
4408      }
4409    }
4410
4411    fragColor = outputColor(color, gl_FragCoord.xy, 1.1);
4412  } else {
4413    // Background — transparent for export, dark for viewport
4414    if (u_transparentBg == 1) {
4415      fragColor = vec4(0.0);
4416    } else {
4417      float vignette = 1.0 - 0.3 * length(vUv - 0.5);
4418      vec3 bgColor = vec3(0.03 * u_bgGradient * vignette);
4419      fragColor = outputColor(bgColor, gl_FragCoord.xy, 1.1);
4420    }
4421  }
4422}
4423`,g=`#version 300 es
4424precision highp float;
4425
4426in vec2 vUv;
4427out vec4 fragColor;
4428
4429uniform float u_time;
4430uniform float u_speed;
4431uniform vec3 u_colors[12];
4432uniform float u_colorPositions[12];
4433uniform int u_colorCount;
4434uniform float u_direction;
4435uniform float u_grain;
4436uniform vec2 u_resolution;
4437
4438// Blend uniforms
4439uniform int u_blendMode;
4440uniform float u_blendIntensity;
4441uniform float u_blendOffset;
4442uniform float u_blendRotation;
4443uniform float u_blendScale;
4444uniform int u_blendAnimRotation;
4445uniform int u_blendAnimIntensity;
4446uniform int u_blendAnimOffset;
4447uniform float u_blendAnimSpeed;
4448uniform float u_blendNoiseMask;
4449uniform float u_blendNoiseScale;
4450uniform float u_blendFalloff;
4451uniform int u_blendDualLayer;
4452uniform int u_blendMode2;
4453uniform float u_blendIntensity2;
4454uniform float u_blendOffset2;
4455uniform float u_blendRotation2;
4456uniform float u_blendScale2;
4457
4458// Mode-specific uniforms
4459uniform int u_reactiveStyle;
4460uniform int u_fftSize;
4461uniform float u_bassSensitivity;
4462uniform float u_midSensitivity;
4463uniform float u_trebleSensitivity;
4464uniform float u_smoothing;
4465uniform float u_reactiveScale;
4466uniform float u_reactiveSpeed;
4467uniform float u_colorReactivity;
4468
4469// Band isolation uniforms
4470uniform int u_bassTarget;   // 0=scale, 1=warp, 2=color, 3=brightness, 4=speed
4471uniform int u_midTarget;
4472uniform int u_trebleTarget;
4473uniform float u_bassSmoothing;
4474uniform float u_trebleSmoothing;
4475
4476// Audio feature uniforms (bound by canvas, not from state)
4477uniform float u_bass;
4478uniform float u_mid;
4479uniform float u_treble;
4480uniform float u_energy;
4481uniform float u_peak;
4482uniform float u_beat;
4483uniform sampler2D u_spectrum;
4484
4485__COMMON__
4486
4487// --- Helpers ---
4488
4489// Simple hash for value noise
4490float arHash(vec2 p) {
4491  return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
4492}
4493
4494// Value noise 2D
4495float arNoise(vec2 p) {
4496  vec2 i = floor(p);
4497  vec2 f = fract(p);
4498  f = f * f * (3.0 - 2.0 * f);
4499  return mix(
4500    mix(arHash(i), arHash(i + vec2(1.0, 0.0)), f.x),
4501    mix(arHash(i + vec2(0.0, 1.0)), arHash(i + vec2(1.0, 1.0)), f.x),
4502    f.y
4503  );
4504}
4505
4506// FBM using value noise (separate from common.glsl simplex fbm)
4507float arFbm(vec2 p, int octaves) {
4508  float sum = 0.0;
4509  float amp = 0.5;
4510  for (int i = 0; i < 8; i++) {
4511    if (i >= octaves) break;
4512    sum += amp * arNoise(p);
4513    p *= 2.0;
4514    amp *= 0.5;
4515  }
4516  return sum;
4517}
4518
4519// Spectrum texture lookup (128x1 R32F on TEXTURE1)
4520// Returns 0.0 when no audio source is connected (texture is zeroed)
4521float getSpectrum(float freq) {
4522  return texture(u_spectrum, vec2(clamp(freq, 0.0, 1.0), 0.5)).r;
4523}
4524
4525// ---- Band-driven visual property helpers ----
4526// Each band (bass/mid/treble) can target a visual property (scale/warp/color/brightness/speed).
4527// These helpers accumulate contributions from all bands that target a given property.
4528
4529float getScaleDriver(float bass, float mid, float treble) {
4530  float v = 0.0;
4531  if (u_bassTarget == 0) v += bass;
4532  if (u_midTarget == 0) v += mid;
4533  if (u_trebleTarget == 0) v += treble;
4534  return v;
4535}
4536
4537float getWarpDriver(float bass, float mid, float treble) {
4538  float v = 0.0;
4539  if (u_bassTarget == 1) v += bass;
4540  if (u_midTarget == 1) v += mid;
4541  if (u_trebleTarget == 1) v += treble;
4542  return v;
4543}
4544
4545float getColorDriver(float bass, float mid, float treble) {
4546  float v = 0.0;
4547  if (u_bassTarget == 2) v += bass;
4548  if (u_midTarget == 2) v += mid;
4549  if (u_trebleTarget == 2) v += treble;
4550  return v;
4551}
4552
4553float getBrightnessDriver(float bass, float mid, float treble) {
4554  float v = 0.0;
4555  if (u_bassTarget == 3) v += bass;
4556  if (u_midTarget == 3) v += mid;
4557  if (u_trebleTarget == 3) v += treble;
4558  return v;
4559}
4560
4561float getSpeedDriver(float bass, float mid, float treble) {
4562  float v = 0.0;
4563  if (u_bassTarget == 4) v += bass;
4564  if (u_midTarget == 4) v += mid;
4565  if (u_trebleTarget == 4) v += treble;
4566  return v;
4567}
4568
4569void main() {
4570  vec2 uv = vUv;
4571  float t = u_time * u_speed * u_reactiveSpeed;
4572  float aspect = u_resolution.x / max(u_resolution.y, 1.0);
4573  uv = applyMouseWarp(uv, aspect);
4574
4575  // Scale audio features by sensitivity
4576  float bass = u_bass * u_bassSensitivity;
4577  float mid = u_mid * u_midSensitivity;
4578  float treble = u_treble * u_trebleSensitivity;
4579  float energy = u_energy;
4580  float peak = u_peak;
4581  float beat = u_beat;
4582
4583  // Band isolation: compute visual property drivers from band targets
4584  float scaleDriver = getScaleDriver(bass, mid, treble);
4585  float warpDriver = getWarpDriver(bass, mid, treble);
4586  float colorDriver = getColorDriver(bass, mid, treble);
4587  float brightnessDriver = getBrightnessDriver(bass, mid, treble);
4588  float speedDriver = getSpeedDriver(bass, mid, treble);
4589
4590  vec3 color = vec3(0.0);
4591  float colorPos = 0.0;
4592
4593  if (u_reactiveStyle == 0) {
4594    // ========== PULSE ==========
4595    // Radial gradient that expands/contracts with bass. Colors shift with
4596    // mid/treble. Beats trigger a bright flash overlay.
4597
4598    vec2 center = vec2(0.5);
4599    vec2 p = (uv - center) * vec2(aspect, 1.0);
4600    float dist = length(p);
4601
4602    // Pulsing radius driven by bass
4603    float radius = (0.2 + scaleDriver * 0.4) * u_reactiveScale;
4604
4605    // Soft radial falloff
4606    float pulse = smoothstep(radius, radius * 0.3, dist);
4607
4608    // Concentric rings that expand outward on beats
4609    float ringPhase = dist * 20.0 - t * 3.0 - beat * 6.28318;
4610    float rings = sin(ringPhase) * 0.5 + 0.5;
4611    rings *= smoothstep(1.0, 0.0, dist);
4612
4613    // Color position: distance-based + audio modulation
4614    colorPos = dist / max(u_reactiveScale, 0.01) + energy * u_colorReactivity * 0.5;
4615    colorPos = fract(colorPos + t * 0.1 + colorDriver * 0.2);
4616
4617    color = mixColors(u_colors, u_colorPositions, u_colorCount, colorPos);
4618    color *= pulse + rings * 0.3;
4619
4620    // Beat flash: bright white burst
4621    color += vec3(beat * 0.3 * peak);
4622
4623    // Treble shimmer on outer edge
4624    float shimmer = arNoise(p * 12.0 + t * 2.0) * treble * 0.15;
4625    color += mixColors(u_colors, u_colorPositions, u_colorCount, fract(colorPos + 0.3)) * shimmer * smoothstep(0.0, 0.5, dist);
4626
4627  } else if (u_reactiveStyle == 1) {
4628    // ========== WAVEFORM ==========
4629    // Horizontal waveform visualization. Multiple layered waves respond to
4630    // different frequency bands. Glow-line aesthetic.
4631
4632    vec2 p = (uv - 0.5) * vec2(aspect, 1.0);
4633
4634    // Three frequency-band waves layered
4635    float wave1 = sin(p.x * 8.0 + t * 2.0) * scaleDriver * 0.15 * u_reactiveScale;
4636    float wave2 = sin(p.x * 15.0 - t * 3.0) * warpDriver * 0.1 * u_reactiveScale;
4637    float wave3 = sin(p.x * 25.0 + t * 5.0) * treble * 0.06 * u_reactiveScale;
4638
4639    // Composite waveform
4640    float waveY = wave1 + wave2 + wave3;
4641    float dist = abs(p.y - waveY);
4642
4643    // Glow lines: bright core with soft falloff
4644    float glow = 0.008 / (dist + 0.008);
4645    // Secondary traces for each band
4646    glow += 0.003 / (abs(p.y - wave1) + 0.003) * 0.5;
4647    glow += 0.002 / (abs(p.y - wave2 * 1.5) + 0.002) * 0.3;
4648    glow += 0.0015 / (abs(p.y + wave3 * 0.8) + 0.0015) * 0.2;
4649
4650    colorPos = fract(uv.x + energy * u_colorReactivity * 0.3 + t * 0.05);
4651    color = mixColors(u_colors, u_colorPositions, u_colorCount, colorPos);
4652    color *= glow;
4653
4654    // Beat brightens the whole field
4655    color += vec3(beat * 0.15);
4656
4657    // Subtle background tint so it is not pure black
4658    vec3 bgTint = mixColors(u_colors, u_colorPositions, u_colorCount, uv.y) * 0.03;
4659    color += bgTint;
4660
4661  } else if (u_reactiveStyle == 2) {
4662    // ========== SPECTRUM ==========
4663    // Vertical bars representing FFT bins. Classic audio visualizer look
4664    // with gradient-colored bars and top-glow caps.
4665
4666    float binCount = max(float(u_fftSize) / 2.0, 1.0);
4667    // Limit visible bars to keep it readable
4668    float visibleBars = min(binCount, 128.0);
4669    float barWidth = 1.0 / visibleBars;
4670    float barIndex = floor(uv.x / barWidth);
4671    float freq = barIndex / visibleBars;
4672
4673    // Read spectrum and apply per-band sensitivity
4674    float specVal = getSpectrum(freq);
4675    float bandMul = freq < 0.15 ? u_bassSensitivity
4676                  : (freq < 0.5 ? u_midSensitivity : u_trebleSensitivity);
4677    specVal *= bandMul * u_reactiveScale;
4678
4679    // Bar height mask (bars grow upward from bottom)
4680    float barHeight = clamp(specVal, 0.0, 1.0);
4681    float barMask = step(1.0 - uv.y, barHeight);
4682
4683    // Gradient color along frequency axis
4684    colorPos = fract(freq + energy * u_colorReactivity * 0.2);
4685    color = mixColors(u_colors, u_colorPositions, u_colorCount, colorPos);
4686
4687    // Smooth bar edges for anti-aliased look
4688    float barLocal = fract(uv.x / barWidth);
4689    float edgeFade = smoothstep(0.0, 0.15, barLocal) * smoothstep(1.0, 0.85, barLocal);
4690
4691    color *= barMask * edgeFade;
4692
4693    // Glowing cap at the top of each bar
4694    float topDist = abs((1.0 - uv.y) - barHeight);
4695    float topGlow = 0.005 / (topDist + 0.005);
4696    vec3 capColor = mixColors(u_colors, u_colorPositions, u_colorCount, freq);
4697    color += capColor * topGlow * 0.3 * step(0.001, barHeight);
4698
4699    // Beat pulse
4700    color += vec3(beat * 0.1);
4701
4702    // Height-based brightness: brighter at the top of bars
4703    color *= 0.7 + 0.3 * (1.0 - uv.y);
4704
4705  } else if (u_reactiveStyle == 3) {
4706    // ========== NEBULA ==========
4707    // Fractal noise nebula that breathes and swirls with audio energy.
4708    // Bass drives scale, treble drives detail, beats create bright bursts.
4709
4710    vec2 p = (uv - 0.5) * 3.0 * u_reactiveScale;
4711    float scale = 2.0 + scaleDriver * 2.0;
4712
4713    // Domain warp driven by energy
4714    vec2 warp = vec2(
4715      arFbm(p * scale + vec2(t * 0.3, 0.0), 5),
4716      arFbm(p * scale + vec2(0.0, t * 0.3), 5)
4717    ) * (0.5 + warpDriver * 1.0 + energy * 0.5);
4718
4719    // Treble adds extra octaves of detail
4720    int detailOctaves = 4 + int(treble * 3.0);
4721    float n = arFbm(p * scale + warp + vec2(t * 0.2), detailOctaves);
4722    n = n * 0.5 + 0.5; // remap 0-1
4723
4724    // Audio modulation of the field
4725    n += scaleDriver * 0.2 - treble * 0.1;
4726    n += beat * 0.15;
4727    n = clamp(n, 0.0, 1.0);
4728
4729    // Ridged variant for filament structures
4730    float ridged = 1.0 - abs(arFbm(p * scale * 1.5 + warp * 0.7, 4) * 2.0 - 1.0);
4731    ridged = pow(ridged, 1.5) * mid * 0.3;
4732
4733    colorPos = fract(n + ridged * 0.2 + energy * u_colorReactivity * 0.3);
4734    color = mixColors(u_colors, u_colorPositions, u_colorCount, colorPos);
4735
4736    // Brightness: base level + energy boost + beat burst
4737    float brightness = 0.5 + brightnessDriver * 0.3 + energy * 0.3 + beat * 0.3;
4738    color *= brightness;
4739
4740    // Add hot emission near ridges
4741    vec3 hotColor = mixColors(u_colors, u_colorPositions, u_colorCount, clamp(colorPos + 0.25, 0.0, 1.0));
4742    color += hotColor * ridged * 0.4;
4743
4744  } else if (u_reactiveStyle == 4) {
4745    // ========== RINGS ==========
4746    // Concentric rings pulse outward with beats. Ring spacing and thickness
4747    // react to bass/mid. Colors cycle with energy.
4748
4749    vec2 center = vec2(0.5);
4750    vec2 p = (uv - center) * vec2(aspect, 1.0);
4751    float dist = length(p);
4752
4753    // Ring geometry driven by audio
4754    float ringSpacing = 0.08 + scaleDriver * 0.06;
4755    float ringWidth = 0.02 + warpDriver * 0.02;
4756
4757    // Multiple expanding rings
4758    float rings = 0.0;
4759    for (int i = 0; i < 8; i++) {
4760      float r = float(i) * ringSpacing + fract(t * 0.5) * ringSpacing;
4761      float d = abs(dist - r);
4762      rings += ringWidth / (d + ringWidth);
4763    }
4764    rings = min(rings, 3.0) / 3.0;
4765
4766    // Beat creates a bright expanding ring burst
4767    float beatRing = beat * 2.0 / (abs(dist - fract(t * 0.8) * 0.5) + 0.05);
4768    beatRing = min(beatRing, 2.0);
4769
4770    // Treble adds a fine interference pattern
4771    float interference = sin(dist * 60.0 - t * 8.0) * 0.5 + 0.5;
4772    interference *= treble * 0.15;
4773
4774    colorPos = fract(dist / max(u_reactiveScale, 0.01) + energy * u_colorReactivity + t * 0.1);
4775    color = mixColors(u_colors, u_colorPositions, u_colorCount, colorPos);
4776    color *= rings + beatRing * 0.2 + interference;
4777
4778    // Center glow
4779    float centerGlow = exp(-dist * 4.0) * (0.3 + energy * 0.4);
4780    vec3 glowColor = mixColors(u_colors, u_colorPositions, u_colorCount, fract(colorPos + 0.5));
4781    color += glowColor * centerGlow;
4782
4783  } else if (u_reactiveStyle == 5) {
4784    // ========== LIQUID ==========
4785    // Fluid simulation-like distortion. Energy drives flow speed, bass warps
4786    // the field, treble adds fine-detail turbulence.
4787
4788    vec2 p = uv * 3.0 * u_reactiveScale;
4789
4790    // Flow field warped by audio
4791    float flowSpeed = 0.3 + speedDriver * 0.3 + energy * 0.4;
4792    vec2 flow = vec2(
4793      arFbm(p + vec2(t * flowSpeed, 0.0), 4),
4794      arFbm(p + vec2(0.0, t * flowSpeed), 4)
4795    );
4796
4797    // Bass warps the field broadly, treble adds fine turbulence
4798    vec2 warp = flow * (0.3 + warpDriver * 0.5);
4799    float turbulence = arFbm(p * (3.0 + treble * 4.0) + warp + vec2(t * 0.5), 5);
4800
4801    // Combine layers
4802    float n = arFbm(p + warp + vec2(turbulence * 0.3), 4);
4803    n = n * 0.5 + 0.5;
4804
4805    // Secondary flow for depth
4806    float n2 = arFbm(p * 1.5 - warp * 0.8 + vec2(t * 0.3, -t * 0.2), 3);
4807    n2 = n2 * 0.5 + 0.5;
4808
4809    // Blend the two fields
4810    float blend = mix(n, n2, 0.3 + colorDriver * 0.2);
4811
4812    // Energy drives overall intensity
4813    blend = blend * (0.6 + energy * 0.4);
4814    blend += beat * 0.1;
4815    blend = clamp(blend, 0.0, 1.0);
4816
4817    colorPos = fract(blend + energy * u_colorReactivity * 0.2 + t * 0.05);
4818    color = mixColors(u_colors, u_colorPositions, u_colorCount, colorPos);
4819
4820    // Peak brightness modulation
4821    color *= 0.7 + peak * 0.3;
4822
4823    // Subtle caustic highlights
4824    float caustic = pow(max(sin(n * 6.28318 + t) * 0.5 + 0.5, 0.0
4824), 3.0) * mid * 0.2;
4825    vec3 causticColor = mixColors(u_colors, u_colorPositions, u_colorCount, fract(colorPos + 0.4));
4826    color += causticColor * caustic;
4827  }
4828
4829  // Grain
4830  color += grain(uv, t, u_grain);
4831
4832  // Self-blend (consistent with all other shaders)
4833  if (u_blendMode > 0) {
4834    float bRotation = u_blendAnimRotation > 0 ? animatedBlendRotation(u_blendRotation, t, u_blendAnimSpeed) : u_blendRotation;
4835    float bOffset = u_blendAnimOffset > 0 ? animatedBlendOffset(u_blendOffset, t, u_blendAnimSpeed) : u_blendOffset;
4836    float bIntensity = u_blendAnimIntensity > 0 ? animatedBlendIntensity(u_blendIntensity, t, u_blendAnimSpeed) : u_blendIntensity;
4837    bIntensity = noiseModulatedIntensity(bIntensity, uv, t, u_blendNoiseMask, u_blendNoiseScale);
4838    bIntensity = applyBlendFalloff(bIntensity, uv.x, u_blendFalloff);
4839    float tB = blendLayerT(uv, 0.5, u_resolution, u_direction, bOffset, bRotation, u_blendScale);
4840    vec3 blendCol = mixColors(u_colors, u_colorPositions, u_colorCount, tB);
4841    color = applyBlendMode(u_blendMode, color, blendCol, bIntensity);
4842    if (u_blendDualLayer > 0 && u_blendMode2 > 0) {
4843      float tB2 = blendLayerT(uv, 0.5, u_resolution, u_direction, u_blendOffset2, u_blendRotation2, u_blendScale2);
4844      vec3 blendCol2 = mixColors(u_colors, u_colorPositions, u_colorCount, tB2);
4845      color = applyBlendMode(u_blendMode2, color, blendCol2, u_blendIntensity2);
4846    }
4847  }
4848
4849  fragColor = outputColor(color, gl_FragCoord.xy, 1.0);
4850}
4851`,h=`#version 300 es
4852precision highp float;
4853
4854in vec2 vUv;
4855out vec4 fragColor;
4856
4857uniform float u_time;
4858uniform float u_speed;
4859uniform vec3 u_colors[12];
4860uniform float u_colorPositions[12];
4861uniform int u_colorCount;
4862uniform float u_direction;
4863uniform float u_grain;
4864uniform vec2 u_resolution;
4865
4866// Blend uniforms
4867uniform int u_blendMode;
4868uniform float u_blendIntensity;
4869uniform float u_blendOffset;
4870uniform float u_blendRotation;
4871uniform float u_blendScale;
4872uniform int u_blendAnimRotation;
4873uniform int u_blendAnimIntensity;
4874uniform int u_blendAnimOffset;
4875uniform float u_blendAnimSpeed;
4876uniform float u_blendNoiseMask;
4877uniform float u_blendNoiseScale;
4878uniform float u_blendFalloff;
4879uniform int u_blendDualLayer;
4880uniform int u_blendMode2;
4881uniform float u_blendIntensity2;
4882uniform float u_blendOffset2;
4883uniform float u_blendRotation2;
4884uniform float u_blendScale2;
4885
4886// Mode-specific
4887uniform int u_chromeStyle;
4888uniform int u_warpIterations;
4889uniform float u_warpAmplitude;
4890uniform float u_warpFrequency;
4891uniform float u_specularSharpness;
4892uniform float u_specularIntensity;
4893uniform float u_chromaticAberration;
4894uniform float u_flowSpeed;
4895
4896__COMMON__
4897
4898// Chrome style color tints applied in specular
4899vec3 getChromeBaseColor(int style, float height) {
4900  // 0=silver, 1=gold, 2=copper, 3=neon, 4=custom (uses gradient)
4901  if (style == 1) return mix(vec3(0.15, 0.1, 0.02), vec3(1.0, 0.85, 0.4), height);
4902  if (style == 2) return mix(vec3(0.12, 0.05, 0.02), vec3(0.95, 0.6, 0.4), height);
4903  if (style == 3) return mix(vec3(0.0, 0.05, 0.1), vec3(0.2, 1.0, 0.8), height);
4904  // Silver (default) and custom
4905  return vec3(height);
4906}
4907
4908void main() {
4909  vec2 uv = vUv;
4910  float aspect = u_resolution.x / u_resolution.y;
4911  uv.x *= aspect;
4912  float time = u_time * u_speed * u_flowSpeed;
4913
4914  // Mouse influence — velocity-based distortion
4915  vec2 mouseUv = u_mouse * 0.5 + 0.5;
4916  mouseUv.x *= aspect;
4917  float mouseDist = distance(uv, mouseUv);
4918  float mouseInfluence = smoothstep(0.6, 0.0, mouseDist);
4919
4920  // Extra distortion from mouse (simulates velocity)
4921  float mouseDistort = mouseInfluence * 0.5;
4922
4923  // Iterative UV warping — the core liquid chrome effect
4924  vec2 warpUv = uv;
4925  int iters = u_warpIterations;
4926  float amp = u_warpAmplitude;
4927
4928  for (int i = 1; i <= 10; i++) {
4929    if (i > iters) break;
4930    float fi = float(i);
4931    warpUv.x += amp / fi * cos(fi * u_warpFrequency * warpUv.y + time + mouseDistort * 2.0);
4932    warpUv.y += amp / fi * cos(fi * (u_warpFrequency * 0.6) * warpUv.x + time + mouseDistort * 2.0);
4933  }
4934
4935  // Calculate "height" for chrome reflection — creates ridges and valleys
4936  vec2 grid = warpUv * 3.0;
4937  float height = sin(grid.x + grid.y) * 0.5 + 0.5;
4938
4939  // Specular highlights — the chrome "shine"
4940  float spec = smoothstep(u_specularSharpness, 1.0, sin(warpUv.x * 10.0 + warpUv.y * 10.0));
4941  float spec2 = smoothstep(u_specularSharpness * 0.9, 1.0, sin(warpUv.x * 7.0 - warpUv.y * 5.0 + time * 0.3));
4942  float specular = max(spec, spec2 * 0.6) * u_specularIntensity;
4943
4944  // Secondary ridges for more complexity
4945  float ridge2 = sin(warpUv.x * 5.0 + warpUv.y * 8.0 + time * 0.2) * 0.5 + 0.5;
4946  height = mix(height, ridge2, 0.3);
4947
4948  // Map height to color
4949  float t = clamp(height, 0.0, 1.0);
4950
4951  vec3 color;
4952  if (u_chromeStyle == 4) {
4953    // Custom: use gradient color stops
4954    color = mixColors(u_colors, u_colorPositions, u_colorCount, t);
4955  } else {
4956    // Preset chrome: base metal color modulated by gradient
4957    vec3 chromeBase = getChromeBaseColor(u_chromeStyle, height);
4958    vec3 gradientColor = mixColors(u_colors, u_colorPositions, u_colorCount, t);
4959    color = chromeBase * gradientColor * 2.0;
4960  }
4961
4962  // Apply specular highlights
4963  color += vec3(specular);
4964
4965  // Chromatic aberration based on mouse proximity
4966  if (u_chromaticAberration > 0.0) {
4967    float ca = u_chromaticAberration + mouseDistort * u_chromaticAberration;
4968    // Re-warp with slight offset for R and B channels
4969    vec2 uvR = uv + vec2(ca, 0.0);
4970    vec2 uvB = uv - vec2(ca, 0.0);
4971    vec2 warpR = uvR;
4972    vec2 warpB = uvB;
4973    for (int i = 1; i <= 10; i++) {
4974      if (i > iters) break;
4975      float fi = float(i);
4976      warpR.x += amp / fi * cos(fi * u_warpFrequency * warpR.y + time);
4977      warpR.y += amp / fi * cos(fi * (u_warpFrequency * 0.6) * warpR.x + time);
4978      warpB.x += amp / fi * cos(fi * u_warpFrequency * warpB.y + time);
4979      warpB.y += amp / fi * cos(fi * (u_warpFrequency * 0.6) * warpB.x + time);
4980    }
4981    float hR = sin(warpR.x * 3.0 + warpR.y * 3.0) * 0.5 + 0.5;
4982    float hB = sin(warpB.x * 3.0 + warpB.y * 3.0) * 0.5 + 0.5;
4983    color.r = mix(color.r, mixColors(u_colors, u_colorPositions, u_colorCount, hR).r, 0.3);
4984    color.b = mix(color.b, mixColors(u_colors, u_colorPositions, u_colorCount, hB).b, 0.3);
4985  }
4986
4987  // Add grain
4988  color += grain(vUv, time, u_grain);
4989
4990  // Self-blend
4991  if (u_blendMode > 0) {
4992    float bRotation = u_blendAnimRotation > 0 ? animatedBlendRotation(u_blendRotation, time, u_blendAnimSpeed) : u_blendRotation;
4993    float bOffset = u_blendAnimOffset > 0 ? animatedBlendOffset(u_blendOffset, time, u_blendAnimSpeed) : u_blendOffset;
4994    float bIntensity = u_blendAnimIntensity > 0 ? animatedBlendIntensity(u_blendIntensity, time, u_blendAnimSpeed) : u_blendIntensity;
4995    bIntensity = noiseModulatedIntensity(bIntensity, vUv, time, u_blendNoiseMask, u_blendNoiseScale);
4996    bIntensity = applyBlendFalloff(bIntensity, t, u_blendFalloff);
4997    float tB = blendLayerT(vUv, t, u_resolution, u_direction, bOffset, bRotation, u_blendScale);
4998    vec3 blendCol = mixColors(u_colors, u_colorPositions, u_colorCount, tB);
4999    color = applyBlendMode(u_blendMode, color, blendCol, bIntensity);
5000    if (u_blendDualLayer > 0 && u_blendMode2 > 0) {
5001      float tB2 = blendLayerT(vUv, t, u_resolution, u_direction, u_blendOffset2, u_blendRotation2, u_blendScale2);
5002      vec3 blendCol2 = mixColors(u_colors, u_colorPositions, u_colorCount, tB2);
5003      color = applyBlendMode(u_blendMode2, color, blendCol2, u_blendIntensity2);
5004    }
5005  }
5006
5007  fragColor = outputColor(color, gl_FragCoord.xy, 1.0);
5008}
5009`,y=`#version 300 es
5010precision highp float;
5011
5012in vec2 vUv;
5013out vec4 fragColor;
5014
5015uniform float u_time;
5016uniform float u_speed;
5017uniform vec3 u_colors[12];
5018uniform float u_colorPositions[12];
5019uniform int u_colorCount;
5020uniform float u_direction;
5021uniform float u_grain;
5022uniform vec2 u_resolution;
5023
5024// Blend uniforms
5025uniform int u_blendMode;
5026uniform float u_blendIntensity;
5027uniform float u_blendOffset;
5028uniform float u_blendRotation;
5029uniform float u_blendScale;
5030uniform int u_blendAnimRotation;
5031uniform int u_blendAnimIntensity;
5032uniform int u_blendAnimOffset;
5033uniform float u_blendAnimSpeed;
5034uniform float u_blendNoiseMask;
5035uniform float u_blendNoiseScale;
5036uniform float u_blendFalloff;
5037uniform int u_blendDualLayer;
5038uniform int u_blendMode2;
5039uniform float u_blendIntensity2;
5040uniform float u_blendOffset2;
5041uniform float u_blendRotation2;
5042uniform float u_blendScale2;
5043
5044// Mode-specific
5045uniform int u_fieldStyle;       // 0=fiber, 1=starburst, 2=convergence, 3=fountain
5046uniform int u_lineCount;
5047uniform float u_originX;
5048uniform float u_originY;
5049uniform float u_lineLength;
5050uniform float u_lineThickness;
5051uniform float u_lineCurvature;
5052uniform float u_dotSize;
5053uniform float u_spread;
5054uniform float u_glowIntensity;
5055uniform float u_glowRadius;
5056uniform float u_mouseForce;
5057uniform float u_mouseRadius;
5058
5059__COMMON__
5060
5061// Hash function for per-line randomization
5062float hash(float n) { return fract(sin(n) * 43758.5453123); }
5063vec2 hash2(float n) { return vec2(hash(n), hash(n + 57.0)); }
5064
5065// Distance from point to line segment
5066float distToSegment(vec2 p, vec2 a, vec2 b) {
5067  vec2 pa = p - a;
5068  vec2 ba = b - a;
5069  float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
5070  return length(pa - ba * h);
5071}
5072
5073// Cubic bezier point
5074vec2 bezier(vec2 a, vec2 b, vec2 c, float t) {
5075  vec2 ab = mix(a, b, t);
5076  vec2 bc = mix(b, c, t);
5077  return mix(ab, bc, t);
5078}
5079
5080// Distance from point to bezier curve (sampled)
5081float distToBezier(vec2 p, vec2 a, vec2 ctrl, vec2 b, out float closestT) {
5082  float minDist = 100.0;
5083  closestT = 0.0;
5084  for (int i = 0; i <= 16; i++) {
5085    float t = float(i) / 16.0;
5086    vec2 pt = bezier(a, ctrl, b, t);
5087    float d = distance(p, pt);
5088    if (d < minDist) {
5089      minDist = d;
5090      closestT = t;
5091    }
5092  }
5093  return minDist;
5094}
5095
5096void main() {
5097  vec2 uv = vUv;
5098  float aspect = u_resolution.x / u_resolution.y;
5099  vec2 aspectUv = vec2(uv.x * aspect, uv.y);
5100  float time = u_time * u_speed;
5101
5102  // Origin point
5103  vec2 origin = vec2(u_originX * aspect, u_originY);
5104
5105  // Mouse position in aspect-corrected space
5106  vec2 mousePos = (u_mouse * 0.5 + 0.5);
5107  mousePos.x *= aspect;
5108
5109  // Background: dark with subtle gradient tint
5110  float bgDist = distance(aspectUv, origin);
5111  float bgT = clamp(bgDist / 1.2, 0.0, 1.0);
5112  vec3 bgGradient = mixColors(u_colors, u_colorPositions, u_colorCount, bgT);
5113  // Keep background dark but tinted — use sqrt to brighten dark palettes
5114  vec3 bgColor = sqrt(bgGradient) * 0.12;
5115
5116  // Origin glow — use brightest color from palette
5117  float glow = exp(-bgDist * bgDist / (u_glowRadius * u_glowRadius)) * u_glowIntensity;
5118  // Sample brightest region of palette for glow
5119  vec3 glowColor = mixColors(u_colors, u_colorPositions, u_colorCount, 0.7);
5120  glowColor = max(glowColor, vec3(0.3)); // Ensure glow is always visible
5121
5122  // Accumulate line color directly (not just brightness)
5123  vec3 lineAccum = vec3(0.0);
5124  float dotBrightness = 0.0;
5125  float totalWeight = 0.0;
5126
5127  int count = u_lineCount;
5128
5129  for (int i = 0; i < 200; i++) {
5130    if (i >= count) break;
5131
5132    float fi = float(i);
5133    float randSeed = fi * 1.37;
5134
5135    // Base angle distribution
5136    float baseAngle;
5137    if (u_fieldStyle == 0) {
5138      // Fiber: semi-circular spread from origin
5139      baseAngle = -u_spread * 0.5 + u_spread * (fi + 0.5) / float(count);
5140    } else if (u_fieldStyle == 1) {
5141      // Starburst: full radial with golden angle offset
5142      baseAngle = fi * 2.39996; // golden angle
5143    } else if (u_fieldStyle == 2) {
5144      // Convergence: lines come from edges toward origin (reversed)
5145      baseAngle = -u_spread * 0.5 + u_spread * (fi + 0.5) / float(count);
5146    } else {
5147      // Fountain: upward spray
5148      baseAngle = -u_spread * 0.25 + u_spread * 0.5 * (fi + 0.5) / float(count);
5149      baseAngle += 1.5708; // rotate 90 deg (up)
5150    }
5151
5152    // Add per-line direction offset (rotates base)
5153    float dirAngle = radians(u_direction);
5154    baseAngle += dirAngle;
5155
5156    // Per-line randomization
5157    vec2 lineRand = hash2(fi * 7.31);
5158    float lengthVar = 0.7 + lineRand.x * 0.6; // 0.7 – 1.3
5159    float phaseOffset = lineRand.y * 6.283;
5160
5161    // Noise-based curvature
5162    float curveNoise = snoise(vec2(fi * 0.1, time * 0.3)) * u_lineCurvature;
5163
5164    // Line geometry
5165    vec2 dir = vec2(cos(baseAngle), sin(baseAngle));
5166    float len = u_lineLength * lengthVar;
5167
5168    // Start point
5169    vec2 lineStart = origin;
5170
5171    // End point
5172    vec2 lineEnd = origin + dir * len;
5173
5174    // Control point for bezier curve (perpendicular offset for curvature)
5175    vec2 perp = vec2(-dir.y, dir.x);
5176    vec2 ctrl = origin + dir * len * 0.5 + perp * curveNoise * len * 0.5;
5177
5178    // Gentle drift animation
5179    float drift = sin(time * 0.5 + phaseOffset) * 0.02;
5180    ctrl += perp * drift;
5181    lineEnd += perp * drift * 0.5;
5182
5183    // Mouse force field — displaces endpoint and control point
5184    if (u_mouseForce > 0.0) {
5185      float mouseDistEnd = distance(lineEnd, mousePos);
5186      float mouseDistCtrl = distance(ctrl, mousePos);
5187      float forceEnd = smoothstep(u_mouseRadius, 0.0, mouseDistEnd) * u_mouseForce;
5188      float forceCtrl = smoothstep(u_mouseRadius, 0.0, mouseDistCtrl) * u_mouseForce * 0.5;
5189
5190      vec2 pushEnd = normalize(lineEnd - mousePos + vec2(0.001)) * forceEnd * 0.15;
5191      vec2 pushCtrl = normalize(ctrl - mousePos + vec2(0.001)) * forceCtrl * 0.15;
5192
5193      lineEnd += pushEnd;
5194      ctrl += pushCtrl;
5195    }
5196
5197    // For convergence style, swap start and end
5198    if (u_fieldStyle == 2) {
5199      vec2 tmp = lineStart;
5200      lineStart = lineEnd;
5201      lineEnd = tmp;
5202    }
5203
5204    // Distance from pixel to bezier curve
5205    float closestT;
5206    float d = distToBezier(aspectUv, lineStart, ctrl, lineEnd, closestT);
5207
5208    // Convert pixel-space thickness to UV space
5209    float pxThickness = u_lineThickness / u_resolution.y;
5210    float softness = pxThickness * 2.0;
5211    float lineBright = smoothstep(pxThickness + softness, pxThickness * 0.2, d);
5212
5213    // Fade along line length (tip brighter, base fades)
5214    float lengthFade = smoothstep(0.0, 0.3, closestT);
5215    lineBright *= lengthFade;
5216
5217    // Gentle pulse
5218    float pulse = 0.7 + 0.3 * sin(time * 1.5 + phaseOffset);
5219    lineBright *= pulse;
5220
5221    // Color this line — map line index to gradient position
5222    float lineGradT = (fi + 0.5) / float(count);
5223    vec3 thisLineColor = mixColors(u_colors, u_colorPositions, u_colorCount, lineGradT);
5224    // Ensure lines are always visible by boosting dark colors
5225    thisLineColor = max(thisLineColor, vec3(0.15));
5226
5227    lineAccum += thisLineColor * lineBright;
5228    totalWeight += lineBright;
5229
5230    // Endpoint dot
5231    vec2 endPt = bezier(lineStart, ctrl, lineEnd, 1.0);
5232    float dotDist = distance(aspectUv, endPt);
5233    float pxDotSize = u_dotSize / u_resolution.y;
5234    float dot = smoothstep(pxDotSize + pxDotSize * 0.5, pxDotSize * 0.2, dotDist) * pulse;
5235    dotBrightness += dot;
5236  }
5237
5238  // Clamp accumulated values
5239  lineAccum = min(lineAccum, vec3(3.0));
5240  dotBrightness = min(dotBrightness, 2.5);
5241
5242  // Compose: background + glow + colored lines + dots
5243  vec3 color = bgColor;
5244  color += glowColor * glow * 0.5;
5245  color += lineAccum * 1.5;
5246  // Dots glow white-ish for maximum visibility
5247  color += vec3(0.9, 0.95, 1.0) * dotBrightness * 0.7;
5248
5249  // Add grain
5250  color += grain(vUv, time, u_grain);
5251
5252  // Self-blend
5253  float t = clamp(bgT, 0.0, 1.0);
5254  if (u_blendMode > 0) {
5255    float bRotation = u_blendAnimRotation > 0 ? animatedBlendRotation(u_blendRotation, time, u_blendAnimSpeed) : u_blendRotation;
5256    float bOffset = u_blendAnimOffset > 0 ? animatedBlendOffset(u_blendOffset, time, u_blendAnimSpeed) : u_blendOffset;
5257    float bIntensity = u_blendAnimIntensity > 0 ? animatedBlendIntensity(u_blendIntensity, time, u_blendAnimSpeed) : u_blendIntensity;
5258    bIntensity = noiseModulatedIntensity(bIntensity, vUv, time, u_blendNoiseMask, u_blendNoiseScale);
5259    bIntensity = applyBlendFalloff(bIntensity, t, u_blendFalloff);
5260    float tB = blendLayerT(vUv, t, u_resolution, u_direction, bOffset, bRotation, u_blendScale);
5261    vec3 blendCol = mixColors(u_colors, u_colorPositions, u_colorCount, tB);
5262    color = applyBlendMode(u_blendMode, color, blendCol, bIntensity);
5263    if (u_blendDualLayer > 0 && u_blendMode2 > 0) {
5264      float tB2 = blendLayerT(vUv, t, u_resolution, u_direction, u_blendOffset2, u_blendRotation2, u_blendScale2);
5265      vec3 blendCol2 = mixColors(u_colors, u_colorPositions, u_colorCount, tB2);
5266      color = applyBlendMode(u_blendMode2, color, blendCol2, u_blendIntensity2);
5267    }
5268  }
5269
5270  fragColor = outputColor(color, gl_FragCoord.xy, 1.0);
5271}
5272`,x=`#version 300 es
5273precision highp float;
5274out vec2 vUv;
5275out vec2 vL;
5276out vec2 vR;
5277out vec2 vT;
5278out vec2 vB;
5279uniform vec2 u_texelSize;
5280void main() {
5281  float x = float((gl_VertexID & 1) << 2);
5282  float y = float((gl_VertexID & 2) << 1);
5283  vUv = vec2(x * 0.5, y * 0.5);
5284  vL = vUv - vec2(u_texelSize.x, 0.0);
5285  vR = vUv + vec2(u_texelSize.x, 0.0);
5286  vT = vUv + vec2(0.0, u_texelSize.y);
5287  vB = vUv - vec2(0.0, u_texelSize.y);
5288  gl_Position = vec4(x - 1.0, y - 1.0, 0.0, 1.0);
5289}
5290`,C=`#version 300 es
5291precision highp float;
5292in vec2 vUv;
5293out vec4 fragColor;
5294uniform sampler2D u_target;
5295uniform vec2 u_texelSize;
5296uniform vec3 u_color;
5297uniform vec2 u_point;
5298uniform float u_radius;
5299void main() {
5300  vec2 p = vUv - u_point;
5301  p.x *= u_texelSize.y / u_texelSize.x;
5302  vec3 splat = exp(-dot(p, p) / u_radius) * u_color;
5303  vec3 base = texture(u_target, vUv).xyz;
5304  fragColor = vec4(base + splat, 1.0);
5305}
5306`,w=`#version 300 es
5307precision highp float;
5308in vec2 vUv;
5309out vec4 fragColor;
5310uniform sampler2D u_velocity;
5311uniform sampler2D u_source;
5312uniform vec2 u_texelSize;
5313uniform float u_dt;
5314uniform float u_dissipation;
5315
5316vec4 bilerp(sampler2D sam, vec2 uv, vec2 tsize) {
5317  vec2 st = uv / tsize - 0.5;
5318  vec2 iuv = floor(st);
5319  vec2 fuv = fract(st);
5320  vec4 a = texture(sam, (iuv + vec2(0.5, 0.5)) * tsize);
5321  vec4 b = texture(sam, (iuv + vec2(1.5, 0.5)) * tsize);
5322  vec4 c = texture(sam, (iuv + vec2(0.5, 1.5)) * tsize);
5323  vec4 d = texture(sam, (iuv + vec2(1.5, 1.5)) * tsize);
5324  return mix(mix(a, b, fuv.x), mix(c, d, fuv.x), fuv.y);
5325}
5326
5327void main() {
5328  vec2 coord = vUv - u_dt * bilerp(u_velocity, vUv, u_texelSize).xy * u_texelSize;
5329  vec4 result = u_dissipation * bilerp(u_source, coord, u_texelSize);
5330  result.a = 1.0;
5331  fragColor = result;
5332}
5333`,S=`#version 300 es
5334precision highp float;
5335in vec2 vUv;
5336in vec2 vL;
5337in vec2 vR;
5338in vec2 vT;
5339in vec2 vB;
5340out vec4 fragColor;
5341uniform sampler2D u_velocity;
5342void main() {
5343  float L = texture(u_velocity, vL).x;
5344  float R = texture(u_velocity, vR).x;
5345  float T = texture(u_velocity, vT).y;
5346  float B = texture(u_velocity, vB).y;
5347  float div = 0.5 * (R - L + T - B);
5348  fragColor = vec4(div, 0.0, 0.0, 1.0);
5349}
5350`,A=`#version 300 es
5351precision highp float;
5352in vec2 vUv;
5353in vec2 vL;
5354in vec2 vR;
5355in vec2 vT;
5356in vec2 vB;
5357out vec4 fragColor;
5358uniform sampler2D u_velocity;
5359void main() {
5360  float L = texture(u_velocity, vL).y;
5361  float R = texture(u_velocity, vR).y;
5362  float T = texture(u_velocity, vT).x;
5363  float B = texture(u_velocity, vB).x;
5364  float vorticity = R - L - T + B;
5365  fragColor = vec4(0.5 * vorticity, 0.0, 0.0, 1.0);
5366}
5367`,I=`#version 300 es
5368precision highp float;
5369in vec2 vUv;
5370in vec2 vL;
5371in vec2 vR;
5372in vec2 vT;
5373in vec2 vB;
5374out vec4 fragColor;
5375uniform sampler2D u_velocity;
5376uniform sampler2D u_curl;
5377uniform float u_strength;
5378uniform float u_dt;
5379void main() {
5380  float L = texture(u_curl, vL).x;
5381  float R = texture(u_curl, vR).x;
5382  float T = texture(u_curl, vT).x;
5383  float B = texture(u_curl, vB).x;
5384  float C = texture(u_curl, vUv).x;
5385  vec2 force = 0.5 * vec2(abs(T) - abs(B), abs(R) - abs(L));
5386  force /= length(force) + 0.0001;
5387  force *= u_strength * C;
5388  force.y *= -1.0;
5389  vec2 velocity = texture(u_velocity, vUv).xy;
5390  velocity += force * u_dt;
5391  fragColor = vec4(velocity, 0.0, 1.0);
5392}
5393`,M=`#version 300 es
5394precision highp float;
5395in vec2 vUv;
5396in vec2 vL;
5397in vec2 vR;
5398in vec2 vT;
5399in vec2 vB;
5400out vec4 fragColor;
5401uniform sampler2D u_pressure;
5402uniform sampler2D u_divergence;
5403void main() {
5404  float L = texture(u_pressure, vL).x;
5405  float R = texture(u_pressure, vR).x;
5406  float T = texture(u_pressure, vT).x;
5407  float B = texture(u_pressure, vB).x;
5408  float divergence = texture(u_divergence, vUv).x;
5409  float pressure = (L + R + B + T - divergence) * 0.25;
5410  fragColor = vec4(pressure, 0.0, 0.0, 1.0);
5411}
5412`,R=`#version 300 es
5413precision highp float;
5414in vec2 vUv;
5415in vec2 vL;
5416in vec2 vR;
5417in vec2 vT;
5418in vec2 vB;
5419out vec4 fragColor;
5420uniform sampler2D u_pressure;
5421uniform sampler2D u_velocity;
5422void main() {
5423  float L = texture(u_pressure, vL).x;
5424  float R = texture(u_pressure, vR).x;
5425  float T = texture(u_pressure, vT).x;
5426  float B = texture(u_pressure, vB).x;
5427  vec2 velocity = texture(u_velocity, vUv).xy;
5428  velocity -= vec2(R - L, T - B);
5429  fragColor = vec4(velocity, 0.0, 1.0);
5430}
5431`,B=`#version 300 es
5432precision highp float;
5433in vec2 vUv;
5434out vec4 fragColor;
5435uniform sampler2D u_pressure;
5436uniform float u_value;
5437void main() {
5438  fragColor = u_value * texture(u_pressure, vUv);
5439}
5440`,D=`#version 300 es
5441precision highp float;
5442
5443in vec2 vUv;
5444out vec4 fragColor;
5445
5446uniform float u_time;
5447uniform float u_speed;
5448uniform vec3 u_colors[12];
5449uniform float u_colorPositions[12];
5450uniform int u_colorCount;
5451uniform float u_direction;
5452uniform float u_grain;
5453uniform vec2 u_resolution;
5454
5455// Blend uniforms
5456uniform int u_blendMode;
5457uniform float u_blendIntensity;
5458uniform float u_blendOffset;
5459uniform float u_blendRotation;
5460uniform float u_blendScale;
5461uniform int u_blendAnimRotation;
5462uniform int u_blendAnimIntensity;
5463uniform int u_blendAnimOffset;
5464uniform float u_blendAnimSpeed;
5465uniform float u_blendNoiseMask;
5466uniform float u_blendNoiseScale;
5467uniform float u_blendFalloff;
5468uniform int u_blendDualLayer;
5469uniform int u_blendMode2;
5470uniform float u_blendIntensity2;
5471uniform float u_blendOffset2;
5472uniform float u_blendRotation2;
5473uniform float u_blendScale2;
5474
5475// Fluid sim display — dye texture from simulation
5476uniform sampler2D u_dyeTexture;
5477
5478// Mode-specific
5479uniform int u_simResolution;
5480uniform int u_dyeResolution;
5481uniform float u_densityDissipation;
5482uniform float u_velocityDissipation;
5483uniform int u_pressureIterations;
5484uniform float u_splatRadius;
5485uniform float u_splatForce;
5486uniform int u_autoAnimate;
5487uniform float u_curl;
5488
5489__COMMON__
5490
5491void main() {
5492  float time = u_time * u_speed;
5493
5494  // Read dye from fluid simulation
5495  vec3 dye = texture(u_dyeTexture, vUv).rgb;
5496
5497  // Map dye intensity to gradient color
5498  float dyeIntensity = max(dye.r, max(dye.g, dye.b));
5499  float t = clamp(dyeIntensity, 0.0, 1.0);
5500
5501  // Use dye color channel ratios to shift the t position for variety
5502  float colorBias = 0.0;
5503  if (dyeIntensity > 0.01) {
5504    colorBias = (dye.r - dye.b) / (dyeIntensity * 3.0);
5505  }
5506  t = clamp(t + colorBias * 0.2, 0.0, 1.0);
5507
5508  vec3 color = mixColors(u_colors, u_colorPositions, u_colorCount, t);
5509
5510  // Modulate brightness by dye intensity
5511  float brightness = pow(dyeIntensity, 0.7);
5512  color *= brightness;
5513
5514  // Background: faint gradient where no dye
5515  float bgFade = 1.0 - brightness;
5516  float angle = radians(u_direction);
5517  vec2 dir = vec2(cos(angle), sin(angle));
5518  float bgT = dot(vUv - 0.5, dir) + 0.5;
5519  vec3 bgColor = mixColors(u_colors, u_colorPositions, u_colorCount, clamp(bgT, 0.0, 1.0));
5520  color += bgColor * bgFade * 0.12;
5521
5522  // Add grain
5523  color += grain(vUv, time, u_grain);
5524
5525  // Self-blend
5526  if (u_blendMode > 0) {
5527    float bRotation = u_blendAnimRotation > 0 ? animatedBlendRotation(u_blendRotation, time, u_blendAnimSpeed) : u_blendRotation;
5528    float bOffset = u_blendAnimOffset > 0 ? animatedBlendOffset(u_blendOffset, time, u_blendAnimSpeed) : u_blendOffset;
5529    float bIntensity = u_blendAnimIntensity > 0 ? animatedBlendIntensity(u_blendIntensity, time, u_blendAnimSpeed) : u_blendIntensity;
5530    bIntensity = noiseModulatedIntensity(bIntensity, vUv, time, u_blendNoiseMask, u_blendNoiseScale);
5531    bIntensity = applyBlendFalloff(bIntensity, t, u_blendFalloff);
5532    float tB = blendLayerT(vUv, t, u_resolution, u_direction, bOffset, bRotation, u_blendScale);
5533    vec3 blendCol = mixColors(u_colors, u_colorPositions, u_colorCount, tB);
5534    color = applyBlendMode(u_blendMode, color, blendCol, bIntensity);
5535    if (u_blendDualLayer > 0 && u_blendMode2 > 0) {
5536      float tB2 = blendLayerT(vUv, t, u_resolution, u_direction, u_blendOffset2, u_blendRotation2, u_blendScale2);
5537      vec3 blendCol2 = mixColors(u_colors, u_colorPositions, u_colorCount, tB2);
5538      color = applyBlendMode(u_blendMode2, color, blendCol2, u_blendIntensity2);
5539    }
5540  }
5541
5542  fragColor = outputColor(color, gl_FragCoord.xy, 1.0);
5543}
5544`;e.s(["FLUID_ADVECTION_FRAGMENT",0,w,"FLUID_CLEAR_FRAGMENT",0,B,"FLUID_CURL_FRAGMENT",0,A,"FLUID_DIVERGENCE_FRAGMENT",0,S,"FLUID_GRADIENT_SUBTRACT_FRAGMENT",0,R,"FLUID_PRESSURE_FRAGMENT",0,M,"FLUID_SIM_FRAGMENT",0,D,"FLUID_SPLAT_FRAGMENT",0,C,"FLUID_VERTEX",0,x,"FLUID_VORTICITY_FRAGMENT",0,I],19529);let k=`#version 300 es
5545precision highp float;
5546
5547in vec2 vUv;
5548out vec4 fragColor;
5549
5550uniform float u_time;
5551uniform float u_speed;
5552uniform vec3 u_colors[12];
5553uniform float u_colorPositions[12];
5554uniform int u_colorCount;
5555uniform float u_direction;
5556uniform float u_grain;
5557uniform vec2 u_resolution;
5558
5559// Blend uniforms
5560uniform int u_blendMode;
5561uniform float u_blendIntensity;
5562uniform float u_blendOffset;
5563uniform float u_blendRotation;
5564uniform float u_blendScale;
5565uniform int u_blendAnimRotation;
5566uniform int u_blendAnimIntensity;
5567uniform int u_blendAnimOffset;
5568uniform float u_blendAnimSpeed;
5569uniform float u_blendNoiseMask;
5570uniform float u_blendNoiseScale;
5571uniform float u_blendFalloff;
5572uniform int u_blendDualLayer;
5573uniform int u_blendMode2;
5574uniform float u_blendIntensity2;
5575uniform float u_blendOffset2;
5576uniform float u_blendRotation2;
5577uniform float u_blendScale2;
5578
5579// Mode-specific
5580uniform int u_warpStyle;        // 0=tunnel, 1=vortex, 2=hyperdrive, 3=nebula
5581uniform float u_warpSpeed;
5582uniform float u_nebulaDensity;
5583uniform float u_starBrightness;
5584uniform float u_tunnelRadius;
5585uniform float u_vortexStrength;
5586uniform float u_streakLength;
5587uniform float u_centerDarkness;
5588uniform float u_vignette;
5589uniform float u_chromaticShift;
5590
5591__COMMON__
5592
5593// Value noise for nebula generation
5594float vnoise(vec2 st) {
5595  vec2 i = floor(st);
5596  vec2 f = fract(st);
5597  float a = fract(sin(dot(i, vec2(12.9898, 78.233))) * 43758.5453);
5598  float b = fract(sin(dot(i + vec2(1.0, 0.0), vec2(12.9898, 78.233))) * 43758.5453);
5599  float c = fract(sin(dot(i + vec2(0.0, 1.0), vec2(12.9898, 78.233))) * 43758.5453);
5600  float d = fract(sin(dot(i + vec2(1.0, 1.0), vec2(12.9898, 78.233))) * 43758.5453);
5601  vec2 u = f * f * (3.0 - 2.0 * f);
5602  return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
5603}
5604
5605float vfbm(vec2 st, int octaves) {
5606  float value = 0.0;
5607  float amp = 0.5;
5608  for (int i = 0; i < 6; i++) {
5609    if (i >= octaves) break;
5610    value += amp * vnoise(st);
5611    st *= 2.0;
5612    amp *= 0.5;
5613  }
5614  return value;
5615}
5616
5617void main() {
5618  vec2 uv = (vUv - 0.5) * 2.0;
5619  float aspect = u_resolution.x / u_resolution.y;
5620  uv.x *= aspect;
5621  float time = u_time * u_speed * u_warpSpeed;
5622
5623  // Mouse influence — shifts tunnel center
5624  vec2 mouseOffset = (u_mouse * 0.5 + 0.5 - 0.5) * 0.4;
5625  if (u_mouseMode > 0) {
5626    uv -= mouseOffset;
5627  }
5628
5629  // Polar coordinates
5630  float r = length(uv);
5631  float a = atan(uv.y, uv.x);
5632
5633  // Warp styles
5634  float tunnelCoord;
5635  float angleCoord;
5636
5637  if (u_warpStyle == 0) {
5638    // Tunnel: classic 1/r mapping flowing forward
5639    tunnelCoord = u_tunnelRadius / (r + 0.001) + time;
5640    angleCoord = a / 3.14159265;
5641  } else if (u_warpStyle == 1) {
5642    // Vortex: spiral inward
5643    tunnelCoord = u_tunnelRadius / (r + 0.001) + time;
5644    angleCoord = a / 3.14159265 + time * u_vortexStrength + r * u_vortexStrength * 2.0;
5645  } else if (u_warpStyle == 2) {
5646    // Hyperdrive: elongated streaks
5647    tunnelCoord = u_tunnelRadius / (r + 0.001) + time * 2.0;
5648    angleCoord = a / 3.14159265;
5649  } else {
5650    // Nebula: slow drift through gas clouds
5651    tunnelCoord = u_tunnelRadius / (r + 0.001) + time * 0.3;
5652    angleCoord = a / 3.14159265 + sin(time * 0.2) * 0.1;
5653  }
5654
5655  vec2 polarUv = vec2(tunnelCoord, angleCoord);
5656
5657  // Nebula / star noise
5658  float streakMult = u_warpStyle == 2 ? u_streakLength * 3.0 : 1.0;
5659  float n = vfbm(polarUv * vec2(streakMult, 10.0) * u_nebulaDensity, 4);
5660
5661  // Stars: sharp bright points
5662  float stars = pow(n, 4.0) * 2.0 * u_starBrightness;
5663
5664  // Nebula: soft cloud layer
5665  float nebula = smoothstep(0.35, 0.75, n);
5666
5667  // Secondary detail layer
5668  float detail = vfbm(polarUv * vec2(2.0 * streakMult, 20.0) * u_nebulaDensity + 7.3, 3);
5669  float fineStars = pow(detail, 6.0) * u_starBrightness * 0.5;
5670
5671  // Map to gradient colors
5672  float colorT = clamp(nebula + stars * 0.3, 0.0, 1.0);
5673  vec3 nebulaColor = mixColors(u_colors, u_colorPositions, u_colorCount, colorT);
5674
5675  // Compose
5676  vec3 color = vec3(0.0);
5677  color += nebulaColor * nebula * 0.6;
5678  color += vec3(1.0) * stars; // White-hot stars
5679  color += vec3(0.8, 0.9, 1.0) * fineStars;
5680
5681  // Chromatic aberration on nebula
5682  if (u_chromaticShift > 0.0) {
5683    float nR = vfbm(polarUv * vec2(streakMult, 10.0) * u_nebulaDensity + vec2(u_chromaticShift, 0.0), 4);
5684    float nB = vfbm(polarUv * vec2(streakMult, 10.0) * u_nebulaDensity - vec2(u_chromaticShift, 0.0), 4);
5685    float nebR = smoothstep(0.35, 0.75, nR);
5686    float nebB = smoothstep(0.35, 0.75, nB);
5687    vec3 colR = mixColors(u_colors, u_colorPositions, u_colorCount, clamp(nebR, 0.0, 1.0));
5688    vec3 colB = mixColors(u_colors, u_colorPositions, u_colorCount, clamp(nebB, 0.0, 1.0));
5689    color.r = mix(color.r, colR.r * nebR * 0.6, 0.3);
5690    color.b = mix(color.b, colB.b * nebB * 0.6, 0.3);
5691  }
5692
5693  // Center darkness (black hole effect)
5694  float centerFade = smoothstep(0.0, u_centerDarkness, r);
5695  color *= centerFade;
5696
5697  // Edge vignette
5698  float vign = 1.0 - smoothstep(0.5, 1.5 - u_vignette * 0.5, length(vUv - 0.5));
5699  color *= vign;
5700
5701  // Depth-based fade (distant things dimmer)
5702  float depthFade = smoothstep(0.0, 0.15, r) * (1.0 - smoothstep(1.5, 3.0, r));
5703  color *= depthFade;
5704
5705  float t = clamp(colorT, 0.0, 1.0);
5706
5707  // Add grain
5708  color += grain(vUv, time, u_grain);
5709
5710  // Self-blend
5711  if (u_blendMode > 0) {
5712    float bRotation = u_blendAnimRotation > 0 ? animatedBlendRotation(u_blendRotation, time, u_blendAnimSpeed) : u_blendRotation;
5713    float bOffset = u_blendAnimOffset > 0 ? animatedBlendOffset(u_blendOffset, time, u_blendAnimSpeed) : u_blendOffset;
5714    float bIntensity = u_blendAnimIntensity > 0 ? animatedBlendIntensity(u_blendIntensity, time, u_blendAnimSpeed) : u_blendIntensity;
5715    bIntensity = noiseModulatedIntensity(bIntensity, vUv, time, u_blendNoiseMask, u_blendNoiseScale);
5716    bIntensity = applyBlendFalloff(bIntensity, t, u_blendFalloff);
5717    float tB = blendLayerT(vUv, t, u_resolution, u_direction, bOffset, bRotation, u_blendScale);
5718    vec3 blendCol = mixColors(u_colors, u_colorPositions, u_colorCount, tB);
5719    color = applyBlendMode(u_blendMode, color, blendCol, bIntensity);
5720    if (u_blendDualLayer > 0 && u_blendMode2 > 0) {
5721      float tB2 = blendLayerT(vUv, t, u_resolution, u_direction, u_blendOffset2, u_blendRotation2, u_blendScale2);
5722      vec3 blendCol2 = mixColors(u_colors, u_colorPositions, u_colorCount, tB2);
5723      color = applyBlendMode(u_blendMode2, color, blendCol2, u_blendIntensity2);
5724    }
5725  }
5726
5727  fragColor = outputColor(color, gl_FragCoord.xy, 1.0);
5728}
5729`;var O=e.i(607490);let T=`#version 300 es
5730precision highp float;
5731
5732in vec2 vUv;
5733out vec4 fragColor;
5734
5735uniform float u_time;
5736uniform float u_speed;
5737uniform vec3 u_colors[12];
5738uniform float u_colorPositions[12];
5739uniform int u_colorCount;
5740uniform float u_direction;
5741uniform float u_grain;
5742uniform vec2 u_resolution;
5743
5744// Blend uniforms
5745uniform int u_blendMode;
5746uniform float u_blendIntensity;
5747uniform float u_blendOffset;
5748uniform float u_blendRotation;
5749uniform float u_blendScale;
5750uniform int u_blendAnimRotation;
5751uniform int u_blendAnimIntensity;
5752uniform int u_blendAnimOffset;
5753uniform float u_blendAnimSpeed;
5754uniform float u_blendNoiseMask;
5755uniform float u_blendNoiseScale;
5756uniform float u_blendFalloff;
5757uniform int u_blendDualLayer;
5758uniform int u_blendMode2;
5759uniform float u_blendIntensity2;
5760uniform float u_blendOffset2;
5761uniform float u_blendRotation2;
5762uniform float u_blendScale2;
5763
5764// Mode-specific uniforms
5765uniform float u_viscosity;
5766uniform float u_flowSpeed;
5767uniform float u_turbulence;
5768uniform float u_thickness;
5769uniform float u_organicScale;
5770uniform int u_layerCount;
5771uniform float u_separation;
5772uniform float u_sheen;
5773
5774__COMMON__
5775
5776void main() {
5777  vec2 uv = vUv;
5778  float aspect = u_resolution.x / max(u_resolution.y, 1.0);
5779  uv = applyMouseWarp(uv, aspect);
5780  float time = u_time * u_speed * u_flowSpeed;
5781
5782  vec2 p = uv - 0.5;
5783  p.x *= aspect;
5784
5785  // ---- Recursive domain warp (double domain warp) ----
5786  // Creates complex, flowing organic patterns like ink in water
5787  vec2 q = p * u_organicScale;
5788
5789  // First warp layer: two FBM channels create a 2D displacement field
5790  vec2 w1 = vec2(
5791    fbm(q + vec2(0.0, time * 0.1), 1.0, 3, 0.5, 2.0),
5792    fbm(q + vec2(5.2, -time * 0.08), 1.0, 3, 0.5, 2.0)
5793  );
5794
5795  // Second warp layer: warped by first for recursive complexity
5796  float visc = u_viscosity * 4.0;
5797  vec2 w2 = vec2(
5798    fbm(q + visc * w1 + vec2(12.3, 0.0), 1.0, 3, 0.5, 2.0),
5799    fbm(q - visc * w1 + vec2(8.2, 0.0), 1.0, 3, 0.5, 2.0)
5800  );
5801
5802  // Final flow field from double-warped coordinates
5803  float turb = u_turbulence * 4.8;
5804  float n = fbm(q + turb * w2, 1.0, 4, 0.5, 2.0);
5805
5806  // ---- Subsurface rim glow via FBM-derived normal ----
5807  float nDx = fbm(q + turb * w2 + vec2(0.02, 0.0), 1.0, 1, 0.5, 2.0);
5808  float nDy = fbm(q + turb * w2 + vec2(0.0, 0.02), 1.0, 1, 0.5, 2.0);
5809  vec3 surfN = normalize(vec3(n - nDx, n - nDy, 0.22));
5810
5811  // Fresnel rim
5812  float fresnel = pow(1.0 - max(dot(surfN, vec3(0.0, 0.0, 1.0)), 0.0), 2.8);
5813
5814  // Specular highlight (traveling light lobe)
5815  vec3 lightDir = normalize(vec3(-0.4 + 0.3 * sin(time * 0.3), 0.65, 0.6));
5816  vec3 halfVec = normalize(lightDir + vec3(0.0, 0.0, 1.0));
5817  float spec = pow(max(dot(surfN, halfVec), 0.0), 46.0);
5818
5819  // ---- Color mapping ----
5820  float angle = radians(u_direction);
5821  vec2 dir = vec2(cos(angle), sin(angle));
5822  float baseT = dot(uv - 0.5, dir) + 0.5;
5823
5824  float imgLum = imageLuminance(uv, aspect);
5825  if (imgLum >= 0.0) baseT = mix(baseT, imgLum, u_imageIntensity);
5826
5827  float t = clamp(baseT + n * 0.35, 0.0, 1.0);
5828  vec3 color = mixColors(u_colors, u_colorPositions, u_colorCount, t);
5829
5830  // Subsurface rim glow: bright edge color + translucency
5831  vec3 rimCol = mixColors(u_colors, u_colorPositions, u_colorCount, clamp(t + 0.3, 0.0, 1.0));
5832  color = mix(color, rimCol, fresnel * u_sheen);
5833  color += fresnel * 0.25 * u_sheen;
5834
5835  // Specular highlight
5836  color += spec * (rimCol + vec3(0.5)) * 0.55 * u_sheen;
5837
5838  // ---- Optional depth layers (subtle variation) ----
5839  int layers = clamp(u_layerCount, 1, 4);
5840  if (layers > 1) {
5841    for (int i = 1; i < 4; i++) {
5842      if (i >= layers) break;
5843      float fi = float(i);
5844      float layerN = fbm(q + turb * w2 + vec2(fi * 2.39, fi * 3.17), 1.0, 3, 0.4, 2.0);
5845      float layerT = clamp(baseT + layerN * 0.3 + fi * u_separation * 0.15, 0.0, 1.0);
5846      vec3 layerCol = mixColors(u_colors, u_colorPositions, u_colorCount, layerT);
5847      float layerMask = smoothstep(0.5 - u_thickness * 0.3, 0.5 + u_thickness * 0.3, layerN * 0.5 + 0.5);
5848      color = mix(color, layerCol, layerMask * 0.25);
5849    }
5850  }
5851
5852  color += grain(uv, time, u_grain);
5853
5854  // Self-blend
5855  if (u_blendMode > 0) {
5856    float bRotation = u_blendAnimRotation > 0 ? animatedBlendRotation(u_blendRotation, time, u_blendAnimSpeed) : u_blendRotation;
5857    float bOffset = u_blendAnimOffset > 0 ? animatedBlendOffset(u_blendOffset, time, u_blendAnimSpeed) : u_blendOffset;
5858    float bIntensity = u_blendAnimIntensity > 0 ? animatedBlendIntensity(u_blendIntensity, time, u_blendAnimSpeed) : u_blendIntensity;
5859    bIntensity = noiseModulatedIntensity(bIntensity, uv, time, u_blendNoiseMask, u_blendNoiseScale);
5860    bIntensity = applyBlendFalloff(bIntensity, baseT, u_blendFalloff);
5861    float tB = blendLayerT(uv, baseT, u_resolution, u_direction, bOffset, bRotation, u_blendScale);
5862    vec3 blendCol = mixColors(u_colors, u_colorPositions, u_colorCount, tB);
5863    color = applyBlendMode(u_blendMode, color, blendCol, bIntensity);
5864    if (u_blendDualLayer > 0 && u_blendMode2 > 0) {
5865      float tB2 = blendLayerT(uv, baseT, u_resolution, u_direction, u_blendOffset2, u_blendRotation2, u_blendScale2);
5866      vec3 blendCol2 = mixColors(u_colors, u_colorPositions, u_colorCount, tB2);
5867      color = applyBlendMode(u_blendMode2, color, blendCol2, u_blendIntensity2);
5868    }
5869  }
5870
5871  fragColor = outputColor(color, gl_FragCoord.xy, 1.0);
5872}
5873`,P=`#version 300 es
5874precision highp float;
5875
5876in vec2 vUv;
5877out vec4 fragColor;
5878
5879uniform float u_time;
5880uniform float u_speed;
5881uniform vec3 u_colors[12];
5882uniform float u_colorPositions[12];
5883uniform int u_colorCount;
5884uniform float u_direction;
5885uniform float u_grain;
5886uniform vec2 u_resolution;
5887
5888// Blend uniforms
5889uniform int u_blendMode;
5890uniform float u_blendIntensity;
5891uniform float u_blendOffset;
5892uniform float u_blendRotation;
5893uniform float u_blendScale;
5894uniform int u_blendAnimRotation;
5895uniform int u_blendAnimIntensity;
5896uniform int u_blendAnimOffset;
5897uniform float u_blendAnimSpeed;
5898uniform float u_blendNoiseMask;
5899uniform float u_blendNoiseScale;
5900uniform float u_blendFalloff;
5901uniform int u_blendDualLayer;
5902uniform int u_blendMode2;
5903uniform float u_blendIntensity2;
5904uniform float u_blendOffset2;
5905uniform float u_blendRotation2;
5906uniform float u_blendScale2;
5907
5908// Mode-specific uniforms
5909uniform float u_prismScale;
5910uniform float u_dispersion;
5911uniform float u_ior;
5912uniform int u_facets;
5913uniform float u_clarity;
5914uniform float u_sparkle;
5915uniform float u_rotation;
5916uniform float u_depth;
5917
5918__COMMON__
5919
5920void main() {
5921  vec2 uv = vUv;
5922  float time = u_time * u_speed;
5923  float aspect = u_resolution.x / max(u_resolution.y, 1.0);
5924  uv = applyMouseWarp(uv, aspect);
5925
5926  // Apply global rotation
5927  float rotRad = radians(u_rotation) + time * 0.02;
5928  vec2 center = vec2(0.5);
5929  vec2 rotUv = uv - center;
5930  rotUv = vec2(
5931    rotUv.x * cos(rotRad) - rotUv.y * sin(rotRad),
5932    rotUv.x * sin(rotRad) + rotUv.y * cos(rotRad)
5933  );
5934  rotUv += center;
5935
5936  vec2 p = rotUv - 0.5;
5937  p.x *= aspect;
5938
5939  // Base gradient direction
5940  float dirAngle = radians(u_direction);
5941  vec2 dir = vec2(cos(dirAngle), sin(dirAngle));
5942  float baseT = dot(uv - 0.5, dir) + 0.5;
5943
5944  float imgLum = imageLuminance(uv, aspect);
5945  if (imgLum >= 0.0) baseT = mix(baseT, imgLum, u_imageIntensity);
5946
5947  // ---- Domain-warped glass surface ----
5948  // u_facets controls surface complexity (more = more spectral folds)
5949  float facetScale = float(max(u_facets, 1)) * 0.5;
5950  vec2 q = p * u_prismScale * facetScale;
5951
5952  // Aurora-like cascading domain warp for flowing glass
5953  float warp1 = snoise(q * 1.5 + time * 0.1);
5954  float warp2 = snoise(q * 3.0 - time * 0.07);
5955  q += vec2(warp1, warp2) * u_depth * 0.4;
5956
5957  // Glass surface height field via FBM
5958  float n = fbm(q, 1.0, 5, 0.5, 2.0);
5959
5960  // ---- Surface normal for refraction direction ----
5961  float nDx = fbm(q + vec2(0.02, 0.0), 1.0, 2, 0.5, 2.0);
5962  float nDy = fbm(q + vec2(0.0, 0.02), 1.0, 2, 0.5, 2.0);
5963  vec3 surfN = normalize(vec3(n - nDx, n - nDy, u_clarity * 0.3 + 0.05));
5964
5965  // ---- Chromatic dispersion: R/G/B refracted at different angles ----
5966  // The surface normal's xy magnitude drives how much light separates
5967  float refractStr = length(surfN.xy) * u_dispersion;
5968
5969  // Each color channel refracts at a different IOR
5970  float iorSpread = 1.0 + u_ior * 0.3;
5971  float tR = clamp(baseT + refractStr * iorSpread, 0.0, 1.0);
5972  float tG = clamp(baseT + refractStr * 0.1, 0.0, 1.0);
5973  float tB = clamp(baseT - refractStr * iorSpread, 0.0, 1.0);
5974
5975  vec3 color = vec3(
5976    mixColors(u_colors, u_colorPositions, u_colorCount, tR).r,
5977    mixColors(u_colors, u_colorPositions, u_colorCount, tG).g,
5978    mixColors(u_colors, u_colorPositions, u_colorCount, tB).b
5979  );
5980
5981  // ---- Caustic brightness where refraction is strongest ----
5982  float caustic = pow(length(surfN.xy), 2.0) * u_dispersion;
5983  color += color * caustic * 1.5;
5984
5985  // ---- Fresnel rim glow (IOR affects intensity) ----
5986  float fresnel = pow(1.0 - max(dot(surfN, vec3(0.0, 0.0, 1.0)), 0.0), 1.5 + u_ior * 2.0);
5987  vec3 rimCol = mixColors(u_colors, u_colorPositions, u_colorCount, clamp(baseT + 0.25, 0.0, 1.0));
5988  color += rimCol * fresnel * 0.4;
5989  color += fresnel * 0.2;
5990
5991  // ---- Sparkle highlights from animated light ----
5992  if (u_sparkle > 0.0) {
5993    vec3 lightDir = normalize(vec3(
5994      sin(time * 0.17) * 0.6,
5995      cos(time * 0.13) * 0.6,
5996      1.0
5997    ));
5998    float specAngle = max(dot(surfN, lightDir), 0.0);
5999    float sparkleVal = pow(specAngle, 32.0 + u_sparkle * 64.0) * u_sparkle;
6000
6001    // Secondary sparkle
6002    vec3 lightDir2 = normalize(vec3(
6003      cos(time * 0.23 + 1.5) * 0.5,
6004      sin(time * 0.19 + 2.3) * 0.5,
6005      1.0
6006    ));
6007    sparkleVal += pow(max(dot(surfN, lightDir2), 0.0), 48.0) * u_sparkle * 0.6;
6008
6009    color += vec3(sparkleVal) * 1.5;
6010  }
6011
6012  // Intra-surface color variation for depth
6013  float surfaceGrad = n * 0.5 + 0.5;
6014  float tIntra = clamp(baseT + surfaceGrad * 0.15, 0.0, 1.0);
6015  vec3 intraCol = mixColors(u_colors, u_colorPositions, u_colorCount, tIntra);
6016  color = mix(color, intraCol * (0.7 + caustic), 0.1);
6017
6018  color += grain(uv, time, u_grain);
6019
6020  // Self-blend
6021  if (u_blendMode > 0) {
6022    float bRotation = u_blendAnimRotation > 0 ? animatedBlendRotation(u_blendRotation, time, u_blendAnimSpeed) : u_blendRotation;
6023    float bOffset = u_blendAnimOffset > 0 ? animatedBlendOffset(u_blendOffset, time, u_blendAnimSpeed) : u_blendOffset;
6024    float bIntensity = u_blendAnimIntensity > 0 ? animatedBlendIntensity(u_blendIntensity, time, u_blendAnimSpeed) : u_blendIntensity;
6025    bIntensity = noiseModulatedIntensity(bIntensity, uv, time, u_blendNoiseMask, u_blendNoiseScale);
6026    bIntensity = applyBlendFalloff(bIntensity, baseT, u_blendFalloff);
6027    float tB = blendLayerT(uv, baseT, u_resolution, u_direction, bOffset, bRotation, u_blendScale);
6028    vec3 blendCol = mixColors(u_colors, u_colorPositions, u_colorCount, tB);
6029    color = applyBlendMode(u_blendMode, color, blendCol, bIntensity);
6030    if (u_blendDualLayer > 0 && u_blendMode2 > 0) {
6031      float tB2 = blendLayerT(uv, baseT, u_resolution, u_direction, u_blendOffset2, u_blendRotation2, u_blendScale2);
6032      vec3 blendCol2 = mixColors(u_colors, u_colorPositions, u_colorCount, tB2);
6033      color = applyBlendMode(u_blendMode2, color, blendCol2, u_blendIntensity2);
6034    }
6035  }
6036
6037  fragColor = outputColor(color, gl_FragCoord.xy, 1.0);
6038}
6039`,F=`#version 300 es
6040precision highp float;
6041
6042in vec2 vUv;
6043out vec4 fragColor;
6044
6045uniform float u_time;
6046uniform float u_speed;
6047uniform vec3 u_colors[12];
6048uniform float u_colorPositions[12];
6049uniform int u_colorCount;
6050uniform float u_direction;
6051uniform float u_grain;
6052uniform vec2 u_resolution;
6053
6054// Blend uniforms
6055uniform int u_blendMode;
6056uniform float u_blendIntensity;
6057uniform float u_blendOffset;
6058uniform float u_blendRotation;
6059uniform float u_blendScale;
6060uniform int u_blendAnimRotation;
6061uniform int u_blendAnimIntensity;
6062uniform int u_blendAnimOffset;
6063uniform float u_blendAnimSpeed;
6064uniform float u_blendNoiseMask;
6065uniform float u_blendNoiseScale;
6066uniform float u_blendFalloff;
6067uniform int u_blendDualLayer;
6068uniform int u_blendMode2;
6069uniform float u_blendIntensity2;
6070uniform float u_blendOffset2;
6071uniform float u_blendRotation2;
6072uniform float u_blendScale2;
6073
6074// Mode-specific uniforms
6075uniform float u_density;
6076uniform float u_coverage;
6077uniform float u_softness;
6078uniform float u_windSpeed;
6079uniform float u_lightAngle;
6080uniform float u_scattering;
6081uniform float u_altitude;
6082uniform float u_billowing;
6083
6084__COMMON__
6085
6086void main() {
6087  vec2 uv = vUv;
6088  float aspect = u_resolution.x / max(u_resolution.y, 1.0);
6089  uv = applyMouseWarp(uv, aspect);
6090  float time = u_time * u_speed;
6091
6092  // Aspect-correct and scale for altitude
6093  vec2 p = uv;
6094  p.x *= aspect;
6095  p *= mix(1.5, 0.6, u_altitude);
6096
6097  // Wind drift
6098  p += vec2(1.0, 0.3) * time * u_windSpeed * 0.15;
6099
6100  // ---- Aurora-like cascading domain warp ----
6101  float warp1 = snoise(p * 1.2 + time * 0.08);
6102  float warp2 = snoise(p * 2.4 - time * 0.06);
6103  p += vec2(warp1 + warp2 * 0.4) * 0.3 * u_billowing;
6104
6105  // ---- Cloud density via FBM ----
6106  float n = fbm(p, 1.2, 5, 0.6, 2.1);
6107  n = n * 0.5 + 0.5;
6108
6109  // Coverage threshold and edge softness
6110  float rawDensity = max(n - (1.0 - u_coverage), 0.0);
6111  float cloudDen = smoothstep(0.0, mix(0.02, 0.3, u_softness), rawDensity);
6112  cloudDen = clamp(cloudDen * u_density, 0.0, 1.0);
6113
6114  // ---- Subsurface rim glow via FBM-derived surface normal ----
6115  float nDx = fbm(p + vec2(0.02, 0.0), 1.2, 2, 0.6, 2.1) * 0.5 + 0.5;
6116  float nDy = fbm(p + vec2(0.0, 0.02), 1.2, 2, 0.6, 2.1) * 0.5 + 0.5;
6117  vec3 surfN = normalize(vec3(n - nDx, n - nDy, 0.22));
6118
6119  float fresnel = pow(1.0 - max(dot(surfN, vec3(0.0, 0.0, 1.0)), 0.0), 2.5);
6120
6121  // ---- Directional lighting ----
6122  float lightRad = radians(u_lightAngle);
6123  vec3 lightDir = normalize(vec3(cos(lightRad), sin(lightRad), 0.7));
6124  float diffuse = mix(0.35, 1.0, max(dot(surfN, lightDir), 0.0));
6125
6126  // Interior darkening (self-shadow)
6127  float interior = mix(1.0, 0.65, cloudDen * cloudDen);
6128
6129  // Forward scattering at cloud edges (silver lining)
6130  float scatter = pow(max(dot(-surfN.xy, lightDir.xy), 0.0), 2.5);
6131  float edgeMask = smoothstep(0.0, 0.2, cloudDen) * smoothstep(0.85, 0.2, cloudDen);
6132  scatter *= (1.0 - cloudDen) * edgeMask * u_scattering * 3.0;
6133
6134  // ---- Color mapping ----
6135  float dirAngle = radians(u_direction);
6136  vec2 dir = vec2(cos(dirAngle), sin(dirAngle));
6137  float baseT = dot(uv - 0.5, dir) + 0.5;
6138
6139  float imgLum = imageLuminance(uv, aspect);
6140  if (imgLum >= 0.0) baseT = mix(baseT, imgLum, u_imageIntensity);
6141
6142  // Cloud color: density shifts gradient position for depth
6143  float cloudT = clamp(baseT + (1.0 - cloudDen) * 0.25 - 0.12, 0.0, 1.0);
6144  vec3 cloudColor = mixColors(u_colors, u_colorPositions, u_colorCount, cloudT);
6145  cloudColor *= diffuse * interior;
6146
6147  // Subsurface rim glow on cloud edges
6148  vec3 rimColor = mixColors(u_colors, u_colorPositions, u_colorCount, clamp(baseT + 0.3, 0.0, 1.0));
6149  cloudColor += rimColor * fresnel * 0.5 * cloudDen;
6150
6151  // Forward scattering silver lining
6152  cloudColor += rimColor * scatter;
6153
6154  // Sky gradient (darker for contrast)
6155  vec3 skyColor = mixColors(u_colors, u_colorPositions, u_colorCount, clamp(baseT, 0.0, 1.0));
6156  skyColor *= mix(1.0, 0.7, uv.y * u_altitude) * 0.85;
6157
6158  // Composite cloud over sky
6159  vec3 color = mix(skyColor, cloudColor, cloudDen);
6160
6161  color += grain(uv, time, u_grain);
6162
6163  // Self-blend
6164  if (u_blendMode > 0) {
6165    float bRotation = u_blendAnimRotation > 0 ? animatedBlendRotation(u_blendRotation, time, u_blendAnimSpeed) : u_blendRotation;
6166    float bOffset = u_blendAnimOffset > 0 ? animatedBlendOffset(u_blendOffset, time, u_blendAnimSpeed) : u_blendOffset;
6167    float bIntensity = u_blendAnimIntensity > 0 ? animatedBlendIntensity(u_blendIntensity, time, u_blendAnimSpeed) : u_blendIntensity;
6168    bIntensity = noiseModulatedIntensity(bIntensity, uv, time, u_blendNoiseMask, u_blendNoiseScale);
6169    bIntensity = applyBlendFalloff(bIntensity, baseT, u_blendFalloff);
6170    float tB = blendLayerT(uv, baseT, u_resolution, u_direction, bOffset, bRotation, u_blendScale);
6171    vec3 blendCol = mixColors(u_colors, u_colorPositions, u_colorCount, tB);
6172    color = applyBlendMode(u_blendMode, color, blendCol, bIntensity);
6173    if (u_blendDualLayer > 0 && u_blendMode2 > 0) {
6174      float tB2 = blendLayerT(uv, baseT, u_resolution, u_direction, u_blendOffset2, u_blendRotation2, u_blendScale2);
6175      vec3 blendCol2 = mixColors(u_colors, u_colorPositions, u_colorCount, tB2);
6176      color = applyBlendMode(u_blendMode2, color, blendCol2, u_blendIntensity2);
6177    }
6178  }
6179
6180  fragColor = outputColor(color, gl_FragCoord.xy, 1.0);
6181}
6182`,L=`#version 300 es
6183precision highp float;
6184
6185in vec2 vUv;
6186out vec4 fragColor;
6187
6188uniform float u_time;
6189uniform float u_speed;
6190uniform vec3 u_colors[12];
6191uniform float u_colorPositions[12];
6192uniform int u_colorCount;
6193uniform float u_direction;
6194uniform float u_grain;
6195uniform vec2 u_resolution;
6196
6197// Blend uniforms
6198uniform int u_blendMode;
6199uniform float u_blendIntensity;
6200uniform float u_blendOffset;
6201uniform float u_blendRotation;
6202uniform float u_blendScale;
6203uniform int u_blendAnimRotation;
6204uniform int u_blendAnimIntensity;
6205uniform int u_blendAnimOffset;
6206uniform float u_blendAnimSpeed;
6207uniform float u_blendNoiseMask;
6208uniform float u_blendNoiseScale;
6209uniform float u_blendFalloff;
6210uniform int u_blendDualLayer;
6211uniform int u_blendMode2;
6212uniform float u_blendIntensity2;
6213uniform float u_blendOffset2;
6214uniform float u_blendRotation2;
6215uniform float u_blendScale2;
6216
6217// Mode-specific uniforms
6218uniform float u_swirlStrength;
6219uniform float u_swirlRadius;
6220uniform int u_spiralArms;
6221uniform float u_tightness;
6222uniform float u_distortion;
6223uniform float u_centerX;
6224uniform float u_centerY;
6225uniform float u_flowSpeed;
6226
6227__COMMON__
6228
6229#define TAU 6.2831853
6230
6231void main() {
6232  vec2 uv = vUv;
6233  float aspect = u_resolution.x / max(u_resolution.y, 1.0);
6234  uv = applyMouseWarp(uv, aspect);
6235  float time = u_time * u_speed;
6236  float flowTime = time * u_flowSpeed;
6237
6238  // Vortex center
6239  vec2 center = vec2(u_centerX, u_centerY);
6240  vec2 delta = uv - center;
6241  vec2 aspectDelta = vec2(delta.x * aspect, delta.y);
6242  float r = length(aspectDelta);
6243
6244  // Normalized radius
6245  float normR = r / max(u_swirlRadius, 0.01);
6246
6247  // ---- Core vortex: radius-dependent rotation ----
6248  // More rotation at center, less at edges (exponential falloff)
6249  float swirlAngle = u_swirlStrength * exp(-normR * u_tightness) * TAU;
6250  swirlAngle += sin(flowTime * 0.6) * 0.8; // animated oscillation
6251
6252  // Apply rotation to coordinates
6253  float cosA = cos(swirlAngle);
6254  float sinA = sin(swirlAngle);
6255  vec2 q = vec2(
6256    aspectDelta.x * cosA - aspectDelta.y * sinA,
6257    aspectDelta.x * sinA + aspectDelta.y * cosA
6258  );
6259
6260  // ---- Organic distortion via noise ----
6261  if (u_distortion > 0.0) {
6262    q += snoise(q * 2.0 + flowTime * 0.08) * u_distortion * 0.1;
6263  }
6264
6265  // ---- FBM in swirled space: creates flowing spiral texture ----
6266  // u_spiralArms scales the frequency for more visible spiral bands
6267  float armFreq = float(max(u_spiralArms, 1)) * 1.5;
6268  float n = fbm(q * armFreq + flowTime * vec2(0.1, -0.07), 1.0, 5, 0.5, 2.0);
6269
6270  // ---- Subsurface rim glow via FBM-derived normal ----
6271  float nDx = fbm(q * armFreq + flowTime * vec2(0.1, -0.07) + vec2(0.02, 0.0), 1.0, 2, 0.5, 2.0);
6272  float nDy = fbm(q * armFreq + flowTime * vec2(0.1, -0.07) + vec2(0.0, 0.02), 1.0, 2, 0.5, 2.0);
6273  vec3 surfN = normalize(vec3(n - nDx, n - nDy, 0.22));
6274
6275  float fresnel = pow(1.0 - max(dot(surfN, vec3(0.0, 0.0, 1.0)), 0.0), 2.8);
6276
6277  // ---- Color mapping ----
6278  float dirAngle = radians(u_direction);
6279  vec2 dir = vec2(cos(dirAngle), sin(dirAngle));
6280  float baseT = dot(uv - 0.5, dir) + 0.5;
6281
6282  float imgLum = imageLuminance(uv, aspect);
6283  if (imgLum >= 0.0) baseT = mix(baseT, imgLum, u_imageIntensity);
6284
6285  // Map FBM field to gradient (shifted by base direction)
6286  float field = n * 0.5 + 0.5;
6287  float t = clamp(baseT * 0.3 + field * 0.7, 0.0, 1.0);
6288  vec3 color = mixColors(u_colors, u_colorPositions, u_colorCount, t);
6289
6290  // Subsurface rim glow
6291  vec3 rimCol = mixColors(u_colors, u_colorPositions, u_colorCount, clamp(t + 0.3, 0.0, 1.0));
6292  color = mix(color, rimCol, fresnel * 0.4);
6293  color += fresnel * 0.2;
6294
6295  // ---- Radial fade: vortex brightest at center ----
6296  float radialFade = 1.0 - smoothstep(0.0, 1.2, normR);
6297  color *= mix(0.3, 1.0, radialFade);
6298
6299  // ---- Bright center eye ----
6300  float eyeRadius = 0.08 * u_swirlRadius;
6301  float eyeGlow = exp(-pow(r / max(eyeRadius, 0.001), 2.0) * 2.0);
6302  vec3 eyeBaseColor = mixColors(u_colors, u_colorPositions, u_colorCount, 0.5);
6303  vec3 eyeColor = mix(vec3(1.2), eyeBaseColor * 1.5, 0.3);
6304  color += eyeColor * eyeGlow * 2.0;
6305
6306  // Outer halo
6307  float haloR = normR * 3.0;
6308  color += eyeBaseColor * exp(-haloR * haloR) * 0.3;
6309
6310  color += grain(uv, time, u_grain);
6311
6312  // Self-blend
6313  if (u_blendMode > 0) {
6314    float bRotation = u_blendAnimRotation > 0 ? animatedBlendRotation(u_blendRotation, time, u_blendAnimSpeed) : u_blendRotation;
6315    float bOffset = u_blendAnimOffset > 0 ? animatedBlendOffset(u_blendOffset, time, u_blendAnimSpeed) : u_blendOffset;
6316    float bIntensity = u_blendAnimIntensity > 0 ? animatedBlendIntensity(u_blendIntensity, time, u_blendAnimSpeed) : u_blendIntensity;
6317    bIntensity = noiseModulatedIntensity(bIntensity, uv, time, u_blendNoiseMask, u_blendNoiseScale);
6318    bIntensity = applyBlendFalloff(bIntensity, baseT, u_blendFalloff);
6319    float tB = blendLayerT(uv, baseT, u_resolution, u_direction, bOffset, bRotation, u_blendScale);
6320    vec3 blendCol = mixColors(u_colors, u_colorPositions, u_colorCount, tB);
6321    color = applyBlendMode(u_blendMode, color, blendCol, bIntensity);
6322    if (u_blendDualLayer > 0 && u_blendMode2 > 0) {
6323      float tB2 = blendLayerT(uv, baseT, u_resolution, u_direction, u_blendOffset2, u_blendRotation2, u_blendScale2);
6324      vec3 blendCol2 = mixColors(u_colors, u_colorPositions, u_colorCount, tB2);
6325      color = applyBlendMode(u_blendMode2, color, blendCol2, u_blendIntensity2);
6326    }
6327  }
6328
6329  fragColor = outputColor(color, gl_FragCoord.xy, 1.0);
6330}
6331`,N=`#version 300 es
6332precision highp float;
6333
6334in vec2 vUv;
6335out vec4 fragColor;
6336
6337uniform float u_time;
6338uniform float u_speed;
6339uniform vec3 u_colors[12];
6340uniform float u_colorPositions[12];
6341uniform int u_colorCount;
6342uniform float u_direction;
6343uniform float u_grain;
6344uniform vec2 u_resolution;
6345
6346// Blend uniforms (declared for parity; unused by this image-driven mode)
6347uniform int u_blendMode;
6348uniform float u_blendIntensity;
6349uniform float u_blendOffset;
6350uniform float u_blendRotation;
6351uniform float u_blendScale;
6352uniform int u_blendAnimRotation;
6353uniform int u_blendAnimIntensity;
6354uniform int u_blendAnimOffset;
6355uniform float u_blendAnimSpeed;
6356uniform float u_blendNoiseMask;
6357uniform float u_blendNoiseScale;
6358uniform float u_blendFalloff;
6359uniform int u_blendDualLayer;
6360uniform int u_blendMode2;
6361uniform float u_blendIntensity2;
6362uniform float u_blendOffset2;
6363uniform float u_blendRotation2;
6364uniform float u_blendScale2;
6365
6366// Heatmap params (match Paper's controls)
6367uniform float u_innerGlow;   // 0-1 tight core glow
6368uniform float u_outerGlow;   // 0-1 wide bloom halo
6369uniform float u_contour;     // 0-1 contour band strength
6370uniform float u_noise;       // 0-1 grain in the heat field
6371uniform float u_scale;       // image zoom / procedural scale
6372uniform float u_flowSpeed;   // outer-glow animation speed
6373
6374__COMMON__
6375
6376float hm_getImgFrame(vec2 uv, float th) {
6377  float frame = 1.0;
6378  frame *= smoothstep(0.0, th, uv.y);
6379  frame *= 1.0 - smoothstep(1.0 - th, 1.0, uv.y);
6380  frame *= smoothstep(0.0, th, uv.x);
6381  frame *= 1.0 - smoothstep(1.0 - th, 1.0, uv.x);
6382  return frame;
6383}
6384
6385// 3x3 weighted blur of the green channel (big-blur channel), mirroring Paper.
6386float hm_blurG(vec2 uv, vec2 dudx, vec2 dudy, float radius, float centerG) {
6387  vec2 texel = 1.0 / vec2(textureSize(u_imageTexture, 0));
6388  vec2 r = radius * texel;
6389  float sum = 4.0 * centerG;
6390  sum += 2.0 * textureGrad(u_imageTexture, uv + vec2(0.0, -r.y), dudx, dudy).g;
6391  sum += 2.0 * textureGrad(u_imageTexture, uv + vec2(0.0, r.y), dudx, dudy).g;
6392  sum += 2.0 * textureGrad(u_imageTexture, uv + vec2(-r.x, 0.0), dudx, dudy).g;
6393  sum += 2.0 * textureGrad(u_imageTexture, uv + vec2(r.x, 0.0), dudx, dudy).g;
6394  sum += textureGrad(u_imageTexture, uv + vec2(-r.x, -r.y), dudx, dudy).g;
6395  sum += textureGrad(u_imageTexture, uv + vec2(r.x, -r.y), dudx, dudy).g;
6396  sum += textureGrad(u_imageTexture, uv + vec2(-r.x, r.y), dudx, dudy).g;
6397  sum += textureGrad(u_imageTexture, uv + vec2(r.x, r.y), dudx, dudy).g;
6398  return sum / 16.0;
6399}
6400
6401float hm_hash(vec2 p) {
6402  return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453123);
6403}
6404
6405void main() {
6406  float aspect = u_resolution.x / max(u_resolution.y, 1.0);
6407  vec2 uvN = vec2(vUv.x, 1.0 - vUv.y);
6408
6409  // No image: a soft animated heat blob so the mode isn't blank.
6410  if (u_hasImage == 0) {
6411    float time = u_time * u_speed;
6412    vec2 p = vec2((vUv.x - 0.5) * aspect, vUv.y - 0.5) / max(u_scale, 0.1);
6413    float d = length(p) * 1.6;
6414    float blob = smoothstep(1.0, 0.0, d);
6415    blob *= 0.55 + 0.6 * (fbm(p * 3.0 + vec2(time * u_flowSpeed * 0.3, time * u_flowSpeed * 0.15), 1.0, 3, 0.5, 2.0) * 0.5 + 0.5);
6416    float heat = clamp(blob * (0.5 + u_innerGlow) + smoothstep(1.4, 0.0, d) * (u_outerGlow * 0.5), 0.0, 1.0);
6417    heat += (0.005 + 0.35 * u_noise) * (hm_hash(uvN) - 0.5);
6418    vec3 c = mixColors(u_colors, u_colorPositions, u_colorCount, clamp(heat, 0.0, 1.0));
6419    c *= smoothstep(0.0, 0.18, heat);   // composite over dark bg (Paper's colorBack)
6420    fragColor = outputColor(c, gl_FragCoord.xy, 1.0);
6421    return;
6422  }
6423
6424  // Reconstruct the padded-texture aspect from the raw image size, using the
6425  // same padding formula as the CPU preprocessing (canvasSize 1000, padding 375).
6426  float ratio = u_imageSize.x / max(u_imageSize.y, 1.0);
6427  float imgW = ratio > 1.0 ? 1000.0 : 1000.0 * ratio;
6428  float imgH = ratio > 1.0 ? 1000.0 / ratio : 1000.0;
6429  float texAspect = (imgW + 750.0) / (imgH + 750.0);
6430
6431  // Paper's exact image-fit sizing (contain, u_fit=1), reconstructed 
6431in the
6432  // fragment since HueGrid's vertex shader is a passthrough. origin (0.5,0.5),
6433  // offset (0,0), and rotation (0) use Paper's defaults, so those terms drop out.
6434  vec2 uvC = vUv - 0.5;
6435  float boxX = min(u_resolution.x / texAspect, u_resolution.y) * texAspect;
6436  vec2 boxScale = u_resolution / vec2(boxX, boxX / texAspect);
6437  vec2 vImageUV = uvC * boxScale / max(u_scale, 0.0001);
6438  vImageUV += 0.5;
6439  vImageUV.y = 1.0 - vImageUV.y;
6440
6441  // Paper's un-pad: sample the image region within the padded texture.
6442  vec2 sUV = (vImageUV - 0.5) * 0.5714285714285714 + 0.5;
6443  float imgSoftFrame = hm_getImgFrame(sUV, 0.03);
6444
6445  vec4 img = texture(u_imageTexture, sUV);
6446  vec2 dudx = dFdx(sUV);
6447  vec2 dudy = dFdy(sUV);
6448
6449  float shape = img.r;                                   // contour channel
6450  float gBlur = hm_blurG(sUV, dudx, dudy, 8.0, img.g);   // big blur (outer glow)
6451  float outerBlur = 1.0 - mix(1.0, gBlur, shape);
6452  float innerBlur = mix(gBlur, 0.0, shape);
6453  float contourCh = mix(img.b, 0.0, shape);
6454  outerBlur *= imgSoftFrame;
6455
6456  float inner = 0.8 + 0.8 * innerBlur;
6457  inner *= (u_innerGlow * 2.0);
6458  inner += (u_contour * 2.0) * contourCh;
6459  inner = min(1.0, inner);
6460  inner *= (1.0 - shape);
6461  inner = pow(inner, 1.2);
6462
6463  float t = mod(u_time * u_speed * u_flowSpeed * 0.3 - 0.1, 1.0);
6464  float outer = 0.9 * pow(max(outerBlur, 0.0), 0.8);
6465  float y = mod(sUV.y - t, 1.0);
6466  float animatedMask = 0.5 + smoothstep(0.3, 0.65, y) * (1.0 - smoothstep(0.65, 1.0, y));
6467  outer *= animatedMask;
6468  outer *= mix(0.0, 5.0, pow(u_outerGlow, 2.0));
6469  outer *= imgSoftFrame;
6470
6471  float heat = clamp(inner + outer, 0.0, 1.0);
6472  heat += (0.005 + 0.35 * u_noise) * (hm_hash(uvN) - 0.5);
6473  heat = clamp(heat, 0.0, 1.0);
6474
6475  vec3 color = mixColors(u_colors, u_colorPositions, u_colorCount, heat);
6476  // Paper composites the heat over a dark background (colorBack, default black):
6477  // fade to near-black where there's little/no heat so the shape glows on dark.
6478  color *= smoothstep(0.0, 0.18, heat);
6479  color += 0.02 * (hm_hash(uvN + 1.0) - 0.5);
6480
6481  fragColor = outputColor(color, gl_FragCoord.xy, 1.0);
6482}
6483`,z=`#version 300 es
6484precision highp float;
6485
6486in vec2 vUv;
6487out vec4 fragColor;
6488
6489uniform float u_time;
6490uniform float u_speed;
6491uniform vec3 u_colors[12];
6492uniform float u_colorPositions[12];
6493uniform int u_colorCount;
6494uniform float u_direction;
6495uniform float u_grain;
6496uniform vec2 u_resolution;
6497
6498// Blend uniforms
6499uniform int u_blendMode;
6500uniform float u_blendIntensity;
6501uniform float u_blendOffset;
6502uniform float u_blendRotation;
6503uniform float u_blendScale;
6504uniform int u_blendAnimRotation;
6505uniform int u_blendAnimIntensity;
6506uniform int u_blendAnimOffset;
6507uniform float u_blendAnimSpeed;
6508uniform float u_blendNoiseMask;
6509uniform float u_blendNoiseScale;
6510uniform float u_blendFalloff;
6511uniform int u_blendDualLayer;
6512uniform int u_blendMode2;
6513uniform float u_blendIntensity2;
6514uniform float u_blendOffset2;
6515uniform float u_blendRotation2;
6516uniform float u_blendScale2;
6517
6518// Mode-specific uniforms
6519uniform float u_gridScale;
6520uniform float u_lineWidth;
6521uniform float u_glowIntensity;
6522uniform float u_glowSize;
6523uniform float u_pulseSpeed;
6524uniform float u_warp;
6525uniform float u_secondaryGrid;
6526uniform float u_perspective;
6527
6528__COMMON__
6529
6530void main() {
6531  vec2 uv = vUv;
6532  float time = u_time * u_speed;
6533  float aspect = u_resolution.x / max(u_resolution.y, 1.0);
6534  uv = applyMouseWarp(uv, aspect);
6535
6536  // Center and aspect-correct
6537  vec2 p = uv - 0.5;
6538  p.x *= aspect;
6539
6540  // Perspective tilt: top of screen narrows to create 3D ground plane effect
6541  float perspStrength = u_perspective * 0.8;
6542  float yNorm = uv.y;
6543  float perspScale = 1.0 + perspStrength * (1.0 - yNorm);
6544  p.x *= perspScale;
6545  // Slight vertical foreshortening
6546  p.y = p.y * (1.0 + perspStrength * 0.5) - perspStrength * 0.25;
6547
6548  // Domain warping with noise for organic grid deformation
6549  float warpN1 = snoise(p * 1.8 + time * 0.15);
6550  float warpN2 = snoise(p * 2.6 - time * 0.12);
6551  vec2 warpOffset = vec2(warpN1, warpN2) * u_warp * 0.25;
6552  vec2 warped = p + warpOffset;
6553
6554  // Scale for the grid
6555  float gridSc = max(u_gridScale, 0.5) * 6.0;
6556  vec2 gridUv = warped * gridSc;
6557
6558  // Primary grid: distance to nearest grid line (each axis)
6559  vec2 gridFract = abs(fract(gridUv) - 0.5);
6560  float lineDistX = gridFract.x;
6561  float lineDistY = gridFract.y;
6562  float lineDist = min(lineDistX, lineDistY);
6563
6564  // Line thickness and glow kernel
6565  float lineThick = max(u_lineWidth, 0.01) * 0.08;
6566  float glowK = max(u_glowSize, 0.05) * 12.0;
6567
6568  // Floor the core width at ~1px in screen space (fwidth) so thin grid lines get
6569  // analytic anti-aliasing instead of shimmering/aliasing. Keeps the profile shape.
6570  float coreW = max(lineThick, fwidth(lineDist) * 1.5);
6571
6572  // Neon glow: exponential falloff for realistic neon look
6573  float primaryGlow = exp(-lineDist * glowK);
6574  // Sharp core line (analytic-AA width)
6575  float primaryCore = smoothstep(coreW * 1.5, coreW * 0.3, lineDist);
6576
6577  // Secondary grid at half scale for depth
6578  float secondaryGlow = 0.0;
6579  float secondaryCore = 0.0;
6580  if (u_secondaryGrid > 0.01) {
6581    vec2 gridUv2 = warped * gridSc * 2.0;
6582    vec2 gridFract2 = abs(fract(gridUv2) - 0.5);
6583    float lineDist2 = min(gridFract2.x, gridFract2.y);
6584    float coreW2 = max(lineThick, fwidth(lineDist2) * 1.5);
6585    secondaryGlow = exp(-lineDist2 * glowK * 1.5) * u_secondaryGrid;
6586    secondaryCore = smoothstep(coreW2 * 1.2, coreW2 * 0.2, lineDist2) * u_secondaryGrid * 0.5;
6587  }
6588
6589  // Temporal pulse modulation
6590  float pulse = 1.0 + sin(time * u_pulseSpeed) * 0.3;
6591
6592  // Intersection hotspots: both X and Y are close to grid line
6593  float intersectDist = lineDistX + lineDistY;
6594  float hotspot = exp(-intersectDist * glowK * 2.5);
6595
6596  // Gradient t-value: map grid position to color
6597  // Use direction-based sampling from the un-warped UV
6598  float angle = radians(u_direction);
6599  vec2 dir = vec2(cos(angle), sin(angle));
6600  float t = dot(uv - 0.5, dir) + 0.5;
6601
6602  // Modulate t by local grid intersection for color variation
6603  vec2 cellId = floor(gridUv);
6604  float cellOffset = snoise(cellId * 0.37) * 0.15;
6605  float tLocal = clamp(t + cellOffset, 0.0, 1.0);
6606
6607  // Image-driven gradient modulation
6608  float imgLum = imageLuminance(uv, aspect);
6609  if (imgLum >= 0.0) {
6610    tLocal = mix(tLocal, imgLum, u_imageIntensity);
6611  }
6612
6613  // Sample user gradient colors
6614  vec3 lineColor = mixColors(u_colors, u_colorPositions, u_colorCount, tLocal);
6615  vec3 hotspotColor = mixColors(u_colors, u_colorPositions, u_colorCount, clamp(tLocal + 0.15, 0.0, 1.0));
6616
6617  // Dark background
6618  vec3 bgColor = vec3(0.005, 0.005, 0.015);
6619
6620  // Compose neon glow layers with additive blending
6621  float glowAmt = u_glowIntensity * 1.5;
6622  vec3 color = bgColor;
6623
6624  // Primary grid glow (soft outer glow)
6625  color += lineColor * primaryGlow * glowAmt * pulse * 0.6;
6626  // Primary grid core (bright center)
6627  color += lineColor * primaryCore * glowAmt * pulse * 1.4;
6628
6629  // Secondary grid
6630  color += lineColor * 0.5 * secondaryGlow * glowAmt * pulse * 0.4;
6631  color += lineColor * 0.5 * secondaryCore * glowAmt * pulse * 0.6;
6632
6633  // Intersection hotspots (bright specular)
6634  color += hotspotColor * hotspot * glowAmt * pulse * 2.0;
6635  // White-hot center at intersections
6636  color += vec3(1.0, 0.98, 0.95) * hotspot * hotspot * glowAmt * 0.8;
6637
6638  // Subtle ambient glow bleeding into background
6639  float ambientGlow = exp(-lineDist * glowK * 0.3) * 0.08 * u_glowIntensity;
6640  color += lineColor * ambientGlow;
6641
6642  // Slight vignette to darken edges
6643  float vig = 1.0 - smoothstep(0.3, 1.1, length(uv - 0.5) * 1.4);
6644  color *= 0.7 + 0.3 * vig;
6645
6646  // Grain
6647  color += grain(uv, time, u_grain);
6648
6649  // Self-blend
6650  if (u_blendMode > 0) {
6651    float bRotation = u_blendAnimRotation > 0 ? animatedBlendRotation(u_blendRotation, time, u_blendAnimSpeed) : u_blendRotation;
6652    float bOffset = u_blendAnimOffset > 0 ? animatedBlendOffset(u_blendOffset, time, u_blendAnimSpeed) : u_blendOffset;
6653    float bIntensity = u_blendAnimIntensity > 0 ? animatedBlendIntensity(u_blendIntensity, time, u_blendAnimSpeed) : u_blendIntensity;
6654    bIntensity = noiseModulatedIntensity(bIntensity, vUv, time, u_blendNoiseMask, u_blendNoiseScale);
6655    bIntensity = applyBlendFalloff(bIntensity, t, u_blendFalloff);
6656    float tB = blendLayerT(vUv, t, u_resolution, u_direction, bOffset, bRotation, u_blendScale);
6657    vec3 blendCol = mixColors(u_colors, u_colorPositions, u_colorCount, tB);
6658    color = applyBlendMode(u_blendMode, color, blendCol, bIntensity);
6659    if (u_blendDualLayer > 0 && u_blendMode2 > 0) {
6660      float tB2 = blendLayerT(vUv, t, u_resolution, u_direction, u_blendOffset2, u_blendRotation2, u_blendScale2);
6661      vec3 blendCol2 = mixColors(u_colors, u_colorPositions, u_colorCount, tB2);
6662      color = applyBlendMode(u_blendMode2, color, blendCol2, u_blendIntensity2);
6663    }
6664  }
6665
6666  fragColor = outputColor(color, gl_FragCoord.xy, 1.0);
6667}
6668`,U=`#version 300 es
6669precision highp float;
6670
6671in vec2 vUv;
6672out vec4 fragColor;
6673
6674uniform float u_time;
6675uniform float u_speed;
6676uniform vec3 u_colors[12];
6677uniform float u_colorPositions[12];
6678uniform int u_colorCount;
6679uniform float u_direction;
6680uniform float u_grain;
6681uniform vec2 u_resolution;
6682
6683// Blend uniforms
6684uniform int u_blendMode;
6685uniform float u_blendIntensity;
6686uniform float u_blendOffset;
6687uniform float u_blendRotation;
6688uniform float u_blendScale;
6689uniform int u_blendAnimRotation;
6690uniform int u_blendAnimIntensity;
6691uniform int u_blendAnimOffset;
6692uniform float u_blendAnimSpeed;
6693uniform float u_blendNoiseMask;
6694uniform float u_blendNoiseScale;
6695uniform float u_blendFalloff;
6696uniform int u_blendDualLayer;
6697uniform int u_blendMode2;
6698uniform float u_blendIntensity2;
6699uniform float u_blendOffset2;
6700uniform float u_blendRotation2;
6701uniform float u_blendScale2;
6702
6703// Mode-specific uniforms
6704uniform float u_paletteSpeed;
6705uniform float u_complexity;
6706uniform float u_phaseShift;
6707uniform float u_brightness;
6708uniform float u_contrast;
6709uniform float u_frequency;
6710uniform float u_warp;
6711uniform float u_sharpness;
6712
6713__COMMON__
6714
6715// Inigo Quilez cosine palette formula
6716vec3 cosinePalette(float t, vec3 a, vec3 b, vec3 c, vec3 d) {
6717  return a + b * cos(6.2831853 * (c * t + d));
6718}
6719
6720void main() {
6721  vec2 uv = vUv;
6722  float time = u_time * u_speed;
6723  float aspect = u_resolution.x / max(u_resolution.y, 1.0);
6724  uv = applyMouseWarp(uv, aspect);
6725
6726  // Center and aspect-correct for spatial calculations
6727  vec2 p = uv - 0.5;
6728  p.x *= aspect;
6729
6730  // Direction-based base t-value
6731  float angle = radians(u_direction);
6732  vec2 dir = vec2(cos(angle), sin(angle));
6733  float baseT = dot(uv - 0.5, dir) + 0.5;
6734
6735  // Domain warping with noise for organic deformation
6736  float warpN1 = snoise(p * 2.5 + time * 0.12);
6737  float warpN2 = snoise(p * 3.8 - time * 0.09 + vec2(5.2, 1.3));
6738  float warpN3 = snoise(p * 5.1 + time * 0.07 + vec2(-2.1, 8.7));
6739  float warpAmount = (warpN1 + warpN2 * 0.5 + warpN3 * 0.25) * u_warp * 0.3;
6740  float t = clamp(baseT + warpAmount, 0.0, 1.0);
6741
6742  // Image-driven gradient modulation
6743  float imgLum = imageLuminance(uv, aspect);
6744  if (imgLum >= 0.0) {
6745    t = mix(t, imgLum, u_imageIntensity);
6746  }
6747
6748  // Derive cosine palette vectors from user gradient colors.
6749  // 'a' = brightness center (average of first two colors)
6750  // 'b' = contrast amplitude (difference between colors)
6751  // 'c' = frequency multiplier per channel
6752  // 'd' = phase offset per channel
6753  vec3 col0 = u_colors[0];
6754  vec3 col1 = u_colors[min(1, u_colorCount - 1)];
6755  vec3 col2 = u_colors[min(2, u_colorCount - 1)];
6756
6757  vec3 a = mix(col0, col1, 0.5) * u_brightness;
6758  vec3 b = abs(col1 - col0) * u_contrast + 0.1;
6759  vec3 c = vec3(u_frequency * 1.0, u_frequency * 1.15, u_frequency * 0.85);
6760  vec3 d = vec3(0.0, u_phaseShift * 0.33, u_phaseShift * 0.67);
6761
6762  // Animate the phase over time for smooth color cycling
6763  float phaseAnim = time * u_paletteSpeed * 0.15;
6764  d += phaseAnim;
6765
6766  // Base layer: primary cosine palette
6767  vec3 color = cosinePalette(t, a, b, c, d);
6768
6769  // Layer additional frequencies for complexity
6770  float complexLayers = u_complexity * 4.0;
6771  int layerCount = int(clamp(complexLayers, 0.0, 4.0));
6772
6773  for (int i = 1; i <= 4; i++) {
6774    if (i > layerCount) break;
6775    float fi = float(i);
6776
6777    // Each layer uses higher frequency, different phase, and warped t
6778    float layerFreq = u_frequency * (1.0 + fi * 0.8);
6779    vec3 layerC = vec3(layerFreq, layerFreq * 1.1, layerFreq * 0.9);
6780
6781    // Offset the phase per layer for variety
6782    vec3 layerD = d + vec3(fi * 0.37, fi * 0.53, fi * 0.71);
6783
6784    // Slightly different spatial position per layer
6785    float layerWarp = snoise(p * (2.0 + fi * 1.5) + time * (0.05 + fi * 0.03));
6786    float layerT = clamp(t + layerWarp * u_warp * 0.15, 0.0, 1.0);
6787
6788    vec3 layerColor = cosinePalette(layerT, a * 0.8, b * (0.6 / fi), layerC, layerD);
6789
6790    // Diminishing blend weight per layer
6791    float weight = 0.35 / fi;
6792    color = mix(color, layerColor, weight);
6793  }
6794
6795  // Optional sharpness: create sharper color band transitions
6796  if (u_sharpness > 0.01) {
6797    // Quantize the palette output to create distinct bands
6798    float bands = 3.0 + u_sharpness * 12.0;
6799    vec3 quantized = floor(color * bands + 0.5) / bands;
6800    color = mix(color, quantized, u_sharpness * 0.7);
6801
6802    // Also apply pow for contrast-based sharpening
6803    color = pow(max(color, vec3(0.0)), vec3(1.0 + u_sharpness * 0.5));
6804  }
6805
6806  // Modulate with the user gradient: blend the cosine palette with
6807  // the direct gradient sample for artistic control
6808  vec3 gradientColor = mixColors(u_colors, u_colorPositions, u_colorCount, t);
6809  color = mix(color, color * gradientColor * 2.0, 0.3);
6810
6811  // Clamp to valid range
6812  color = clamp(color, vec3(0.0), vec3(1.5));
6813
6814  // Grain
6815  color += grain(uv, time, u_grain);
6816
6817  // Self-blend
6818  if (u_blendMode > 0) {
6819    float bRotation = u_blendAnimRotation > 0 ? animatedBlendRotation(u_blendRotation, time, u_blendAnimSpeed) : u_blendRotation;
6820    float bOffset = u_blendAnimOffset > 0 ? animatedBlendOffset(u_blendOffset, time, u_blendAnimSpeed) : u_blendOffset;
6821    float bIntensity = u_blendAnimIntensity > 0 ? animatedBlendIntensity(u_blendIntensity, time, u_blendAnimSpeed) : u_blendIntensity;
6822    bIntensity = noiseModulatedIntensity(bIntensity, vUv, time, u_blendNoiseMask, u_blendNoiseScale);
6823    bIntensity = applyBlendFalloff(bIntensity, t, u_blendFalloff);
6824    float tB = blendLayerT(vUv, t, u_resolution, u_direction, bOffset, bRotation, u_blendScale);
6825    vec3 blendCol = mixColors(u_colors, u_colorPositions, u_colorCount, tB);
6826    color = applyBlendMode(u_blendMode, color, blendCol, bIntensity);
6827    if (u_blendDualLayer > 0 && u_blendMode2 > 0) {
6828      float tB2 = blendLayerT(vUv, t, u_resolution, u_direction, u_blendOffset2, u_blendRotation2, u_blendScale2);
6829      vec3 blendCol2 = mixColors(u_colors, u_colorPositions, u_colorCount, tB2);
6830      color = applyBlendMode(u_blendMode2, color, blendCol2, u_blendIntensity2);
6831    }
6832  }
6833
6834  fragColor = outputColor(color, gl_FragCoord.xy, 1.0);
6835}
6836`,q=`#version 300 es
6837precision highp float;
6838
6839in vec2 vUv;
6840out vec4 fragColor;
6841
6842uniform float u_time;
6843uniform float u_speed;
6844uniform vec3 u_colors[12];
6845uniform float u_colorPositions[12];
6846uniform int u_colorCount;
6847uniform float u_direction;
6848uniform float u_grain;
6849uniform vec2 u_resolution;
6850
6851// Blend uniforms
6852uniform int u_blendMode;
6853uniform float u_blendIntensity;
6854uniform float u_blendOffset;
6855uniform float u_blendRotation;
6856uniform float u_blendScale;
6857uniform int u_blendAnimRotation;
6858uniform int u_blendAnimIntensity;
6859uniform int u_blendAnimOffset;
6860uniform float u_blendAnimSpeed;
6861uniform float u_blendNoiseMask;
6862uniform float u_blendNoiseScale;
6863uniform float u_blendFalloff;
6864uniform int u_blendDualLayer;
6865uniform int u_blendMode2;
6866uniform float u_blendIntensity2;
6867uniform float u_blendOffset2;
6868uniform float u_blendRotation2;
6869uniform float u_blendScale2;
6870
6871// Mode-specific uniforms
6872uniform int u_layers;
6873uniform float u_complexity;
6874uniform float u_turbulence;
6875uniform float u_colorCycle;
6876uniform float u_pulsate;
6877uniform float u_warmth;
6878uniform float u_scale;
6879uniform float u_blend;
6880
6881__COMMON__
6882
6883void main() {
6884  vec2 uv = vUv;
6885  float time = u_time * u_speed;
6886  float aspect = u_resolution.x / max(u_resolution.y, 1.0);
6887  uv = applyMouseWarp(uv, aspect);
6888
6889  // Center and aspect-correct
6890  vec2 p = uv - 0.5;
6891  p.x *= aspect;
6892
6893  // Scale the plasma domain
6894  vec2 sp = p * max(u_scale, 0.1) * 4.0;
6895
6896  // ---- Aurora-like domain warp for organic flow ----
6897  float warp1 = snoise(sp * 0.8 + time * 0.12);
6898  float warp2 = snoise(sp * 1.6 - time * 0.09);
6899  sp += vec2(warp1, warp2) * u_turbulence * 0.3;
6900
6901  // Complexity multiplier
6902  float cx = 1.0 + u_complexity * 1.5;
6903
6904  // ---- Three energy layers with noise-modulated sin patterns ----
6905  float l1 = sin((sp.x * 11.0 * cx + time * 1.8) + snoise(sp * 4.0) * 4.0) * 0.5 + 0.5;
6906  float l2 = sin((sp.y * 13.0 * cx - time * 1.4) + snoise(sp * 3.3 + vec2(5.0, 0.0)) * 3.2) * 0.5 + 0.5;
6907  float l3 = sin((length(sp) * 18.0 * cx - time * 2.3) + snoise(sp * 2.0 + vec2(0.0, 8.0)) * 4.0) * 0.5 + 0.5;
6908
6909  // Color cycling
6910  float cycleOff = time * u_colorCycle * 0.08;
6911  float t1 = fract(l1 + cycleOff);
6912  float t2 = fract(l2 + cycleOff + 0.33);
6913  float t3 = fract(l3 + cycleOff + 0.66);
6914
6915  // Image modulation
6916  float imgLum = imageLuminance(uv, aspect);
6917  if (imgLum >= 0.0) {
6918    t1 = mix(t1, imgLum, u_imageIntensity);
6919    t2 = mix(t2, imgLum, u_imageIntensity);
6920    t3 = mix(t3, imgLum, u_imageIntensity);
6921  }
6922
6923  // Color each layer from different gradient positions
6924  vec3 c1 = mixColors(u_colors, u_colorPositions, u_colorCount, t1);
6925  vec3 c2 = mixColors(u_colors, u_colorPositions, u_colorCount, t2);
6926  vec3 c3 = mixColors(u_colors, u_colorPositions, u_colorCount, t3);
6927
6928  // ---- Additive blending: layers combine for bright intersections ----
6929  vec3 additive = clamp(c1 * 0.55 + c2 * 0.45 + c3 * 0.38, vec3(0.0), vec3(2.0));
6930
6931  // Screen blend path for neon glow
6932  vec3 screenMix = c1;
6933  screenMix = 1.0 - (1.0 - screenMix) * (1.0 - c2 * 0.6);
6934  screenMix = 1.0 - (1.0 - screenMix) * (1.0 - c3 * 0.5);
6935
6936  vec3 color = mix(additive, screenMix, u_blend);
6937
6938  // ---- Subsurface glow via noise-derived normal ----
6939  float surfaceN = snoise(sp * 2.0 + time * 0.1);
6940  float surfaceDx = snoise((sp + vec2(0.02, 0.0)) * 2.0 + time * 0.1);
6941  float surfaceDy = snoise((sp + vec2(0.0, 0.02)) * 2.0 + time * 0.1);
6942  vec3 surfN = normalize(vec3(surfaceN - surfaceDx, surfaceN - surfaceDy, 0.22));
6943  float fresnel = pow(1.0 - max(dot(surfN, vec3(0.0, 0.0, 1.0)), 0.0), 2.8);
6944
6945  // Rim glow adds edge brightness
6946  color += fresnel * c1 * 0.3;
6947
6948  // ---- Hot spots: where all 3 layers peak simultaneously ----
6949  float hotSpot = l1 * l2 * l3;
6950  color += vec3(pow(hotSpot, 2.0) * 3.0) * 0.4 * u_blend;
6951
6952  // ---- Pulsation ----
6953  float pulse = 1.0 + sin(time * u_pulsate * 0.6) * 0.25 * u_pulsate;
6954  color *= pulse;
6955
6956  // ---- Warmth bias ----
6957  color.r *= 1.0 + u_warmth * 0.3;
6958  color.b *= 1.0 - u_warmth * 0.15;
6959
6960  // ---- Saturation boost ----
6961  float lum = dot(color, vec3(0.2126, 0.7152, 0.0722));
6962  color = mix(vec3(lum), color, 1.3);
6963
6964  color = clamp(color, vec3(0.0), vec3(2.0));
6965  color += grain(uv, time, u_grain);
6966
6967  // Self-blend (use t1 as reference)
6968  float baseT = t1;
6969  if (u_blendMode > 0) {
6970    float bRotation = u_blendAnimRotation > 0 ? animatedBlendRotation(u_blendRotation, time, u_blendAnimSpeed) : u_blendRotation;
6971    float bOffset = u_blendAnimOffset > 0 ? animatedBlendOffset(u_blendOffset, time, u_blendAnimSpeed) : u_blendOffset;
6972    float bIntensity = u_blendAnimIntensity > 0 ? animatedBlendIntensity(u_blendIntensity, time, u_blendAnimSpeed) : u_blendIntensity;
6973    bIntensity = noiseModulatedIntensity(bIntensity, uv, time, u_blendNoiseMask, u_blendNoiseScale);
6974    bIntensity = applyBlendFalloff(bIntensity, baseT, u_blendFalloff);
6975    float tB = blendLayerT(uv, baseT, u_resolution, u_direction, bOffset, bRotation, u_blendScale);
6976    vec3 blendCol = mixColors(u_colors, u_colorPositions, u_colorCount, tB);
6977    color = applyBlendMode(u_blendMode, color, blendCol, bIntensity);
6978    if (u_blendDualLayer > 0 && u_blendMode2 > 0) {
6979      float tB2 = blendLayerT(uv, baseT, u_resolution, u_direction, u_blendOffset2, u_blendRotation2, u_blendScale2);
6980      vec3 blendCol2 = mixColors(u_colors, u_colorPositions, u_colorCount, tB2);
6981      color = applyBlendMode(u_blendMode2, color, blendCol2, u_blendIntensity2);
6982    }
6983  }
6984
6985  fragColor = outputColor(color, gl_FragCoord.xy, 1.0);
6986}
6987`,G=`#version 300 es
6988precision highp float;
6989
6990in vec2 vUv;
6991out vec4 fragColor;
6992
6993uniform float u_time;
6994uniform float u_speed;
6995uniform vec3 u_colors[12];
6996uniform float u_colorPositions[12];
6997uniform int u_colorCount;
6998uniform float u_direction;
6999uniform float u_grain;
7000uniform vec2 u_resolution;
7001
7002// Blend uniforms
7003uniform int u_blendMode;
7004uniform float u_blendIntensity;
7005uniform float u_blendOffset;
7006uniform float u_blendRotation;
7007uniform float u_blendScale;
7008uniform int u_blendAnimRotation;
7009uniform int u_blendAnimIntensity;
7010uniform int u_blendAnimOffset;
7011uniform float u_blendAnimSpeed;
7012uniform float u_blendNoiseMask;
7013uniform float u_blendNoiseScale;
7014uniform float u_blendFalloff;
7015uniform int u_blendDualLayer;
7016uniform int u_blendMode2;
7017uniform float u_blendIntensity2;
7018uniform float u_blendOffset2;
7019uniform float u_blendRotation2;
7020uniform float u_blendScale2;
7021
7022// Mode-specific uniforms
7023uniform float u_glowRadius;
7024uniform float u_glowIntensity;
7025uniform float u_scatterAmount;
7026uniform float u_rimPower;
7027uniform float u_absorption;
7028uniform float u_thickness;
7029uniform float u_innerGlow;
7030uniform float u_organicShape;
7031
7032__COMMON__
7033
7034// Compute a smooth noise-based surface field.
7035// Returns a height value representing the translucent "surface".
7036float surfaceField(vec2 p, float time) {
7037  float n1 = snoise(p * 2.0 + time * 0.08);
7038  float n2 = snoise(p * 4.5 - time * 0.06 + vec2(3.7, 1.2));
7039  float n3 = snoise(p * 8.2 + time * 0.04 + vec2(-1.5, 5.8));
7040  float n4 = snoise(p * 14.0 - time * 0.03 + vec2(7.3, -2.9));
7041
7042  // Organic shape: more octaves = more complex surface topology
7043  float shape = n1 * 0.5
7044              + n2 * 0.25
7045              + n3 * 0.125 * u_organicShape
7046              + n4 * 0.0625 * u_organicShape;
7047
7048  return shape;
7049}
7050
7051// Compute fake normal from noise gradient using central differences.
7052// Accepts the already-computed center value h to avoid a redundant surfaceField call.
7053vec3 computeNormal(vec2 p, float time, float eps, float h) {
7054  float hx = surfaceField(p + vec2(eps, 0.0), time);
7055  float hy = surfaceField(p + vec2(0.0, eps), time);
7056
7057  float dhdx = (hx - h) / eps;
7058  float dhdy = (hy - h) / eps;
7059
7060  // Construct normal from height gradient (pointing "up" from surface)
7061  vec3 normal = normalize(vec3(-dhdx * 0.5, -dhdy * 0.5, 1.0));
7062  return normal;
7063}
7064
7065void main() {
7066  vec2 uv = vUv;
7067  float time = u_time * u_speed;
7068  float aspect = u_resolution.x / max(u_resolution.y, 1.0);
7069  uv = applyMouseWarp(uv, aspect);
7070
7071  // Center and aspect-correct
7072  vec2 p = uv - 0.5;
7073  p.x *= aspect;
7074
7075  // Direction-based t for gradient mapping
7076  float angle = radians(u_direction);
7077  vec2 dir = vec2(cos(angle), sin(angle));
7078  float baseT = dot(uv - 0.5, dir) + 0.5;
7079
7080  // Surface field: creates the organic "material" shape
7081  float surface = surfaceField(p, time);
7082
7083  // Thickness: thicker regions absorb more light
7084  // Map surface noise to a thickness value (0 = thin/translucent, 1 = thick/opaque)
7085  float thicknessField = smoothstep(-0.6, 0.6, surface) * u_thickness;
7086
7087  // Compute fake surface normal for fresnel/rim calculation
7088  // Reuses the precomputed surface value to avoid a redundant surfaceField call
7089  float eps = 0.005 + 0.005 * (1.0 - u_organicShape);
7090  vec3 normal = computeNormal(p, time, eps, surface);
7091
7092  // View direction: slightly off-center to create asymmetric rim
7093  float viewShift = sin(time * 0.15) * 0.1;
7094  vec3 viewDir = normalize(vec3(viewShift, 0.05, 1.0));
7095
7096  // Fresnel rim: edges glow bright (light grazing the surface)
7097  float NdotV = max(dot(normal, viewDir), 0.0);
7098  float rimPow = max(u_rimPower, 0.1) * 4.0;
7099  float fresnel = pow(1.0 - NdotV, rimPow);
7100
7101  // Gradient t-value: modulated by surface topology
7102  float t = clamp(baseT + surface * 0.2, 0.0, 1.0);
7103
7104  // Image-driven gradient modulation
7105  float imgLum = imageLuminance(uv, aspect);
7106  if (imgLum >= 0.0) {
7107    t = mix(t, imgLum, u_imageIntensity);
7108  }
7109
7110  // Base material color from user gradient
7111  vec3 baseColor = mixColors(u_colors, u_colorPositions, u_colorCount, t);
7112
7113  // Subsurface scattering simulation:
7114  // Light penetrates the surface, spreads outward, and takes on the material color.
7115  // We simulate this by sampling colors at offset t-values and blending them.
7116  float scatterRadius = u_scatterAmount * 0.3;
7117  vec3 scatterColor = vec3(0.0);
7118  float scatterWeights = 0.0;
7119
7120  // Sample at multiple offsets around the current point
7121  for (int i = -2; i <= 2; i++) {
7122    float offset = float(i) * scatterRadius * 0.15;
7123    // Also add noise-based spatial scatter
7124    float noiseOffset = snoise(p * 3.0 + vec2(float(i) * 1.7, time * 0.05)) * scatterRadius * 0.1;
7125    float scatterT = clamp(t + offset + noiseOffset, 0.0, 1.0);
7126
7127    vec3 sampleColor = mixColors(u_colors, u_colorPositions, u_colorCount, scatterT);
7128
7129    // Weight: closer samples contribute more
7130    float w = exp(-abs(float(i)) * 0.5);
7131    scatterColor += sampleColor * w;
7132    scatterWeights += w;
7133  }
7134  scatterColor /= max(scatterWeights, 0.001);
7135
7136  // Blend scatter with base: thinner areas show more scatter
7137  float scatterMix = (1.0 - thicknessField * 0.6) * u_scatterAmount * 0.5;
7138  vec3 materialColor = mix(baseColor, scatterColor, scatterMix);
7139
7140  // Absorption: thicker regions absorb more light (darken)
7141  float absorb = 1.0 - thicknessField * u_absorption * 0.7;
7142  materialColor *= absorb;
7143
7144  // Rim glow: bright, saturated light at the edges
7145  vec3 rimColor = mixColors(u_colors, u_colorPositions, u_colorCount, clamp(t + 0.2, 0.0, 1.0));
7146  // Make rim brighter and slightly desaturated for a luminous effect
7147  rimColor = mix(rimColor, vec3(1.0), 0.3);
7148  float rimIntensity = fresnel * u_glowIntensity * 1.5;
7149
7150  // Inner glow: warm emission from within, modulated by noise
7151  float innerNoiseA = snoise(p * 3.5 + time * 0.12 + vec2(2.1, -0.8));
7152  float innerNoiseB = snoise(p * 5.8 - time * 0.09 + vec2(-3.4, 6.1));
7153  float innerField = smoothstep(-0.2, 0.5, innerNoiseA * 0.6 + innerNoiseB * 0.4);
7154
7155  vec3 innerGlowColor = mixColors(u_colors, u_colorPositions, u_colorCount, clamp(t - 0.15, 0.0, 1.0));
7156  // Warm tint for inner glow to simulate internal light emission
7157  innerGlowColor = mix(innerGlowColor, innerGlowColor * vec3(1.2, 1.05, 0.9), 0.4);
7158  float innerIntensity = innerField * u_innerGlow * (1.0 - thicknessField * 0.3);
7159
7160  // Subsurface glow radius: soft, diffused glow around the entire surface
7161  float distFromCenter = length(p);
7162  float glowFalloff = exp(-distFromCenter * (3.0 - u_glowRadius * 2.0));
7163  vec3 ssGlow = scatterColor * glowFalloff * u_glowIntensity * 0.4;
7164
7165  // Final composition: base material + rim + subsurface + inner glow
7166  vec3 color = materialColor;
7167  color += rimColor * rimIntensity;
7168  color += ssGlow;
7169  color += innerGlowColor * innerIntensity * 0.5;
7170
7171  // Subtle surface variation to avoid flatness
7172  float surfaceDetail = snoise(p * 12.0 + time * 0.02) * 0.03;
7173  color += color * surfaceDetail;
7174
7175  // Soft vignette to keep focus
7176  float vig = 1.0 - smoothstep(0.4, 1.0, distFromCenter * 1.2);
7177  color *= 0.85 + 0.15 * vig;
7178
7179  // Clamp
7180  color = clamp(color, vec3(0.0), vec3(1.6));
7181
7182  // Grain
7183  color += grain(uv, time, u_grain);
7184
7185  // Self-blend
7186  if (u_blendMode > 0) {
7187    float bRotation = u_blendAnimRotation > 0 ? animatedBlendRotation(u_blendRotation, time, u_blendAnimSpeed) : u_blendRotation;
7188    float bOffset = u_blendAnimOffset > 0 ? animatedBlendOffset(u_blendOffset, time, u_blendAnimSpeed) : u_blendOffset;
7189    float bIntensity = u_blendAnimIntensity > 0 ? animatedBlendIntensity(u_blendIntensity, time, u_blendAnimSpeed) : u_blendIntensity;
7190    bIntensity = noiseModulatedIntensity(bIntensity, vUv, time, u_blendNoiseMask, u_blendNoiseScale);
7191    bIntensity = applyBlendFalloff(bIntensity, t, u_blendFalloff);
7192    float tB = blendLayerT(vUv, t, u_resolution, u_direction, bOffset, bRotation, u_blendScale);
7193    vec3 blendCol = mixColors(u_colors, u_colorPositions, u_colorCount, tB);
7194    color = applyBlendMode(u_blendMode, color, blendCol, bIntensity);
7195    if (u_blendDualLayer > 0 && u_blendMode2 > 0) {
7196      float tB2 = blendLayerT(vUv, t, u_resolution, u_direction, u_blendOffset2, u_blendRotation2, u_blendScale2);
7197      vec3 blendCol2 = mixColors(u_colors, u_colorPositions, u_colorCount, tB2);
7198      color = applyBlendMode(u_blendMode2, color, blendCol2, u_blendIntensity2);
7199    }
7200  }
7201
7202  fragColor = outputColor(color, gl_FragCoord.xy, 1.0);
7203}
7204`,H=`#version 300 es
7205precision highp float;
7206
7207in vec2 vUv;
7208out vec4 fragColor;
7209
7210// Standard uniforms
7211uniform float u_time;
7212uniform float u_speed;
7213uniform vec3 u_colors[12];
7214uniform float u_colorPositions[12];
7215uniform int u_colorCount;
7216uniform float u_direction;
7217uniform float u_grain;
7218uniform vec2 u_resolution;
7219
7220// Blend uniforms
7221uniform int u_blendMode;
7222uniform float u_blendIntensity;
7223uniform float u_blendOffset;
7224uniform float u_blendRotation;
7225uniform float u_blendScale;
7226uniform int u_blendAnimRotation;
7227uniform int u_blendAnimIntensity;
7228uniform int u_blendAnimOffset;
7229uniform float u_blendAnimSpeed;
7230uniform float u_blendNoiseMask;
7231uniform float u_blendNoiseScale;
7232uniform float u_blendFalloff;
7233uniform int u_blendDualLayer;
7234uniform int u_blendMode2;
7235uniform float u_blendIntensity2;
7236uniform float u_blendOffset2;
7237uniform float u_blendRotation2;
7238uniform float u_blendScale2;
7239
7240// Mode-specific uniforms
7241uniform int u_effectType;       // 0=fire, 1=rain, 2=wind, 3=storm, 4=sand
7242uniform float u_fontSize;
7243uniform float u_density;
7244uniform int u_charSet;
7245uniform float u_flowSpeed;
7246uniform float u_turbulence;
7247uniform float u_noiseScale;
7248uniform float u_contrast;
7249uniform float u_threshold;
7250uniform float u_warpIntensity;
7251uniform float u_gridAngle;
7252uniform float u_cursorInfluence;
7253uniform float u_cursorRadius;
7254uniform float u_colorInfluence;
7255uniform int u_bgMode;       // 0=solid, 1=gradient
7256uniform vec3 u_bgColorRGB;
7257uniform vec3 u_inkColorRGB;
7258
7259__COMMON__
7260
7261// ---------------------------------------------------------------------------
7262// Hash helpers
7263// ---------------------------------------------------------------------------
7264float columnHash(float col) {
7265  return fract(sin(col * 127.1 + 311.7) * 43758.5453);
7266}
7267
7268float cellHash(float col, float row) {
7269  return fract(sin(col * 127.1 + row * 269.5 + 311.7) * 43758.5453);
7270}
7271
7272// ---------------------------------------------------------------------------
7273// Character bitmap sets (5x5 packed into int, bit = iy*5 + ix)
7274// ---------------------------------------------------------------------------
7275
7276// Digit patterns (0-9) -- readable 5x5 bitmaps
7277// Layout: row 0 = bottom, row 4 = top (matches iy*5+ix bit packing)
7278//
7279// 0: ring       1: center col  2: zigzag     3: right-turn
7280// 4: T-bar      5: snake       6: bulge      7: slash
7281// 8: hourglass  9: hook
7282int[10] digitPatterns() {
7283  return int[10](
7284    //  0: .###.  |  1: ..#..  |  2: .###.  |  3: .###.
7285    //     #...#  |     .##..  |     ....#  |     ....#
7286    //     #...#  |     ..#..  |     .###.  |     ..##.
7287    //     #...#  |     ..#..  |     #....  |     ....#
7288    //     .###.  |     .###.  |     #####  |     .###.
7289    0xE9D2E,     // 0  = 01110 10011 10100 10010 01110
7290    0x7108C,     // 1  = 00111 00010 00001 00001 01100
7291    0x1F4270E,   // 2  = 11111 00100 00100 01110 01110  (simplified)
7292    0xE83A0E,    // 3  = 01110 10000 01110 00000 01110  (simplified)
7293
7294    //  4: #...#  |  5: #####  |  6: .###.  |  7: #####
7295    //     #...#  |     #....  |     #....  |     ...#.
7296    //     #####  |     .###.  |     ####.  |     ..#..
7297    //     ....#  |     ....#  |     #...#  |     .#...
7298    //     ....#  |     ####.  |     .###.  |     #....
7299    0x108FD1,    // 4
7300    0xE82F1F,    // 5
7301    0xE958E,     // 6
7302    0x11111F,    // 7
7303
7304    //  8: .###.  |  9: .###.
7305    //     #...#  |     #...#
7306    //     .###.  |     .####
7307    //     #...#  |     ....#
7308    //     .###.  |     .###.
7309    0xE95D2E,    // 8
7310    0xE87D2E     // 9
7311  );
7312}
7313
7314// Symbol patterns -- ordered by density: space . : - = + # % @ !
7315int[10] symbolPatterns() {
7316  return int[10](
7317    0,           // space
7318    0x4000,      // .
7319    0x4010040,   // :   (invalid > 25 bits, but kept for consistency with ascii.ts)
7320    0xA8000,     // -
7321    0xA80A8,     // =
7322    0x4AE04,     // +
7323    0xEA5AE,     // #
7324    0x155D57,    // %
7325    0x1F7FBE,    // @
7326    0x4E444      // !
7327  );
7328}
7329
7330// Katakana-style angular patterns -- geometric strokes
7331int[10] katakanaPatterns() {
7332  return int[10](
7333    // Angular strokes reminiscent of katakana characters
7334    0x1F0842,    // a-like: horizontal top + diagonal
7335    0x4A52A,     // i-like: two strokes
7336    0xE0C21,     // u-like: crown shape
7337    0x15294,     // e-like: H-bar
7338    0xE4A52,     // o-like: cross with tail
7339    0x4E4E4,     // ka-like: vertical with bars
7340    0x1597A,     // ki-like: ladder
7341    0xA5294,     // ku-like: angle
7342    0x739CE,     // ke-like: flag
7343    0x4E944      // ko-like: box with stem
7344  );
7345}
7346
7347// Mixed patterns -- digits then symbols interleaved
7348int[20] mixedPatterns() {
7349  int d[10] = digitPatterns();
7350  int s[10] = symbolPatterns();
7351  return int[20](
7352    d[0], d[1], d[2], d[3], d[4],
7353    d[5], d[6], d[7], d[8], d[9],
7354    s[0], s[1], s[2], s[3], s[4],
7355    s[5], s[6], s[7], s[8], s[9]
7356  );
7357}
7358
7359// ---------------------------------------------------------------------------
7360// Character pixel lookup
7361// ---------------------------------------------------------------------------
7362float getFireCharPixel(int charIndex, vec2 pos, int charSetId) {
7363  int ix = int(pos.x);
7364  int iy = int(pos.y);
7365  if (ix < 0 || ix >= 5 || iy < 0 || iy >= 5) return 0.0;
7366
7367  int bit = iy * 5 + ix;
7368  int pattern = 0;
7369
7370  if (charSetId == 0) {
7371    // Digits
7372    int pats[10] = digitPatterns();
7373    int idx = clamp(charIndex, 0, 9);
7374    pattern = pats[idx];
7375  } else if (charSetId == 1) {
7376    // Symbols
7377    int pats[10] = symbolPatterns();
7378    int idx = clamp(charIndex, 0, 9);
7379    pattern = pats[idx];
7380  } else if (charSetId == 2) {
7381    // Mixed: 0-9 digits, 10-19 symbols
7382    int mixIdx = charIndex;
7383    if (mixIdx < 10) {
7384      int pats[10] = digitPatterns();
7385      pattern = pats[clamp(mixIdx, 0, 9)];
7386    } else {
7387      int pats[10] = symbolPatterns();
7388      pattern = pats[clamp(mixIdx - 10, 0, 9)];
7389    }
7390  } else {
7391    // Katakana
7392    int pats[10] = katakanaPatterns();
7393    int idx = clamp(charIndex, 0, 9);
7394    pattern = pats[idx];
7395  }
7396
7397  return float((pattern >> bit) & 1);
7398}
7399
7400// ---------------------------------------------------------------------------
7401// Density field functions
7402// ---------------------------------------------------------------------------
7403
7404// Fire: FBM + upward domain warp, dense at bottom, sparse at top
7405float fireDensity(vec2 p, float time, float scale, float warp) {
7406  vec2 q = p * scale;
7407  q.y -= time * 0.3;
7408
7409  // Two noise layers for domain warp
7410  float w1 = snoise(q * 0.8 + time * 0.15);
7411  float w2 = snoise(q * 1.5 - time * 0.12);
7412  q += vec2(w1, w2) * warp;
7413
7414  float d = fbm(q, 2.0, 4, 1.0, 2.0) * 0.5 + 0.5;
7415
7416  // Vertical bias: dense at bottom, sparse at top
7417  d *= mix(0.2, 1.0, pow(1.0 - p.y, 0.5));
7418
7419  return clamp(d, 0.0, 1.0);
7420}
7421
7422// Rain: continuous falling character streams — columns of drops one after another
7423float rainDensity(vec2 p, float time, float scale, float warp) {
7424  float density = 0.0;
7425
7426  // Two depth layers for parallax richness
7427  for (int layer = 0; layer < 2; layer++) {
7428    float layerSeed = float(layer) * 53.7;
7429    float layerScale = scale * (1.0 + float(layer) * 0.3);
7430    float dropSpeed = 1.5 + float(layer) * 0.7;
7431
7432    // Dense column grid — many narrow columns
7433    float colWidth = 1.0 / (layerScale * 20.0);
7434    float col = floor(p.x / colWidth);
7435    float colFrac = fract(p.x / colWidth);
7436
7437    float ch = columnHash(col + layerSeed);
7438
7439    // 90% of columns active for heavy rain
7440    if (ch < 0.1) continue;
7441
7442    // Column thinness — narrow vertical band
7443    float wobble = snoise(vec2(col * 0.2, time * 0.08)) * warp * 0.15;
7444    float thin = smoothstep(0.4, 0.1, abs(colFrac - 0.5 - wobble));
7445
7446    // Scroll Y downward — continuous falling stream
7447    float colSpeed = dropSpeed * (0.5 + ch);
7448    float scrollY = p.y + time * colSpeed * 0.25 + ch * 13.0;
7449
7450    // Continuous stream with organic gaps via layered noise
7451    // Low-freq noise for large stream segments, hi-freq for texture
7452    float n1 = snoise(vec2(col * 1.7 + layerSeed, scrollY * scale * 3.0));
7453    float n2 = snoise(vec2(col * 0.5 + layerSeed * 2.0, scrollY * scale * 6.0)) * 0.3;
7454    float dropY = n1 + n2;
7455
7456    // Bias toward visible — only deepest troughs create gaps (~75% solid)
7457    float streamMask = smoothstep(-0.6, -0.1, dropY);
7458
7459    // Intensity variation along stream for visual depth
7460    float streamIntensity = 0.4 + 0.6 * smoothstep(-0.1, 0.5, dropY);
7461
7462    density += thin * streamMask * streamIntensity;
7463  }
7464
7465  return clamp(density, 0.0, 1.0);
7466}
7467
7468// Wind: horizontally-stretched noise with directional flow
7469float windDensity(vec2 p, float time, float scale, float warp) {
7470  float angle = radians(u_gridAngle);
7471  vec2 windDir = vec2(cos(angle), sin(angle));
7472
7473  // Horizontally stretched noise (compress along wind direction)
7474  vec2 q = p * scale;
7475  // Stretch perpendicular to wind, compress along wind
7476  float along = dot(q, windDir);
7477  float perp = dot(q, vec2(-windDir.y, windDir.x));
7478  vec2 stretched = vec2(along * 0.3, perp * 2.0);
7479
7480  // Directional flow
7481  stretched.x -= time * 0.5;
7482
7483  // Domain warp
7484  float w1 = snoise(stretched * 0.8 + time * 0.1) * warp;
7485  stretched += vec2(w1);
7486
7487  float d = snoise(stretched) * 0.5 + 0.5;
7488
7489  // Gust bursts: periodic sine modulation
7490  float gust = sin(along * 2.0 - time * 1.5) * 0.5 + 0.5;
7491  d *= mix(0.5, 1.0, gust);
7492
7493  // Second octave
7494  float d2 = snoise(stretched * 2.0 + time * 0.15) * 0.5 + 0.5;
7495  d = mix(d, d2, 0.3);
7496
7497  return clamp(d, 0.0, 1.0);
7498}
7499
7500// Storm: turbulent FBM with rotating vortex spiral
7501float stormDensity(vec2 p, float time, float scale, float warp) {
7502  vec2 center = vec2(0.5, 0.5);
7503  vec2 delta = p - center;
7504  float dist = length(delta);
7505  float ang = atan(delta.y, delta.x);
7506
7507  // Spiral rotation: angle increases with distance and time
7508  ang += dist * 4.0 - time * 0.8;
7509
7510  // Convert back to cartesian for noise sampling
7511  vec2 q = vec2(cos(ang), sin(ang)) * dist * scale;
7512
7513  // Domain warp
7514  float w1 = snoise(q * 0.6 + time * 0.2) * warp;
7515  float w2 = snoise(q * 1.2 - time * 0.15) * warp;
7516  q += vec2(w1, w2);
7517
7518  // High-frequency turbulent FBM (5 octaves)
7519  float d = fbm(q, 2.0, 5, 1.0, 2.0) * 0.5 + 0.5;
7520
7521  // Add spiral arms
7522  float arms = sin(ang * 3.0 + dist * 8.0 - time * 1.2) * 0.5 + 0.5;
7523  d = mix(d, arms, 0.4);
7524
7525  // Eye of the storm: less dense at exact center
7526  float eye = smoothstep(0.0, 0.15, dist);
7527  d *= eye;
7528
7529  return clamp(d, 0.0, 1.0);
7530}
7531
7532// Sand: flowing particles drifting with wind, settling with gravity
7533float sandDensity(vec2 p, float time, float scale, float warp) {
7534  float density = 0.0;
7535
7536  // Wind direction from gridAngle
7537  float angle = radians(u_gridAngle);
7538  vec2 windDir = vec2(cos(angle), sin(angle));
7539
7540  // Multiple particle layers at different scales
7541  for (int layer = 0; layer < 4; layer++) {
7542    float layerOffset = float(layer) * 3.14;
7543    float layerScale = scale * (1.5 + float(layer) * 0.8);
7544
7545    // Move with wind + slight gravity (downward drift)
7546    vec2 flow = windDir * time * 0.3 * (1.0 + float(layer) * 0.2);
7547    flow.y -= time * 0.05; // gravity settling
7548
7549    vec2 q = p * layerScale + flow + layerOffset;
7550
7551    // Domain warp for organic drift
7552    float w1 = snoise(q * 0.3 + time * 0.08) * warp;
7553    float w2 = snoise(q * 0.5 - time * 0.06) * warp * 0.5;
7554    q += vec2(w1, w2);
7555
7556    // High-frequency noise for individual grain-like dots
7557    float n = snoise(q * 2.0) * 0.5 + 0.5;
7558
7559    // Sharpen into point-like particles (high power = sharper dots)
7560    n = pow(n, 3.0);
7561
7562    // Layer intensity varies (front layers denser)
7563    float layerWeight = 0.4 + 0.2 * float(3 - layer);
7564    density += n * layerWeight;
7565  }
7566
7567  // Gravity bias: denser at bottom, sparser at top
7568  density *= mix(0.3, 1.0, pow(1.0 - p.y, 0.7));
7569
7570  // Turbulent gusts
7571  float gust = snoise(vec2(p.x * 2.0 + time * 0.4, p.y * 0.5)) * 0.5 + 0.5;
7572  density *= mix(0.6, 1.2, gust);
7573
7574  return clamp(density, 0.0, 1.0);
7575}
7576
7577// ---------------------------------------------------------------------------
7578// Main
7579// ---------------------------------------------------------------------------
7580void main() {
7581  vec2 uv = vUv;
7582  float time = u_time * u_speed;
7583  float aspect = u_resolution.x / max(u_resolution.y, 1.0);
7584  uv = applyMouseWarp(uv, aspect);
7585
7586  // Base gradient direction for color sampling and self-blend
7587  float angle = radians(u_direction);
7588  vec2 dir = vec2(cos(angle), sin(angle));
7589  float baseT = dot(uv - 0.5, dir) + 0.5;
7590  baseT = clamp(baseT, 0.0, 1.0);
7591
7592  // Image luminance modulation
7593  float imgLum = imageLuminance(uv, aspect);
7594  if (imgLum >= 0.0) {
7595    baseT = mix(baseT, imgLum, u_imageIntensity);
7596  }
7597
7598  // -----------------------------------------------------------------------
7599  // Rotated cell grid from gl_FragCoord.xy
7600  // -----------------------------------------------------------------------
7601  float cellSize = max(u_fontSize * u_density, 1.0);
7602  float gridRad = radians(u_gridAngle);
7603  mat2 gridRot = mat2(cos(gridRad), -sin(gridRad), sin(gridRad), cos(gridRad));
7604
7605  vec2 rotatedCoord = gridRot * gl_FragCoord.xy;
7606  vec2 cell = floor(rotatedCoord / cellSize);
7607  vec2 cellUV = fract(rotatedCoord / cellSize) * 5.0;
7608
7609  // Cell center in UV space (for mouse distance, color sampling)
7610  vec2 cellCenterPx = (cell + 0.5) * cellSize;
7611  // Unrotate back to screen space
7612  mat2 gridRotInv = mat2(cos(gridRad), sin(gridRad), -sin(gridRad), cos(gridRad));
7613  vec2 cellCenterScreen = gridRotInv * cellCenterPx;
7614  vec2 cellCenter = cellCenterScreen / u_resolution;
7615
7616  // -----------------------------------------------------------------------
7617  // Sample density field at cell center
7618  // -----------------------------------------------------------------------
7619  float scale = u_noiseScale;
7620  float warp = u_warpIntensity;
7621  float flowTime = time * u_flowSpeed;
7622
7623  float density = 0.0;
7624  if (u_effectType == 0) {
7625    density = fireDensity(cellCenter, flowTime, scale, warp);
7626  } else if (u_effectType == 1) {
7627    density = rainDensity(cellCenter, flowTime, scale, warp);
7628  } else if (u_effectType == 2) {
7629    density = windDensity(cellCenter, flowTime, scale, warp);
7630  } else if (u_effectType == 3) {
7631    density = stormDensity(cellCenter, flowTime, scale, warp);
7632  } else {
7633    density = sandDensity(cellCenter, flowTime, scale, warp);
7634  }
7635
7636  // Turbulence: add high-frequency noise variation
7637  if (u_turbulence > 0.0) {
7638    float turb = snoise(cellCenter * scale * 3.0 + flowTime * 0.3) * 0.5 + 0.5;
7639    density = mix(density, density * turb, u_turbulence * 0.5);
7640  }
7641
7642  // Apply contrast (power curve)
7643  density = pow(clamp(density, 0.0, 1.0), u_contrast);
7644
7645  // Apply threshold (smoothstep cutoff)
7646  density = smoothstep(u_threshold, u_threshold + 0.3, density);
7647
7648  // -----------------------------------------------------------------------
7649  // Mouse cursor boosts local density
7650  // -----------------------------------------------------------------------
7651  if (u_mouseMode > 0) {
7652    vec2 mouseUv = u_mouse * 0.5 + 0.5;
7653    float mouseDist = distance(cellCenter, mouseUv);
7654
7655    if (u_cursorInfluence > 0.0) {
7656      float cursorBoost = smoothstep(u_cursorRadius, 0.0, mouseDist) * u_cursorInfluence;
7657      density = clamp(density + cursorBoost, 0.0, 1.0);
7658    }
7659
7660    // Mouse velocity trail: subtle density boost along movement path
7661    float velMag = length(u_mouseVelocity);
7662    if (velMag > 0.001) {
7663      float trailBoost = smoothstep(u_cursorRadius * 2.0, 0.0, mouseDist) * velMag * 5.0;
7664      trailBoost = clamp(trailBoost, 0.0, 0.4);
7665      density = clamp(density + trailBoost, 0.0, 1.0);
7666    }
7667  }
7668
7669  // -----------------------------------------------------------------------
7670  // Map density -> character index (ordered by visual weight)
7671  // -----------------------------------------------------------------------
7672  int charCount = 10;
7673  if (u_charSet == 2) charCount = 20; // mixed set
7674
7675  // Cell hash for per-cell character variation
7676  float charHash = cellHash(cell.x, cell.y);
7677
7678  // Base index from density (0 = lightest/sparsest, 9/19 = heaviest/densest)
7679  int charIdx = int(density * float(charCount - 1));
7680
7681  // Small random variation for natural feel (+/- 1 from density index)
7682  int variation = int(charHash * 3.0) - 1; // -1, 0, or 1
7683  charIdx = clamp(charIdx + variation, 0, charCount - 1);
7684
7685  // -----------------------------------------------------------------------
7686  // Render 5x5 bitmap pixel
7687  // -----------------------------------------------------------------------
7688  float pixel = getFireCharPixel(charIdx, cellUV, u_charSet);
7689
7690  // -----------------------------------------------------------------------
7691  // Color composition: light background, dark ink
7692  // -----------------------------------------------------------------------
7693
7694  // Background color — solid or gradient mode
7695  float gradientT = dot(cellCenter - 0.5, dir) + 0.5;
7696  gradientT = clamp(gradientT, 0.0, 1.0);
7697  vec3 bgColor = (u_bgMode == 1)
7698    ? mixColors(u_colors, u_colorPositions, u_colorCount, gradientT)
7699    : u_bgColorRGB;
7700
7701  // Ink color — user-chosen inkColor as base, with optional gradient tint
7702  vec3 darkInk = u_inkColorRGB;
7703  // Gradient-sampled ink for color variation across the canvas
7704  vec3 gradientInk = mixColors(u_colors, u_colorPositions, u_colorCount, gradientT);
7705  vec3 inkColor = mix(darkInk, gradientInk, u_colorInfluence);
7706
7707  // Final composition: mix background toward ink based on pixel * density
7708  vec3 color = mix(bgColor, inkColor, pixel * density);
7709
7710  // -----------------------------------------------------------------------
7711  // Grain
7712  // -----------------------------------------------------------------------
7713  color += grain(uv, time, u_grain);
7714
7715  // -----------------------------------------------------------------------
7716  // Self-blend
7717  // -----------------------------------------------------------------------
7718  if (u_blendMode > 0) {
7719    float bRotation = u_blendAnimRotation > 0 ? animatedBlendRotation(u_blendRotation, time, u_blendAnimSpeed) : u_blendRotation;
7720    float bOffset = u_blendAnimOffset > 0 ? animatedBlendOffset(u_blendOffset, time, u_blendAnimSpeed) : u_blendOffset;
7721    float bIntensity = u_blendAnimIntensity > 0 ? animatedBlendIntensity(u_blendIntensity, time, u_blendAnimSpeed) : u_blendIntensity;
7722    bIntensity = noiseModulatedIntensity(bIntensity, uv, time, u_blendNoiseMask, u_blendNoiseScale);
7723    bIntensity = applyBlendFalloff(bIntensity, baseT, u_blendFalloff);
7724    float tB = blendLayerT(uv, baseT, u_resolution, u_direction, bOffset, bRotation, u_blendScale);
7725    vec3 blendCol = mixColors(u_colors, u_colorPositions, u_colorCount, tB);
7726    color = applyBlendMode(u_blendMode, color, blendCol, bIntensity);
7727    if (u_blendDualLayer > 0 && u_blendMode2 > 0) {
7728      float tB2 = blendLayerT(uv, baseT, u_resolution, u_direction, u_blendOffset2, u_blendRotation2, u_blendScale2);
7729      vec3 blendCol2 = mixColors(u_colors, u_colorPositions, u_colorCount, tB2);
7730      color = applyBlendMode(u_blendMode2, color, blendCol2, u_blendIntensity2);
7731    }
7732  }
7733
7734  // -----------------------------------------------------------------------
7735  // Final output
7736  // -----------------------------------------------------------------------
7737  fragColor = outputColor(color, gl_FragCoord.xy, 1.0);
7738}
7739`,E=`#version 300 es
7740precision highp float;
7741
7742in vec2 vUv;
7743out vec4 fragColor;
7744
7745uniform float u_time;
7746uniform float u_speed;
7747uniform vec3 u_colors[12];
7748uniform float u_colorPositions[12];
7749uniform int u_colorCount;
7750uniform float u_direction;
7751uniform float u_grain;
7752uniform vec2 u_resolution;
7753
7754// Blend uniforms
7755uniform int u_blendMode;
7756uniform float u_blendIntensity;
7757uniform float u_blendOffset;
7758uniform float u_blendRotation;
7759uniform float u_blendScale;
7760uniform int u_blendAnimRotation;
7761uniform int u_blendAnimIntensity;
7762uniform int u_blendAnimOffset;
7763uniform float u_blendAnimSpeed;
7764uniform float u_blendNoiseMask;
7765uniform float u_blendNoiseScale;
7766uniform float u_blendFalloff;
7767uniform int u_blendDualLayer;
7768uniform int u_blendMode2;
7769uniform float u_blendIntensity2;
7770uniform float u_blendOffset2;
7771uniform float u_blendRotation2;
7772uniform float u_blendScale2;
7773
7774__COMMON__
7775
7776// Hash functions for particle positions
7777float hash11(float p) {
7778  p = fract(p * 0.1031);
7779  p *= p + 33.33;
7780  p *= p + p;
7781  return fract(p);
7782}
7783
7784vec2 hash21(float p) {
7785  vec3 p3 = fract(vec3(p) * vec3(0.1031, 0.1030, 0.0973));
7786  p3 += dot(p3, p3.yzx + 33.33);
7787  return fract((p3.xx + p3.yz) * p3.zy);
7788}
7789
7790// Curl noise: rotate gradient of scalar noise by 90 degrees
7791vec2 curlNoise(vec2 p, float t) {
7792  float eps = 0.01;
7793  float nx = snoise(vec2(p.x + eps, p.y) + t * 0.1);
7794  float ny = snoise(vec2(p.x, p.y + eps) + t * 0.1);
7795  float n0 = snoise(p + t * 0.1);
7796  // Curl = (dN/dy, -dN/dx)
7797  float dNdx = (nx - n0) / eps;
7798  float dNdy = (ny - n0) / eps;
7799  return vec2(dNdy, -dNdx) * 0.15;
7800}
7801
7802void main() {
7803  vec2 uv = vUv;
7804  float aspect = u_resolution.x / max(u_resolution.y, 1.0);
7805  uv = applyMouseWarp(uv, aspect);
7806  float t = u_time * u_speed;
7807
7808  vec2 aspectUv = vec2(uv.x * aspect, uv.y);
7809
7810  // Dark tinted background from gradient
7811  float dirAngle = radians(u_direction);
7812  vec2 dir = vec2(cos(dirAngle), sin(dirAngle));
7813  float baseT = dot(uv - 0.5, dir) + 0.5;
7814
7815  float imgLum = imageLuminance(uv, aspect);
7816  if (imgLum >= 0.0) baseT = mix(baseT, imgLum, u_imageIntensity);
7817
7818  vec3 bgColor = mixColors(u_colors, u_colorPositions, u_colorCount, baseT);
7819  bgColor *= 0.08;
7820
7821  // Accumulate particle glow
7822  vec3 particleAccum = vec3(0.0);
7823  float totalGlow = 0.0;
7824
7825  const int PARTICLE_COUNT = 300;
7826  for (int i = 0; i < PARTICLE_COUNT; i++) {
7827    float fi = float(i);
7828
7829    // Base position from hash
7830    vec2 basePos = hash21(fi * 7.23 + 1.7);
7831
7832    // Animate along curl noise field
7833    vec2 pos = basePos;
7834    // Accumulate curl displacement over multiple steps for smoother trails
7835    vec2 curl1 = curlNoise(pos * 3.0, t * 0.4 + fi * 0.01);
7836    vec2 curl2 = curlNoise((pos + curl1) * 3.0, t * 0.4 + fi * 0.01 + 10.0);
7837    pos += curl1 + curl2 * 0.5;
7838
7839    // Wrap positions
7840    pos = fract(pos);
7841
7842    // Aspect correct for distance calc
7843    vec2 pAspect = vec2(pos.x * aspect, pos.y);
7844    float d = distance(aspectUv, pAspect);
7845
7846    // Particle size varies per particle
7847    float size = mix(0.006, 0.018, hash11(fi * 3.14));
7848
7849    // Soft gaussian dot
7850    float glow = exp(-d * d / (size * size));
7851
7852    // Fade particles based on curl speed for trailing effect
7853    float speed = length(curl1) * 8.0;
7854    float trail = 0.6 + 0.4 * clamp(speed, 0.0, 1.0);
7855    glow *= trail;
7856
7857    // Per-particle palette color
7858    float colorT = fract(fi / float(PARTICLE_COUNT) + t * 0.05);
7859    vec3 pColor = mixColors(u_colors, u_colorPositions, u_colorCount, colorT);
7860    pColor = max(pColor, vec3(0.12));
7861
7862    particleAccum += pColor * glow;
7863    totalGlow += glow;
7864  }
7865
7866  // Soft background glow from nearby particle density
7867  float densityGlow = min(totalGlow, 3.0) * 0.05;
7868  vec3 densityColor = mixColors(u_colors, u_colorPositions, u_colorCount, baseT);
7869
7870  // Compose
7871  vec3 color = bgColor;
7872  color += densityColor * densityGlow;
7873  color += particleAccum * 1.8;
7874
7875  color += grain(uv, t, u_grain);
7876
7877  // Self-blend
7878  if (u_blendMode > 0) {
7879    float bRotation = u_blendAnimRotation > 0 ? animatedBlendRotation(u_blendRotation, t, u_blendAnimSpeed) : u_blendRotation;
7880    float bOffset = u_blendAnimOffset > 0 ? animatedBlendOffset(u_blendOffset, t, u_blendAnimSpeed) : u_blendOffset;
7881    float bIntensity = u_blendAnimIntensity > 0 ? animatedBlendIntensity(u_blendIntensity, t, u_blendAnimSpeed) : u_blendIntensity;
7882    bIntensity = noiseModulatedIntensity(bIntensity, uv, t, u_blendNoiseMask, u_blendNoiseScale);
7883    bIntensity = applyBlendFalloff(bIntensity, baseT, u_blendFalloff);
7884    float tB = blendLayerT(uv, baseT, u_resolution, u_direction, bOffset, bRotation, u_blendScale);
7885    vec3 blendCol = mixColors(u_colors, u_colorPositions, u_colorCount, tB);
7886    color = applyBlendMode(u_blendMode, color, blendCol, bIntensity);
7887    if (u_blendDualLayer > 0 && u_blendMode2 > 0) {
7888      float tB2 = blendLayerT(uv, baseT, u_resolution, u_direction, u_blendOffset2, u_blendRotation2, u_blendScale2);
7889      vec3 blendCol2 = mixColors(u_colors, u_colorPositions, u_colorCount, tB2);
7890      color = applyBlendMode(u_blendMode2, color, blendCol2, u_blendIntensity2);
7891    }
7892  }
7893
7894  fragColor = outputColor(color, gl_FragCoord.xy, 1.0);
7895}
7896`,W=`#version 300 es
7897precision highp float;
7898
7899in vec2 vUv;
7900out vec4 fragColor;
7901
7902uniform float u_time;
7903uniform float u_speed;
7904uniform vec3 u_colors[12];
7905uniform float u_colorPositions[12];
7906uniform int u_colorCount;
7907uniform float u_direction;
7908uniform float u_grain;
7909uniform vec2 u_resolution;
7910
7911// Blend uniforms
7912uniform int u_blendMode;
7913uniform float u_blendIntensity;
7914uniform float u_blendOffset;
7915uniform float u_blendRotation;
7916uniform float u_blendScale;
7917uniform int u_blendAnimRotation;
7918uniform int u_blendAnimIntensity;
7919uniform int u_blendAnimOffset;
7920uniform float u_blendAnimSpeed;
7921uniform float u_blendNoiseMask;
7922uniform float u_blendNoiseScale;
7923uniform float u_blendFalloff;
7924uniform int u_blendDualLayer;
7925uniform int u_blendMode2;
7926uniform float u_blendIntensity2;
7927uniform float u_blendOffset2;
7928uniform float u_blendRotation2;
7929uniform float u_blendScale2;
7930
7931__COMMON__
7932
7933// Deterministic hash
7934float hash11(float p) {
7935  p = fract(p * 0.1031);
7936  p *= p + 33.33;
7937  p *= p + p;
7938  return fract(p);
7939}
7940
7941vec2 hash21(float p) {
7942  vec3 p3 = fract(vec3(p) * vec3(0.1031, 0.1030, 0.0973));
7943  p3 += dot(p3, p3.yzx + 33.33);
7944  return fract((p3.xx + p3.yz) * p3.zy);
7945}
7946
7947// Bokeh circle: soft fill with brighter ring edge
7948float bokehCircle(float dist, float radius, float softness) {
7949  // Soft interior
7950  float inner = smoothstep(radius, radius * (1.0 - softness), dist);
7951  // Bright ring near edge
7952  float ring = smoothstep(radius * 1.05, radius, dist) *
7953               smoothstep(radius * 0.75, radius * 0.92, dist);
7954  return inner * 0.5 + ring * 0.7;
7955}
7956
7957void main() {
7958  vec2 uv = vUv;
7959  float aspect = u_resolution.x / max(u_resolution.y, 1.0);
7960  uv = applyMouseWarp(uv, aspect);
7961  float t = u_time * u_speed;
7962
7963  vec2 aspectUv = vec2(uv.x * aspect, uv.y);
7964
7965  // Background: warm-tinted gradient
7966  float dirAngle = radians(u_direction);
7967  vec2 dir = vec2(cos(dirAngle), sin(dirAngle));
7968  float baseT = dot(uv - 0.5, dir) + 0.5;
7969
7970  float imgLum = imageLuminance(uv, aspect);
7971  if (imgLum >= 0.0) baseT = mix(baseT, imgLum, u_imageIntensity);
7972
7973  vec3 bgColor = mixColors(u_colors, u_colorPositions, u_colorCount, baseT);
7974  bgColor *= 0.15;
7975
7976  vec3 colorAccum = bgColor;
7977
7978  // 3 depth layers: back (0) to front (2)
7979  for (int layer = 0; layer < 3; layer++) {
7980    float fl = float(layer);
7981    float depth = 1.0 - fl * 0.35; // 1.0, 0.65, 0.3
7982
7983    // Deeper layers: softer, larger, more transparent
7984    float baseRadius = mix(0.08, 0.03, fl / 2.0);
7985    float softness = mix(0.7, 0.3, fl / 2.0);
7986    float opacity = mix(0.25, 0.6, fl / 2.0);
7987    int count = int(mix(35.0, 50.0, fl / 2.0));
7988
7989    // Parallax drift — each layer moves at different speed
7990    vec2 drift = vec2(
7991      sin(t * 0.15 * depth + fl * 2.0) * 0.08,
7992      cos(t * 0.12 * depth + fl * 1.5) * 0.06
7993    ) * depth;
7994
7995    for (int i = 0; i < 50; i++) {
7996      if (i >= count) break;
7997      float fi = float(i);
7998      float seed = fi * 13.73 + fl * 100.0;
7999
8000      // Random position
8001      vec2 pos = hash21(seed);
8002      pos += drift;
8003      pos = fract(pos);
8004
8005      // Size variation per circle
8006      float sizeVar = 0.6 + 0.8 * hash11(seed + 7.0);
8007      float radius = baseRadius * sizeVar;
8008
8009      vec2 pAspect = vec2(pos.x * aspect, pos.y);
8010      float d = distance(aspectUv, pAspect);
8011
8012      float b = bokehCircle(d, radius, softness);
8013
8014      // Color from palette with warm shift
8015      float colorT = fract(hash11(seed + 3.0) + fl * 0.15);
8016      vec3 circleColor = mixColors(u_colors, u_colorPositions, u_colorCount, colorT);
8017      // Warm tinting — push slightly toward amber
8018      circleColor = mix(circleColor, circleColor * vec3(1.1, 1.0, 0.85), 0.3);
8019      circleColor = max(circleColor, vec3(0.08));
8020
8021      colorAccum += circleColor * b * opacity;
8022    }
8023  }
8024
8025  vec3 color = colorAccum;
8026
8027  color += grain(uv, t, u_grain);
8028
8029  // Self-blend
8030  if (u_blendMode > 0) {
8031    float bRotation = u_blendAnimRotation > 0 ? animatedBlendRotation(u_blendRotation, t, u_blendAnimSpeed) : u_blendRotation;
8032    float bOffset = u_blendAnimOffset > 0 ? animatedBlendOffset(u_blendOffset, t, u_blendAnimSpeed) : u_blendOffset;
8033    float bIntensity2 = u_blendAnimIntensity > 0 ? animatedBlendIntensity(u_blendIntensity, t, u_blendAnimSpeed) : u_blendIntensity;
8034    bIntensity2 = noiseModulatedIntensity(bIntensity2, uv, t, u_blendNoiseMask, u_blendNoiseScale);
8035    bIntensity2 = applyBlendFalloff(bIntensity2, baseT, u_blendFalloff);
8036    float tB = blendLayerT(uv, baseT, u_resolution, u_direction, bOffset, bRotation, u_blendScale);
8037    vec3 blendCol = mixColors(u_colors, u_colorPositions, u_colorCount, tB);
8038    color = applyBlendMode(u_blendMode, color, blendCol, bIntensity2);
8039    if (u_blendDualLayer > 0 && u_blendMode2 > 0) {
8040      float tB2 = blendLayerT(uv, baseT, u_resolution, u_direction, u_blendOffset2, u_blendRotation2, u_blendScale2);
8041      vec3 blendCol2 = mixColors(u_colors, u_colorPositions, u_colorCount, tB2);
8042      color = applyBlendMode(u_blendMode2, color, blendCol2, u_blendIntensity2);
8043    }
8044  }
8045
8046  fragColor = outputColor(color, gl_FragCoord.xy, 1.0);
8047}
8048`,V=`#version 300 es
8049precision highp float;
8050
8051in vec2 vUv;
8052out vec4 fragColor;
8053
8054uniform float u_time;
8055uniform float u_speed;
8056uniform vec3 u_colors[12];
8057uniform float u_colorPositions[12];
8058uniform int u_colorCount;
8059uniform float u_direction;
8060uniform float u_grain;
8061uniform vec2 u_resolution;
8062
8063// Blend uniforms
8064uniform int u_blendMode;
8065uniform float u_blendIntensity;
8066uniform float u_blendOffset;
8067uniform float u_blendRotation;
8068uniform float u_blendScale;
8069uniform int u_blendAnimRotation;
8070uniform int u_blendAnimIntensity;
8071uniform int u_blendAnimOffset;
8072uniform float u_blendAnimSpeed;
8073uniform float u_blendNoiseMask;
8074uniform float u_blendNoiseScale;
8075uniform float u_blendFalloff;
8076uniform int u_blendDualLayer;
8077uniform int u_blendMode2;
8078uniform float u_blendIntensity2;
8079uniform float u_blendOffset2;
8080uniform float u_blendRotation2;
8081uniform float u_blendScale2;
8082
8083__COMMON__
8084
8085float hash11(float p) {
8086  p = fract(p * 0.1031);
8087  p *= p + 33.33;
8088  p *= p + p;
8089  return fract(p);
8090}
8091
8092vec2 hash22(vec2 p) {
8093  vec3 p3 = fract(vec3(p.xyx) * vec3(0.1031, 0.1030, 0.0973));
8094  p3 += dot(p3, p3.yzx + 33.33);
8095  return fract((p3.xx + p3.yz) * p3.zy);
8096}
8097
8098// 4-point star shape via polar distance
8099float starShape(vec2 p, float size, int points) {
8100  float angle = atan(p.y, p.x);
8101  float r = length(p);
8102  float fp = float(points);
8103  // Modulate radius: peaks at each arm, valleys in between
8104  float star = cos(angle * fp) * 0.4 + 0.6;
8105  // Additional thin cross spikes
8106  float cross = pow(abs(cos(angle * fp * 0.5)), 16.0) * 0.5;
8107  float shape = r / (size * (star + cross));
8108  return shape;
8109}
8110
8111void main() {
8112  vec2 uv = vUv;
8113  float aspect = u_resolution.x / max(u_resolution.y, 1.0);
8114  uv = applyMouseWarp(uv, aspect);
8115  float t = u_time * u_speed;
8116
8117  vec2 aspectUv = vec2(uv.x * aspect, uv.y);
8118
8119  // Dark background with subtle gradient tint
8120  float dirAngle = radians(u_direction);
8121  vec2 dir = vec2(cos(dirAngle), sin(dirAngle));
8122  float baseT = dot(uv - 0.5, dir) + 0.5;
8123
8124  float imgLum = imageLuminance(uv, aspect);
8125  if (imgLum >= 0.0) baseT = mix(baseT, imgLum, u_imageIntensity);
8126
8127  vec3 bgColor = mixColors(u_colors, u_colorPositions, u_colorCount, baseT);
8128  bgColor *= 0.06;
8129
8130  vec3 color = bgColor;
8131
8132  // Grid-based particle placement for ~150 stars
8133  const float GRID = 12.0;
8134  for (float gy = 0.0; gy < GRID; gy++) {
8135    for (float gx = 0.0; gx < GRID; gx++) {
8136      vec2 cell = vec2(gx, gy);
8137      vec2 cellHash = hash22(cell + 0.5);
8138
8139      // Skip ~15% of cells for irregular distribution
8140      if (cellHash.x < 0.15) continue;
8141
8142      // Particle position within cell
8143      vec2 jitter = hash22(cell * 17.3 + 3.7);
8144      vec2 starPos = (cell + 0.15 + jitter * 0.7) / GRID;
8145
8146      vec2 pAspect = vec2(starPos.x * aspect, starPos.y);
8147      vec2 delta = aspectUv - pAspect;
8148
8149      float seed = cell.x * GRID + cell.y;
8150
8151      // Varied size
8152      float size = mix(0.005, 0.022, hash11(seed * 5.17));
8153
8154      // Star shape distance
8155      float starDist = starShape(delta, size, 4);
8156
8157      // Core glow (gaussian)
8158      float coreGlow = exp(-starDist * starDist * 8.0);
8159
8160      // Sharp star rays
8161      float rays = max(1.0 - starDist, 0.0);
8162      rays = pow(rays, 3.0);
8163
8164      float brightness = coreGlow * 0.8 + rays * 0.6;
8165
8166      // Per-star twinkle
8167      float twinkleSpeed = mix(1.5, 4.0, hash11(seed * 2.31));
8168      float twinklePhase = hash11(seed * 8.93) * 6.283;
8169      float twinkle = sin(t * twinkleSpeed + twinklePhase) * 0.5 + 0.5;
8170      // Sharpen twinkle: occasional bright flashes
8171      twinkle = pow(twinkle, 2.0);
8172      // Keep minimum brightness so stars don't fully vanish
8173      twinkle = 0.15 + 0.85 * twinkle;
8174
8175      brightness *= twinkle;
8176
8177      // Color from palette
8178      float colorT = hash11(seed * 3.71);
8179      vec3 starColor = mixColors(u_colors, u_colorPositions, u_colorCount, colorT);
8180      starColor = max(starColor, vec3(0.2));
8181      // Push bright stars toward white
8182      starColor = mix(starColor, vec3(1.0), brightness * 0.4);
8183
8184      color += starColor * brightness;
8185    }
8186  }
8187
8188  color += grain(uv, t, u_grain);
8189
8190  // Self-blend
8191  if (u_blendMode > 0) {
8192    float bRotation = u_blendAnimRotation > 0 ? animatedBlendRotation(u_blendRotation, t, u_blendAnimSpeed) : u_blendRotation;
8193    float bOffset = u_blendAnimOffset > 0 ? animatedBlendOffset(u_blendOffset, t, u_blendAnimSpeed) : u_blendOffset;
8194    float bInt = u_blendAnimIntensity > 0 ? animatedBlendIntensity(u_blendIntensity, t, u_blendAnimSpeed) : u_blendIntensity;
8195    bInt = noiseModulatedIntensity(bInt, uv, t, u_blendNoiseMask, u_blendNoiseScale);
8196    bInt = applyBlendFalloff(bInt, baseT, u_blendFalloff);
8197    float tB = blendLayerT(uv, baseT, u_resolution, u_direction, bOffset, bRotation, u_blendScale);
8198    vec3 blendCol = mixColors(u_colors, u_colorPositions, u_colorCount, tB);
8199    color = applyBlendMode(u_blendMode, color, blendCol, bInt);
8200    if (u_blendDualLayer > 0 && u_blendMode2 > 0) {
8201      float tB2 = blendLayerT(uv, baseT, u_resolution, u_direction, u_blendOffset2, u_blendRotation2, u_blendScale2);
8202      vec3 blendCol2 = mixColors(u_colors, u_colorPositions, u_colorCount, tB2);
8203      color = applyBlendMode(u_blendMode2, color, blendCol2, u_blendIntensity2);
8204    }
8205  }
8206
8207  fragColor = outputColor(color, gl_FragCoord.xy, 1.0);
8208}
8209`,X=`#version 300 es
8210precision highp float;
8211
8212in vec2 vUv;
8213out vec4 fragColor;
8214
8215uniform float u_time;
8216uniform float u_speed;
8217uniform vec3 u_colors[12];
8218uniform float u_colorPositions[12];
8219uniform int u_colorCount;
8220uniform float u_direction;
8221uniform float u_grain;
8222uniform vec2 u_resolution;
8223
8224// Blend uniforms
8225uniform int u_blendMode;
8226uniform float u_blendIntensity;
8227uniform float u_blendOffset;
8228uniform float u_blendRotation;
8229uniform float u_blendScale;
8230uniform int u_blendAnimRotation;
8231uniform int u_blendAnimIntensity;
8232uniform int u_blendAnimOffset;
8233uniform float u_blendAnimSpeed;
8234uniform float u_blendNoiseMask;
8235uniform float u_blendNoiseScale;
8236uniform float u_blendFalloff;
8237uniform int u_blendDualLayer;
8238uniform int u_blendMode2;
8239uniform float u_blendIntensity2;
8240uniform float u_blendOffset2;
8241uniform float u_blendRotation2;
8242uniform float u_blendScale2;
8243
8244__COMMON__
8245
8246float hash11(float p) {
8247  p = fract(p * 0.1031);
8248  p *= p + 33.33;
8249  p *= p + p;
8250  return fract(p);
8251}
8252
8253// Rain streak: elongated soft capsule along fall direction
8254float rainStreak(vec2 uv, vec2 pos, float streakLen, float thickness, float windAngle) {
8255  vec2 delta = uv - pos;
8256  // Rotate into streak-local space (aligned with fall direction + wind)
8257  float c = cos(windAngle);
8258  float s = sin(windAngle);
8259  vec2 local = vec2(delta.x * c + delta.y * s, -delta.x * s + delta.y * c);
8260
8261  // Capsule SDF: clamp along streak length
8262  float halfLen = streakLen * 0.5;
8263  local.y = clamp(local.y, -halfLen, halfLen);
8264  vec2 nearest = vec2(0.0, local.y);
8265  float d = distance(vec2(delta.x * c + delta.y * s, -delta.x * s + delta.y * c), nearest);
8266
8267  // Motion blur: fade along streak length (brighter at head)
8268  float along = (-delta.x * s + delta.y * c);
8269  float headFade = smoothstep(-halfLen, halfLen * 0.3, along);
8270
8271  return exp(-d * d / (thickness * thickness)) * headFade;
8272}
8273
8274void main() {
8275  vec2 uv = vUv;
8276  float aspect = u_resolution.x / max(u_resolution.y, 1.0);
8277  uv = applyMouseWarp(uv, aspect);
8278  float t = u_time * u_speed;
8279
8280  vec2 aspectUv = vec2(uv.x * aspect, uv.y);
8281
8282  // Background: dark with gradient tint
8283  float dirAngle = radians(u_direction);
8284  vec2 dir = vec2(cos(dirAngle), sin(dirAngle));
8285  float baseT = dot(uv - 0.5, dir) + 0.5;
8286
8287  float imgLum = imageLuminance(uv, aspect);
8288  if (imgLum >= 0.0) baseT = mix(baseT, imgLum, u_imageIntensity);
8289
8290  vec3 bgColor = mixColors(u_colors, u_colorPositions, u_colorCount, baseT);
8291  bgColor *= 0.1;
8292
8293  // Slight wind angle (radians from vertical)
8294  float windAngle = 0.12 + sin(t * 0.2) * 0.04;
8295
8296  vec3 rainAccum = vec3(0.0);
8297
8298  // Multiple rain columns with different speeds
8299  const int COLUMNS = 30;
8300  const int DROPS_PER_COL = 6;
8301
8302  for (int col = 0; col < COLUMNS; col++) {
8303    float fc = float(col);
8304    float colSeed = fc * 7.31;
8305
8306    // Column x position
8307    float colX = hash11(colSeed) * aspect;
8308    // Slight x jitter per column
8309    colX += sin(t * 0.3 + fc * 2.0) * 0.005;
8310
8311    // Depth layer (affects size, speed, brightness)
8312    float depth = hash11(colSeed + 1.0);
8313    float fallSpeed = mix(0.3, 0.8, depth);
8314    float streakLen = mix(0.03, 0.10, depth);
8315    float thickness = mix(0.001, 0.004, depth);
8316    float brightness = mix(0.3, 0.8, depth);
8317
8318    for (int drop = 0; drop < DROPS_PER_COL; drop++) {
8319      float fd = float(drop);
8320      float dropSeed = colSeed + fd * 17.0;
8321
8322      // Stagger drops in time
8323      float phase = hash11(dropSeed) * 6.0;
8324      float y = fract(phase - t * fallSpeed);
8325      // Slight x variation per drop
8326      float dropX = colX + hash11(dropSeed + 5.0) * 0.01;
8327
8328      vec2 dropPos = vec2(dropX, y);
8329      float streak = rainStreak(aspectUv, dropPos, streakLen, thickness, windAngle);
8330
8331      // Color from palette
8332      float colorT = hash11(dropSeed + 3.0);
8333      vec3 dropColor = mixColors(u_colors, u_colorPositions, u_colorCount, colorT);
8334      dropColor = max(dropColor, vec3(0.15));
8335      // Push bright drops toward white for glassy look
8336      dropColor = mix(dropColor, vec3(0.8, 0.85, 1.0), 0.3);
8337
8338      rainAccum += dropColor * streak * brightness;
8339    }
8340  }
8341
8342  // Edge fade at top and bottom
8343  float edgeFade = smoothstep(0.0, 0.1, uv.y) * smoothstep(1.0, 0.85, uv.y);
8344
8345  vec3 color = bgColor;
8346  color += rainAccum * edgeFade * 2.0;
8347
8348  // Subtle ambient mist from palette
8349  float mist = snoise(vec2(uv.x * 3.0, uv.y * 1.5 - t * 0.05)) * 0.5 + 0.5;
8350  vec3 mistColor = mixColors(u_colors, u_colorPositions, u_colorCount, baseT);
8351  color += mistColor * mist * 0.03;
8352
8353  color += grain(uv, t, u_grain);
8354
8355  // Self-blend
8356  if (u_blendMode > 0) {
8357    float bRotation = u_blendAnimRotation > 0 ? animatedBlendRotation(u_blendRotation, t, u_blendAnimSpeed) : u_blendRotation;
8358    float bOffset = u_blendAnimOffset > 0 ? animatedBlendOffset(u_blendOffset, t, u_blendAnimSpeed) : u_blendOffset;
8359    float bInt = u_blendAnimIntensity > 0 ? animatedBlendIntensity(u_blendIntensity, t, u_blendAnimSpeed) : u_blendIntensity;
8360    bInt = noiseModulatedIntensity(bInt, uv, t, u_blendNoiseMask, u_blendNoiseScale);
8361    bInt = applyBlendFalloff(bInt, baseT, u_blendFalloff);
8362    float tB = blendLayerT(uv, baseT, u_resolution, u_direction, bOffset, bRotation, u_blendScale);
8363    vec3 blendCol = mixColors(u_colors, u_colorPositions, u_colorCount, tB);
8364    color = applyBlendMode(u_blendMode, color, blendCol, bInt);
8365    if (u_blendDualLayer > 0 && u_blendMode2 > 0) {
8366      float tB2 = blendLayerT(uv, baseT, u_resolution, u_direction, u_blendOffset2, u_blendRotation2, u_blendScale2);
8367      vec3 blendCol2 = mixColors(u_colors, u_colorPositions, u_colorCount, tB2);
8368      color = applyBlendMode(u_blendMode2, color, blendCol2, u_blendIntensity2);
8369    }
8370  }
8371
8372  fragColor = outputColor(color, gl_FragCoord.xy, 1.0);
8373}
8374`,Y=`#version 300 es
8375precision highp float;
8376
8377in vec2 vUv;
8378out vec4 fragColor;
8379
8380uniform float u_time;
8381uniform float u_speed;
8382uniform vec3 u_colors[12];
8383uniform float u_colorPositions[12];
8384uniform int u_colorCount;
8385uniform float u_direction;
8386uniform float u_grain;
8387uniform vec2 u_resolution;
8388
8389// Blend uniforms
8390uniform int u_blendMode;
8391uniform float u_blendIntensity;
8392uniform float u_blendOffset;
8393uniform float u_blendRotation;
8394uniform float u_blendScale;
8395uniform int u_blendAnimRotation;
8396uniform int u_blendAnimIntensity;
8397uniform int u_blendAnimOffset;
8398uniform float u_blendAnimSpeed;
8399uniform float u_blendNoiseMask;
8400uniform float u_blendNoiseScale;
8401uniform float u_blendFalloff;
8402uniform int u_blendDualLayer;
8403uniform int u_blendMode2;
8404uniform float u_blendIntensity2;
8405uniform float u_blendOffset2;
8406uniform float u_blendRotation2;
8407uniform float u_blendScale2;
8408
8409__COMMON__
8410
8411float hash11(float p) {
8412  p = fract(p * 0.1031);
8413  p *= p + 33.33;
8414  p *= p + p;
8415  return fract(p);
8416}
8417
8418vec2 hash21(float p) {
8419  vec3 p3 = fract(vec3(p) * vec3(0.1031, 0.1030, 0.0973));
8420  p3 += dot(p3, p3.yzx + 33.33);
8421  return fract((p3.xx + p3.yz) * p3.zy);
8422}
8423
8424// Rotate a 2D point
8425vec2 rot2d(vec2 p, float a) {
8426  float c = cos(a);
8427  float s = sin(a);
8428  return vec2(p.x * c - p.y * s, p.x * s + p.y * c);
8429}
8430
8431// SDF for a rectangle
8432float sdBox(vec2 p, vec2 halfSize) {
8433  vec2 d = abs(p) - halfSize;
8434  return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);
8435}
8436
8437// SDF for a diamond (rotated square)
8438float sdDiamond(vec2 p, float size) {
8439  p = abs(p);
8440  return (p.x + p.y - size) * 0.707;
8441}
8442
8443void main() {
8444  vec2 uv = vUv;
8445  float aspect = u_resolution.x / max(u_resolution.y, 1.0);
8446  uv = applyMouseWarp(uv, aspect);
8447  float t = u_time * u_speed;
8448
8449  vec2 aspectUv = vec2(uv.x * aspect, uv.y);
8450
8451  // Light-ish background from gradient
8452  float dirAngle = radians(u_direction);
8453  vec2 dir = vec2(cos(dirAngle), sin(dirAngle));
8454  float baseT = dot(uv - 0.5, dir) + 0.5;
8455
8456  float imgLum = imageLuminance(uv, aspect);
8457  if (imgLum >= 0.0) baseT = mix(baseT, imgLum, u_imageIntensity);
8458
8459  vec3 bgColor = mixColors(u_colors, u_colorPositions, u_colorCount, baseT);
8460  bgColor *= 0.12;
8461
8462  vec3 confettiAccum = vec3(0.0);
8463
8464  const int PIECE_COUNT = 120;
8465
8466  for (int i = 0; i < PIECE_COUNT; i++) {
8467    float fi = float(i);
8468    float seed = fi * 11.37;
8469
8470    // Random initial position
8471    vec2 initPos = hash21(seed);
8472
8473    // Gravity fall: different speeds per piece
8474    float fallSpeed = mix(0.15, 0.45, hash11(seed + 1.0));
8475    float sway = sin(t * mix(1.0, 3.0, hash11(seed + 2.0)) + fi * 3.0) * 0.04;
8476
8477    // Current position (wrapping vertically)
8478    vec2 pos;
8479    pos.x = initPos.x * aspect + sway;
8480    pos.y = fract(initPos.y + 0.5 - t * fallSpeed);
8481
8482    vec2 delta = aspectUv - pos;
8483
8484    // Tumbling rotation
8485    float tumbleSpeed = mix(2.0, 6.0, hash11(seed + 3.0));
8486    float tumblePhase = hash11(seed + 4.0) * 6.283;
8487    float angle = t * tumbleSpeed + tumblePhase;
8488
8489    vec2 rotDelta = rot2d(delta, angle);
8490
8491    // Shape type: 0=square, 1=diamond, 2=rectangle
8492    float shapeType = hash11(seed + 5.0);
8493    float size = mix(0.005, 0.015, hash11(seed + 6.0));
8494
8495    float d;
8496    if (shapeType < 0.33) {
8497      // Square
8498      d = sdBox(rotDelta, vec2(size));
8499    } else if (shapeType < 0.66) {
8500      // Diamond
8501      d = sdDiamond(rotDelta, size * 1.4);
8502    } else {
8503      // Rectangle (elongated)
8504      d = sdBox(rotDelta, vec2(size * 0.5, size * 1.5));
8505    }
8506
8507    // Soft edge
8508    float shape = 1.0 - smoothstep(-0.001, 0.002, d);
8509
8510    // Simulate 3D tumble: modulate brightness with sin of rotation
8511    // (as confetti rotates, it shows face vs edge)
8512    float faceBright = abs(sin(angle * 0.5)) * 0.6 + 0.4;
8513    shape *= faceBright;
8514
8515    // Color from palette
8516    float colorT = hash11(seed + 7.0);
8517    vec3 pieceColor = mixColors(u_colors, u_colorPositions, u_colorCount, colorT);
8518    pieceColor = max(pieceColor, vec3(0.15));
8519    // Boost saturation for celebratory feel
8520    float luma = dot(pieceColor, vec3(0.299, 0.587, 0.114));
8521    pieceColor = mix(vec3(luma), pieceColor, 1.4);
8522
8523    confettiAccum += pieceColor * shape;
8524  }
8525
8526  vec3 color = bgColor + confettiAccum * 1.5;
8527
8528  color += grain(uv, t, u_grain);
8529
8530  // Self-blend
8531  if (u_blendMode > 0) {
8532    float bRotation = u_blendAnimRotation > 0 ? animatedBlendRotation(u_blendRotation, t, u_blendAnimSpeed) : u_blendRotation;
8533    float bOffset = u_blendAnimOffset > 0 ? animatedBlendOffset(u_blendOffset, t, u_blendAnimSpeed) : u_blendOffset;
8534    float bInt = u_blendAnimIntensity > 0 ? animatedBlendIntensity(u_blendIntensity, t, u_blendAnimSpeed) : u_blendIntensity;
8535    bInt = noiseModulatedIntensity(bInt, uv, t, u_blendNoiseMask, u_blendNoiseScale);
8536    bInt = applyBlendFalloff(bInt, baseT, u_blendFalloff);
8537    float tB = blendLayerT(uv, baseT, u_resolution, u_direction, bOffset, bRotation, u_blendScale);
8538    vec3 blendCol = mixColors(u_colors, u_colorPositions, u_colorCount, tB);
8539    color = applyBlendMode(u_blendMode, color, blendCol, bInt);
8540    if (u_blendDualLayer > 0 && u_blendMode2 > 0) {
8541      float tB2 = blendLayerT(uv, baseT, u_resolution, u_direction, u_blendOffset2, u_blendRotation2, u_blendScale2);
8542      vec3 blendCol2 = mixColors(u_colors, u_colorPositions, u_colorCount, tB2);
8543      color = applyBlendMode(u_blendMode2, color, blendCol2, u_blendIntensity2);
8544    }
8545  }
8546
8547  fragColor = outputColor(color, gl_FragCoord.xy, 1.0);
8548}
8549`,j=`#version 300 es
8550precision highp float;
8551
8552in vec2 vUv;
8553out vec4 fragColor;
8554
8555uniform float u_time;
8556uniform float u_speed;
8557uniform vec3 u_colors[12];
8558uniform float u_colorPositions[12];
8559uniform int u_colorCount;
8560uniform float u_direction;
8561uniform float u_grain;
8562uniform vec2 u_resolution;
8563
8564// Blend uniforms
8565uniform int u_blendMode;
8566uniform float u_blendIntensity;
8567uniform float u_blendOffset;
8568uniform float u_blendRotation;
8569uniform float u_blendScale;
8570uniform int u_blendAnimRotation;
8571uniform int u_blendAnimIntensity;
8572uniform int u_blendAnimOffset;
8573uniform float u_blendAnimSpeed;
8574uniform float u_blendNoiseMask;
8575uniform float u_blendNoiseScale;
8576uniform float u_blendFalloff;
8577uniform int u_blendDualLayer;
8578uniform int u_blendMode2;
8579uniform float u_blendIntensity2;
8580uniform float u_blendOffset2;
8581uniform float u_blendRotation2;
8582uniform float u_blendScale2;
8583
8584__COMMON__
8585
8586float hash11(float p) {
8587  p = fract(p * 0.1031);
8588  p *= p + 33.33;
8589  p *= p + p;
8590  return fract(p);
8591}
8592
8593vec2 hash22(vec2 p) {
8594  vec3 p3 = fract(vec3(p.xyx) * vec3(0.1031, 0.1030, 0.0973));
8595  p3 += dot(p3, p3.yzx + 33.33);
8596  return fract((p3.xx + p3.yz) * p3.zy);
8597}
8598
8599void main() {
8600  vec2 uv = vUv;
8601  float aspect = u_resolution.x / max(u_resolution.y, 1.0);
8602  uv = applyMouseWarp(uv, aspect);
8603  float t = u_time * u_speed;
8604
8605  // Center the coordinate system
8606  vec2 centered = uv - 0.5;
8607  centered.x *= aspect;
8608
8609  float r = length(centered);
8610  float theta = atan(centered.y, centered.x);
8611
8612  // Direction-based base T for blending
8613  float dirAngle = radians(u_direction);
8614  vec2 dir = vec2(cos(dirAngle), sin(dirAngle));
8615  float baseT = dot(uv - 0.5, dir) + 0.5;
8616
8617  float imgLum = imageLuminance(uv, aspect);
8618  if (imgLum >= 0.0) baseT = mix(baseT, imgLum, u_imageIntensity);
8619
8620  // Very dark cosmic background
8621  vec3 bgColor = mixColors(u_colors, u_colorPositions, u_colorCount, baseT);
8622  bgColor *= 0.03;
8623
8624  // ---- Bright galaxy core ----
8625  float coreGlow = exp(-r * r * 50.0) * 1.5;
8626  float coreHalo = exp(-r * r * 8.0) * 0.3;
8627  vec3 coreColor = mixColors(u_colors, u_colorPositions, u_colorCount, 0.5);
8628  coreColor = max(coreColor, vec3(0.4));
8629  // Push core toward warm white
8630  coreColor = mix(coreColor, vec3(1.0, 0.95, 0.85), 0.5);
8631
8632  // ---- Spiral arms ----
8633  // Differential rotation: inner regions rotate faster
8634  float rotSpeed = t * 0.2;
8635  float diffTheta = theta + rotSpeed / (r + 0.15);
8636
8637  // Logarithmic spiral: angle = a * ln(r) + offset
8638  float spiralTightness = 3.0;
8639
8640  float armAccum = 0.0;
8641  vec3 armColorAccum = vec3(0.0);
8642
8643  // 3 spiral arms
8644  for (int arm = 0; arm < 3; arm++) {
8645    float armOffset = float(arm) * 6.283 / 3.0;
8646
8647    // How close this pixel is to the spiral arm
8648    float spiralAngle = spiralTightness * log(max(r, 0.01)) + armOffset;
8649    float angleDiff = diffTheta - spiralAngle;
8650    // Wrap to [-PI, PI]
8651    angleDiff = mod(angleDiff + 3.14159, 6.28318) - 3.14159;
8652
8653    // Arm width (wider at larger radius)
8654    float armWidth = mix(0.3, 0.8, clamp(r * 2.0, 0.0, 1.0));
8655    float armBright = exp(-angleDiff * angleDiff / (armWidth * armWidth));
8656
8657    // Fade arm at very large and very small radii
8658    armBright *= smoothstep(0.02, 0.08, r) * smoothstep(0.55, 0.3, r);
8659
8660    armAccum += armBright;
8661
8662    // Color varies along arm and between arms
8663    float armT = fract(r * 2.0 + float(arm) * 0.33);
8664    vec3 armCol = mixColors(u_colors, u_colorPositions, u_colorCount, armT);
8665    armColorAccum += armCol * armBright;
8666  }
8667
8668  armAccum = min(armAccum, 2.0);
8669  armColorAccum = min(armColorAccum, vec3(2.0));
8670
8671  // ---- Star particles along arms ----
8672  // Grid-based hash for star placement
8673  vec3 starAccum = vec3(0.0);
8674
8675  const float STAR_GRID = 24.0;
8676  for (float sy = 0.0; sy < STAR_GRID; sy++) {
8677    for (float sx = 0.0; sx < STAR_GRID; sx++) {
8678      vec2 cell = vec2(sx, sy);
8679      vec2 cellHash = hash22(cell + 7.0);
8680
8681      // Star position in UV space
8682      vec2 starUv = (cell + cellHash) / STAR_GRID;
8683      vec2 starCentered = starUv - 0.5;
8684      starCentered.x *= aspect;
8685
8686      float starR = length(starCentered);
8687      float starTheta = atan(starCentered.y, starCentered.x);
8688
8689      // Check if star is near a spiral arm
8690      float starDiffTheta = starTheta + rotSpeed / (starR + 0.15);
8691      float nearArm = 0.0;
8692      for (int arm = 0; arm < 3; arm++) {
8693        float armOff = float(arm) * 6.283 / 3.0;
8694        float spiralA = spiralTightness * log(max(starR, 0.01)) + armOff;
8695        float aDiff = mod(starDiffTheta - spiralA + 3.14159, 6.28318) - 3.14159;
8696        float armW = mix(0.4, 1.0, clamp(starR * 2.0, 0.0, 1.0));
8697        nearArm += exp(-aDiff * aDiff / (armW * armW));
8698      }
8699
8700      // Star probability increases near arms
8701      float prob = mix(0.05, 0.7, min(nearArm, 1.0));
8702      float seed = cell.x * STAR_GRID + cell.y;
8703      if (hash11(seed * 3.17) > prob) continue;
8704
8705      // Star distance from current pixel
8706      vec2 sAspect = vec2(starUv.x * aspect, starUv.y);
8707      vec2 aspectUvLocal = vec2(uv.x * aspect, uv.y);
8708      float d = distance(aspectUvLocal, sAspect);
8709
8710      // Star size
8711      float starSize = mix(0.001, 0.006, hash11(seed * 2.73));
8712
8713      // Star glow
8714      float glow = exp(-d * d / (starSize * starSize));
8715
8716      // Twinkle
8717      float twinkle = sin(t * mix(1.0, 3.5, hash11(seed * 4.1)) + seed * 3.0) * 0.5 + 0.5;
8718      glow *= 0.3 + 0.7 * twinkle;
8719
8720      // Star color
8721      float colorT = hash11(seed * 1.93);
8722      vec3 starCol = mixColors(u_colors, u_colorPositions, u_colorCount, colorT);
8723      starCol = max(starCol, vec3(0.3));
8724      // Push toward blue-white for cosmic feel
8725      starCol = mix(starCol, vec3(0.8, 0.85, 1.0), 0.3);
8726
8727      starAccum += starCol * glow;
8728    }
8729  }
8730
8731  // ---- Compose ----
8732  vec3 color = bgColor;
8733
8734  // Spiral arm nebula glow
8735  color += armColorAccum * 0.5;
8736
8737  // Core
8738  color += coreColor * (coreGlow + coreHalo);
8739
8740  // Stars
8741  color += starAccum * 1.5;
8742
8743  color += grain(uv, t, u_grain);
8744
8745  // Self-blend
8746  if (u_blendMode > 0) {
8747    float bRotation = u_blendAnimRotation > 0 ? animatedBlendRotation(u_blendRotation, t, u_blendAnimSpeed) : u_blendRotation;
8748    float bOffset = u_blendAnimOffset > 0 ? animatedBlendOffset(u_blendOffset, t, u_blendAnimSpeed) : u_blendOffset;
8749    float bInt = u_blendAnimIntensity > 0 ? animatedBlendIntensity(u_blendIntensity, t, u_blendAnimSpeed) : u_blendIntensity;
8750    bInt = noiseModulatedIntensity(bInt, uv, t, u_blendNoiseMask, u_blendNoiseScale);
8751    bInt = applyBlendFalloff(bInt, baseT, u_blendFalloff);
8752    float tB = blendLayerT(uv, baseT, u_resolution, u_direction, bOffset, bRotation, u_blendScale);
8753    vec3 blendCol = mixColors(u_colors, u_colorPositions, u_colorCount, tB);
8754    color = applyBlendMode(u_blendMode, color, blendCol, bInt);
8755    if (u_blendDualLayer > 0 && u_blendMode2 > 0) {
8756      float tB2 = blendLayerT(uv, baseT, u_resolution, u_direction, u_blendOffset2, u_blendRotation2, u_blendScale2);
8757      vec3 blendCol2 = mixColors(u_colors, u_colorPositions, u_colorCount, tB2);
8758      color = applyBlendMode(u_blendMode2, color, blendCol2, u_blendIntensity2);
8759    }
8760  }
8761
8762  fragColor = outputColor(color, gl_FragCoord.xy, 1.0);
8763}
8764`,K={simple:o,noise:t,aurora:i.AURORA_FRAGMENT,radial:n,dither:l,halftone:a,ascii:r,mesh:s,cosmic:u,heightfield3d:f,isometric:c,terrainHud:d,cinematicBeam:m,fractalGlass:p,surfaces3d:v,metallic3d:b,audioReactive:g,liquidChrome:h,particleField:y,fluidSim:D,eventHorizon:k,blackHole:O.BLACK_HOLE_FRAGMENT,liquidGradient:T,prismaticGlass:P,cloud:F,swirlVortex:L,heatmap:N,liquidMetal:"#ver
8764sion 300 es\nprecision highp float;\nout vec4 fragColor;\nvoid main() { fragColor = vec4(0.0, 0.0, 0.0, 1.0); }",neonMesh:z,cosinePalette:U,additivePlasma:q,subsurfaceGlow:G,asciiArt:H,particleFlow:E,particleBokeh:W,particleSparkle:V,particleRain:X,particleConfetti:Y,particleGalaxy:j,meshGradient:"void main() { gl_FragColor = vec4(1.0, 0.0, 1.0, 1.0); }"};e.s(["FRAGMENT_SHADERS",0,K],204776)}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.