Account Settings
FIRST NAME
[Loading...]
LAST NAME
[Loading...]
EMAIL
[Loading...]
ID
[Loading...] For support purposes only
Share AgencyGenius

Step 1: Add HTML, Css & JavaScript

Code
Html
Css
Javascript
Combined
 <div id="webgl-fluid-background"> 
 <!-- 
 * WebGL Fluid Background Effect - HTML Structure 
 * This is the HTML structure for the fluid particle animation 
 * 
 * BLEND MODE: active 
 * Change to "inactive" in the JavaScript to disable the screen blend mode 
 --> 
  
 <!-- WEBGL Fluid particles canvas --> 
 <canvas id="fluid"></canvas> 
 </div> 
 <!-- End of HTML Structure for WebGL Fluid Background Effect --> 
 <div id="webgl-fluid-css"> 
 <!-- 
 * WebGL Fluid Background Effect - CSS Styles 
 * These styles position the fluid effect over the entire page 
 --> 
 <style> 
 /* Container styles to ensure proper positioning */ 
 #webgl-fluid-background { 
 position: fixed; 
 top: 0; 
 left: 0; 
 width: 100%; 
 height: 100%; 
 z-index: 99999; /* Using high z-index to ensure it's above everything */ 
 pointer-events: none; /* Allows clicks to pass through to elements below */ 
 } 
  
 /* Canvas styles for the fluid animation */ 
 #webgl-fluid-background #fluid { 
 position: fixed; 
 top: 0; 
 left: 0; 
 width: 100%; 
 height: 100%; 
 } 
 </style> 
 </div> 
 <!-- End of CSS for WebGL Fluid Background Effect --> 
 <div id="webgl-fluid-javascript"> 
 <!-- 
 * WebGL Fluid Background Effect - JavaScript 
 * This script creates the fluid particle animation with WebGL 
 --> 
 <script> 
 // Self-executing function to avoid global scope pollution 
 (function() { 
 // Initialize fluid animation on page load 
 window.addEventListener('load', () => { 
 initFluid(); 
  
 // Check for blend mode setting 
 const blendModeActive = true; // Change to false to disable blend mode 
  
 // Apply blend mode if active 
 if (blendModeActive) { 
 document.getElementById('fluid').style.mixBlendMode = 'screen'; 
 } 
 }); 
  
 const initFluid = () => { 
 // Get the canvas element 
 const canvas = document.getElementById('fluid'); 
  
 // Resize canvas to match window size 
 function resizeCanvas() { 
 canvas.width = window.innerWidth; 
 canvas.height = window.innerHeight; 
 } 
 resizeCanvas(); 
  
 // Configuration for the fluid simulation 
 let config = { 
 SIM_RESOLUTION: 128, 
 DYE_RESOLUTION: 1440, 
 CAPTURE_RESOLUTION: 512, 
 DENSITY_DISSIPATION: 3.5, 
 VELOCITY_DISSIPATION: 2, 
 PRESSURE: 0.1, 
 PRESSURE_ITERATIONS: 20, 
 CURL: 3, 
 SPLAT_RADIUS: 0.2, 
 SPLAT_FORCE: 6000, 
 SHADING: true, 
 COLOR_UPDATE_SPEED: 10, 
 PAUSED: false, 
 BACK_COLOR: { r: 0, g: 0, b: 0 }, 
 TRANSPARENT: true, 
 } 
  
 // Pointer prototype for tracking mouse/touch interactions 
 function pointerPrototype() { 
 this.id = -1; 
 this.texcoordX = 0; 
 this.texcoordY = 0; 
 this.prevTexcoordX = 0; 
 this.prevTexcoordY = 0; 
 this.deltaX = 0; 
 this.deltaY = 0; 
 this.down = false; 
 this.moved = false; 
 this.color = [30, 0, 300]; 
 } 
  
 let pointers = []; 
 pointers.push(new pointerPrototype()); 
  
 // Initialize WebGL context 
 const { gl, ext } = getWebGLContext(canvas); 
  
 if (!ext.supportLinearFiltering) { 
 config.DYE_RESOLUTION = 512; 
 config.SHADING = false; 
 } 
  
 function getWebGLContext(canvas) { 
 const params = { alpha: true, depth: false, stencil: false, antialias: false, preserveDrawingBuffer: false }; 
 let gl = canvas.getContext('webgl2', params); 
 const isWebGL2 = !!gl; 
 if (!isWebGL2) 
 gl = canvas.getContext('webgl', params) || canvas.getContext('experimental-webgl', params); 
  
 let halfFloat; 
 let supportLinearFiltering; 
  
 if (isWebGL2) { 
 gl.getExtension('EXT_color_buffer_float'); 
 supportLinearFiltering = gl.getExtension('OES_texture_float_linear'); 
 } else { 
 halfFloat = gl.getExtension('OES_texture_half_float'); 
 supportLinearFiltering = gl.getExtension('OES_texture_half_float_linear'); 
 } 
  
 gl.clearColor(0.0, 0.0, 0.0, 1.0); 
 const halfFloatTexType = isWebGL2 ? gl.HALF_FLOAT : halfFloat.HALF_FLOAT_OES; 
 let formatRGBA; 
 let formatRG; 
 let formatR; 
  
 if (isWebGL2) { 
 formatRGBA = getSupportedFormat(gl, gl.RGBA16F, gl.RGBA, halfFloatTexType); 
 formatRG = getSupportedFormat(gl, gl.RG16F, gl.RG, halfFloatTexType); 
 formatR = getSupportedFormat(gl, gl.R16F, gl.RED, halfFloatTexType); 
 } else { 
 formatRGBA = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloatTexType); 
 formatRG = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloatTexType); 
 formatR = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloatTexType); 
 } 
  
 return { 
 gl, 
 ext: { 
 formatRGBA, 
 formatRG, 
 formatR, 
 halfFloatTexType, 
 supportLinearFiltering 
 } 
 }; 
 } 
  
 function getSupportedFormat(gl, internalFormat, format, type) { 
 if (!supportRenderTextureFormat(gl, internalFormat, format, type)) { 
 switch (internalFormat) { 
 case gl.R16F: 
 return getSupportedFormat(gl, gl.RG16F, gl.RG, type); 
 case gl.RG16F: 
 return getSupportedFormat(gl, gl.RGBA16F, gl.RGBA, type); 
 default: 
 return null; 
 } 
 } 
  
 return { 
 internalFormat, 
 format 
 } 
 } 
  
 function supportRenderTextureFormat(gl, internalFormat, format, type) { 
 let texture = gl.createTexture(); 
 gl.bindTexture(gl.TEXTURE_2D, texture); 
 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); 
 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); 
 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 
 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 
 gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, 4, 4, 0, format, type, null); 
  
 let fbo = gl.createFramebuffer(); 
 gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); 
 gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); 
  
 let status = gl.checkFramebufferStatus(gl.FRAMEBUFFER); 
 return status == gl.FRAMEBUFFER_COMPLETE; 
 } 
  
 class Material { 
 constructor(vertexShader, fragmentShaderSource) { 
 this.vertexShader = vertexShader; 
 this.fragmentShaderSource = fragmentShaderSource; 
 this.programs = []; 
 this.activeProgram = null; 
 this.uniforms = []; 
 } 
  
 setKeywords(keywords) { 
 let hash = 0; 
 for (let i = 0; i < keywords.length; i++) 
 hash += hashCode(keywords[i]); 
  
 let program = this.programs[hash]; 
 if (program == null) { 
 let fragmentShader = compileShader(gl.FRAGMENT_SHADER, this.fragmentShaderSource, keywords); 
 program = createProgram(this.vertexShader, fragmentShader); 
 this.programs[hash] = program; 
 } 
  
 if (program == this.activeProgram) return; 
  
 this.uniforms = getUniforms(program); 
 this.activeProgram = program; 
 } 
  
 bind() { 
 gl.useProgram(this.activeProgram); 
 } 
 } 
  
 class Program { 
 constructor(vertexShader, fragmentShader) { 
 this.uniforms = {}; 
 this.program = createProgram(vertexShader, fragmentShader); 
 this.uniforms = getUniforms(this.program); 
 } 
  
 bind() { 
 gl.useProgram(this.program); 
 } 
 } 
  
 function createProgram(vertexShader, fragmentShader) { 
 let program = gl.createProgram(); 
 gl.attachShader(program, vertexShader); 
 gl.attachShader(program, fragmentShader); 
 gl.linkProgram(program); 
  
 if (!gl.getProgramParameter(program, gl.LINK_STATUS)) 
 console.trace(gl.getProgramInfoLog(program)); 
  
 return program; 
 } 
  
 function getUniforms(program) { 
 let uniforms = []; 
 let uniformCount = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); 
  
 for (let i = 0; i < uniformCount; i++) { 
 let uniformName = gl.getActiveUniform(program, i).name; 
 uniforms[uniformName] = gl.getUniformLocation(program, uniformName); 
 } 
  
 return uniforms; 
 } 
  
 function compileShader(type, source, keywords) { 
 source = addKeywords(source, keywords); 
 const shader = gl.createShader(type); 
 gl.shaderSource(shader, source); 
 gl.compileShader(shader); 
  
 if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) 
 console.trace(gl.getShaderInfoLog(shader)); 
  
 return shader; 
 } 
  
 function addKeywords(source, keywords) { 
 if (keywords == null) return source; 
 let keywordsString = ''; 
 keywords.forEach(keyword => { 
 keywordsString += '#define ' + keyword + '\n'; 
 }); 
 return keywordsString + source; 
 } 
  
 // Load shaders 
 const baseVertexShader = compileShader(gl.VERTEX_SHADER, ` 
 precision highp float; 
 attribute vec2 aPosition; 
 varying vec2 vUv; 
 varying vec2 vL; 
 varying vec2 vR; 
 varying vec2 vT; 
 varying vec2 vB; 
 uniform vec2 texelSize; 
 void main () { 
 vUv = aPosition * 0.5 + 0.5; 
 vL = vUv - vec2(texelSize.x, 0.0); 
 vR = vUv + vec2(texelSize.x, 0.0); 
 vT = vUv + vec2(0.0, texelSize.y); 
 vB = vUv - vec2(0.0, texelSize.y); 
 gl_Position = vec4(aPosition, 0.0, 1.0); 
 } 
 `); 
  
 const blurVertexShader = compileShader(gl.VERTEX_SHADER, ` 
 precision highp float; 
 attribute vec2 aPosition; 
 varying vec2 vUv; 
 varying vec2 vL; 
 varying vec2 vR; 
 uniform vec2 texelSize; 
 void main () { 
 vUv = aPosition * 0.5 + 0.5; 
 float offset = 1.33333333; 
 vL = vUv - texelSize * offset; 
 vR = vUv + texelSize * offset; 
 gl_Position = vec4(aPosition, 0.0, 1.0); 
 } 
 `); 
  
 const blurShader = compileShader(gl.FRAGMENT_SHADER, ` 
 precision mediump float; 
 precision mediump sampler2D; 
 varying vec2 vUv; 
 varying vec2 vL; 
 varying vec2 vR; 
 uniform sampler2D uTexture; 
 void main () { 
 vec4 sum = texture2D(uTexture, vUv) * 0.29411764; 
 sum += texture2D(uTexture, vL) * 0.35294117; 
 sum += texture2D(uTexture, vR) * 0.35294117; 
 gl_FragColor = sum; 
 } 
 `); 
  
 const copyShader = compileShader(gl.FRAGMENT_SHADER, ` 
 precision mediump float; 
 precision mediump sampler2D; 
 varying highp vec2 vUv; 
 uniform sampler2D uTexture; 
 void main () { 
 gl_FragColor = texture2D(uTexture, vUv); 
 } 
 `); 
  
 const clearShader = compileShader(gl.FRAGMENT_SHADER, ` 
 precision mediump float; 
 precision mediump sampler2D; 
 varying highp vec2 vUv; 
 uniform sampler2D uTexture; 
 uniform float value; 
 void main () { 
 gl_FragColor = value * texture2D(uTexture, vUv); 
 } 
 `); 
  
 const colorShader = compileShader(gl.FRAGMENT_SHADER, ` 
 precision mediump float; 
 uniform vec4 color; 
 void main () { 
 gl_FragColor = color; 
 } 
 `); 
  
 const displayShaderSource = ` 
 precision highp float; 
 precision highp sampler2D; 
 varying vec2 vUv; 
 varying vec2 vL; 
 varying vec2 vR; 
 varying vec2 vT; 
 varying vec2 vB; 
 uniform sampler2D uTexture; 
 uniform sampler2D uDithering; 
 uniform vec2 ditherScale; 
 uniform vec2 texelSize; 
 vec3 linearToGamma (vec3 color) { 
 color = max(color, vec3(0)); 
 return max(1.055 * pow(color, vec3(0.416666667)) - 0.055, vec3(0)); 
 } 
 void main () { 
 vec3 c = texture2D(uTexture, vUv).rgb; 
 #ifdef SHADING 
 vec3 lc = texture2D(uTexture, vL).rgb; 
 vec3 rc = texture2D(uTexture, vR).rgb; 
 vec3 tc = texture2D(uTexture, vT).rgb; 
 vec3 bc = texture2D(uTexture, vB).rgb; 
 float dx = length(rc) - length(lc); 
 float dy = length(tc) - length(bc); 
 vec3 n = normalize(vec3(dx, dy, length(texelSize))); 
 vec3 l = vec3(0.0, 0.0, 1.0); 
 float diffuse = clamp(dot(n, l) + 0.7, 0.7, 1.0); 
 c *= diffuse; 
 #endif 
 float a = max(c.r, max(c.g, c.b)); 
 gl_FragColor = vec4(c, a); 
 } 
 `; 
  
 const splatShader = compileShader(gl.FRAGMENT_SHADER, ` 
 precision highp float; 
 precision highp sampler2D; 
 varying vec2 vUv; 
 uniform sampler2D uTarget; 
 uniform float aspectRatio; 
 uniform vec3 color; 
 uniform vec2 point; 
 uniform float radius; 
 void main () { 
 vec2 p = vUv - point.xy; 
 p.x *= aspectRatio; 
 vec3 splat = exp(-dot(p, p) / radius) * color; 
 vec3 base = texture2D(uTarget, vUv).xyz; 
 gl_FragColor = vec4(base + splat, 1.0); 
 } 
 `); 
  
 const advectionShader = compileShader(gl.FRAGMENT_SHADER, ` 
 precision highp float; 
 precision highp sampler2D; 
 varying vec2 vUv; 
 uniform sampler2D uVelocity; 
 uniform sampler2D uSource; 
 uniform vec2 texelSize; 
 uniform vec2 dyeTexelSize; 
 uniform float dt; 
 uniform float dissipation; 
 vec4 bilerp (sampler2D sam, vec2 uv, vec2 tsize) { 
 vec2 st = uv / tsize - 0.5; 
 vec2 iuv = floor(st); 
 vec2 fuv = fract(st); 
 vec4 a = texture2D(sam, (iuv + vec2(0.5, 0.5)) * tsize); 
 vec4 b = texture2D(sam, (iuv + vec2(1.5, 0.5)) * tsize); 
 vec4 c = texture2D(sam, (iuv + vec2(0.5, 1.5)) * tsize); 
 vec4 d = texture2D(sam, (iuv + vec2(1.5, 1.5)) * tsize); 
 return mix(mix(a, b, fuv.x), mix(c, d, fuv.x), fuv.y); 
 } 
 void main () { 
 #ifdef MANUAL_FILTERING 
 vec2 coord = vUv - dt * bilerp(uVelocity, vUv, texelSize).xy * texelSize; 
 vec4 result = bilerp(uSource, coord, dyeTexelSize); 
 #else 
 vec2 coord = vUv - dt * texture2D(uVelocity, vUv).xy * texelSize; 
 vec4 result = texture2D(uSource, coord); 
 #endif 
 float decay = 1.0 + dissipation * dt; 
 gl_FragColor = result / decay; 
 }`, 
 ext.supportLinearFiltering ? null : ['MANUAL_FILTERING'] 
 ); 
  
 const divergenceShader = compileShader(gl.FRAGMENT_SHADER, ` 
 precision mediump float; 
 precision mediump sampler2D; 
 varying highp vec2 vUv; 
 varying highp vec2 vL; 
 varying highp vec2 vR; 
 varying highp vec2 vT; 
 varying highp vec2 vB; 
 uniform sampler2D uVelocity; 
 void main () { 
 float L = texture2D(uVelocity, vL).x; 
 float R = texture2D(uVelocity, vR).x; 
 float T = texture2D(uVelocity, vT).y; 
 float B = texture2D(uVelocity, vB).y; 
 vec2 C = texture2D(uVelocity, vUv).xy; 
 if (vL.x < 0.0) { L = -C.x; } 
 if (vR.x > 1.0) { R = -C.x; } 
 if (vT.y > 1.0) { T = -C.y; } 
 if (vB.y < 0.0) { B = -C.y; } 
 float div = 0.5 * (R - L + T - B); 
 gl_FragColor = vec4(div, 0.0, 0.0, 1.0); 
 } 
 `); 
  
 const curlShader = compileShader(gl.FRAGMENT_SHADER, ` 
 precision mediump float; 
 precision mediump sampler2D; 
 varying highp vec2 vUv; 
 varying highp vec2 vL; 
 varying highp vec2 vR; 
 varying highp vec2 vT; 
 varying highp vec2 vB; 
 uniform sampler2D uVelocity; 
 void main () { 
 float L = texture2D(uVelocity, vL).y; 
 float R = texture2D(uVelocity, vR).y; 
 float T = texture2D(uVelocity, vT).x; 
 float B = texture2D(uVelocity, vB).x; 
 float vorticity = R - L - T + B; 
 gl_FragColor = vec4(0.5 * vorticity, 0.0, 0.0, 1.0); 
 } 
 `); 
  
 const vorticityShader = compileShader(gl.FRAGMENT_SHADER, ` 
 precision highp float; 
 precision highp sampler2D; 
 varying vec2 vUv; 
 varying vec2 vL; 
 varying vec2 vR; 
 varying vec2 vT; 
 varying vec2 vB; 
 uniform sampler2D uVelocity; 
 uniform sampler2D uCurl; 
 uniform float curl; 
 uniform float dt; 
 void main () { 
 float L = texture2D(uCurl, vL).x; 
 float R = texture2D(uCurl, vR).x; 
 float T = texture2D(uCurl, vT).x; 
 float B = texture2D(uCurl, vB).x; 
 float C = texture2D(uCurl, vUv).x; 
 vec2 force = 0.5 * vec2(abs(T) - abs(B), abs(R) - abs(L)); 
 force /= length(force) + 0.0001; 
 force *= curl * C; 
 force.y *= -1.0; 
 vec2 velocity = texture2D(uVelocity, vUv).xy; 
 velocity += force * dt; 
 velocity = min(max(velocity, -1000.0), 1000.0); 
 gl_FragColor = vec4(velocity, 0.0, 1.0); 
 } 
 `); 
  
 const pressureShader = compileShader(gl.FRAGMENT_SHADER, ` 
 precision mediump float; 
 precision mediump sampler2D; 
 varying highp vec2 vUv; 
 varying highp vec2 vL; 
 varying highp vec2 vR; 
 varying highp vec2 vT; 
 varying highp vec2 vB; 
 uniform sampler2D uPressure; 
 uniform sampler2D uDivergence; 
 void main () { 
 float L = texture2D(uPressure, vL).x; 
 float R = texture2D(uPressure, vR).x; 
 float T = texture2D(uPressure, vT).x; 
 float B = texture2D(uPressure, vB).x; 
 float C = texture2D(uPressure, vUv).x; 
 float divergence = texture2D(uDivergence, vUv).x; 
 float pressure = (L + R + B + T - divergence) * 0.25; 
 gl_FragColor = vec4(pressure, 0.0, 0.0, 1.0); 
 } 
 `); 
  
 const gradientSubtractShader = compileShader(gl.FRAGMENT_SHADER, ` 
 precision mediump float; 
 precision mediump sampler2D; 
 varying highp vec2 vUv; 
 varying highp vec2 vL; 
 varying highp vec2 vR; 
 varying highp vec2 vT; 
 varying highp vec2 vB; 
 uniform sampler2D uPressure; 
 uniform sampler2D uVelocity; 
 void main () { 
 float L = texture2D(uPressure, vL).x; 
 float R = texture2D(uPressure, vR).x; 
 float T = texture2D(uPressure, vT).x; 
 float B = texture2D(uPressure, vB).x; 
 vec2 velocity = texture2D(uVelocity, vUv).xy; 
 velocity.xy -= vec2(R - L, T - B); 
 gl_FragColor = vec4(velocity, 0.0, 1.0); 
 } 
 `); 
  
 const blit = (() => { 
 gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer()); 
 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, -1, 1, 1, 1, 1, -1]), gl.STATIC_DRAW); 
 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, gl.createBuffer()); 
 gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 0, 2, 3]), gl.STATIC_DRAW); 
 gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0); 
 gl.enableVertexAttribArray(0); 
  
 return (target, clear = false) => { 
 if (target == null) { 
 gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); 
 gl.bindFramebuffer(gl.FRAMEBUFFER, null); 
 } else { 
 gl.viewport(0, 0, target.width, target.height); 
 gl.bindFramebuffer(gl.FRAMEBUFFER, target.fbo); 
 } 
  
 if (clear) { 
 gl.clearColor(0.0, 0.0, 0.0, 1.0); 
 gl.clear(gl.COLOR_BUFFER_BIT); 
 } 
  
 gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0); 
 } 
 })(); 
  
 function CHECK_FRAMEBUFFER_STATUS() { 
 let status = gl.checkFramebufferStatus(gl.FRAMEBUFFER); 
 if (status != gl.FRAMEBUFFER_COMPLETE) 
 console.trace("Framebuffer error: " + status); 
 } 
  
 // Initialize variable and programs 
 let dye; 
 let velocity; 
 let divergence; 
 let curl; 
 let pressure; 
  
 const blurProgram = new Program(blurVertexShader, blurShader); 
 const copyProgram = new Program(baseVertexShader, copyShader); 
 const clearProgram = new Program(baseVertexShader, clearShader); 
 const colorProgram = new Program(baseVertexShader, colorShader); 
 const splatProgram = new Program(baseVertexShader, splatShader); 
 const advectionProgram = new Program(baseVertexShader, advectionShader); 
 const divergenceProgram = new Program(baseVertexShader, divergenceShader); 
 const curlProgram = new Program(baseVertexShader, curlShader); 
 const vorticityProgram = new Program(baseVertexShader, vorticityShader); 
 const pressureProgram = new Program(baseVertexShader, pressureShader); 
 const gradienSubtractProgram = new Program(baseVertexShader, gradientSubtractShader); 
  
 const displayMaterial = new Material(baseVertexShader, displayShaderSource); 
  
 function initFramebuffers() { 
 let simRes = getResolution(config.SIM_RESOLUTION); 
 let dyeRes = getResolution(config.DYE_RESOLUTION); 
  
 const texType = ext.halfFloatTexType; 
 const rgba = ext.formatRGBA; 
 const rg = ext.formatRG; 
 const r = ext.formatR; 
 const filtering = ext.supportLinearFiltering ? gl.LINEAR : gl.NEAREST; 
  
 gl.disable(gl.BLEND); 
  
 if (dye == null) 
 dye = createDoubleFBO(dyeRes.width, dyeRes.height, rgba.internalFormat, rgba.format, texType, filtering); 
 else 
 dye = resizeDoubleFBO(dye, dyeRes.width, dyeRes.height, rgba.internalFormat, rgba.format, texType, filtering); 
  
 if (velocity == null) 
 velocity = createDoubleFBO(simRes.width, simRes.height, rg.internalFormat, rg.format, texType, filtering); 
 else 
 velocity = resizeDoubleFBO(velocity, simRes.width, simRes.height, rg.internalFormat, rg.format, texType, filtering); 
  
 divergence = createFBO(simRes.width, simRes.height, r.internalFormat, r.format, texType, gl.NEAREST); 
 curl = createFBO(simRes.width, simRes.height, r.internalFormat, r.format, texType, gl.NEAREST); 
 pressure = createDoubleFBO(simRes.width, simRes.height, r.internalFormat, r.format, texType, gl.NEAREST); 
 } 
  
 function createFBO(w, h, internalFormat, format, type, param) { 
 gl.activeTexture(gl.TEXTURE0); 
 let texture = gl.createTexture(); 
 gl.bindTexture(gl.TEXTURE_2D, texture); 
 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, param); 
 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, param); 
 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 
 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 
 gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, w, h, 0, format, type, null); 
  
 let fbo = gl.createFramebuffer(); 
 gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); 
 gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); 
 gl.viewport(0, 0, w, h); 
 gl.clear(gl.COLOR_BUFFER_BIT); 
  
 let texelSizeX = 1.0 / w; 
 let texelSizeY = 1.0 / h; 
  
 return { 
 texture, 
 fbo, 
 width: w, 
 height: h, 
 texelSizeX, 
 texelSizeY, 
 attach(id) { 
 gl.activeTexture(gl.TEXTURE0 + id); 
 gl.bindTexture(gl.TEXTURE_2D, texture); 
 return id; 
 } 
 }; 
 } 
  
 function createDoubleFBO(w, h, internalFormat, format, type, param) { 
 let fbo1 = createFBO(w, h, internalFormat, format, type, param); 
 let fbo2 = createFBO(w, h, internalFormat, format, type, param); 
  
 return { 
 width: w, 
 height: h, 
 texelSizeX: fbo1.texelSizeX, 
 texelSizeY: fbo1.texelSizeY, 
 get read() { 
 return fbo1; 
 }, 
 set read(value) { 
 fbo1 = value; 
 }, 
 get write() { 
 return fbo2; 
 }, 
 set write(value) { 
 fbo2 = value; 
 }, 
 swap() { 
 let temp = fbo1; 
 fbo1 = fbo2; 
 fbo2 = temp; 
 } 
 } 
 } 
  
 function resizeFBO(target, w, h, internalFormat, format, type, param) { 
 let newFBO = createFBO(w, h, internalFormat, format, type, param); 
 copyProgram.bind(); 
 gl.uniform1i(copyProgram.uniforms.uTexture, target.attach(0)); 
 blit(newFBO); 
 return newFBO; 
 } 
  
 function resizeDoubleFBO(target, w, h, internalFormat, format, type, param) { 
 if (target.width == w && target.height == h) 
 return target; 
  
 target.read = resizeFBO(target.read, w, h, internalFormat, format, type, param); 
 target.write = createFBO(w, h, internalFormat, format, type, param); 
 target.width = w; 
 target.height = h; 
 target.texelSizeX = 1.0 / w; 
 target.texelSizeY = 1.0 / h; 
  
 return target; 
 } 
  
 function updateKeywords() { 
 let displayKeywords = []; 
 if (config.SHADING) displayKeywords.push("SHADING"); 
 displayMaterial.setKeywords(displayKeywords); 
 } 
  
 updateKeywords(); 
 initFramebuffers(); 
  
 let lastUpdateTime = Date.now(); 
 let colorUpdateTimer = 0.0; 
  
 function update() { 
 const dt = calcDeltaTime(); 
  
 if (resizeCanvas()) 
 initFramebuffers(); 
  
 updateColors(dt); 
 applyInputs(); 
 step(dt); 
 render(null); 
  
 requestAnimationFrame(update); 
 } 
  
 function calcDeltaTime() { 
 let now = Date.now(); 
 let dt = (now - lastUpdateTime) / 1000; 
 dt = Math.min(dt, 0.016666); 
 lastUpdateTime = now; 
 return dt; 
 } 
  
 function resizeCanvas() { 
 let width = scaleByPixelRatio(canvas.clientWidth); 
 let height = scaleByPixelRatio(canvas.clientHeight); 
  
 if (canvas.width != width || canvas.height != height) { 
 canvas.width = width; 
 canvas.height = height; 
 return true; 
 } 
  
 return false; 
 } 
  
 function updateColors(dt) { 
 colorUpdateTimer += dt * config.COLOR_UPDATE_SPEED; 
  
 if (colorUpdateTimer >= 1) { 
 colorUpdateTimer = wrap(colorUpdateTimer, 0, 1); 
 pointers.forEach(p => { 
 p.color = generateColor(); 
 }); 
 } 
 } 
  
 function applyInputs() { 
 pointers.forEach(p => { 
 if (p.moved) { 
 p.moved = false; 
 splatPointer(p); 
 } 
 }); 
 } 
  
 function step(dt) { 
 gl.disable(gl.BLEND); 
  
 curlProgram.bind(); 
 gl.uniform2f(curlProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY); 
 gl.uniform1i(curlProgram.uniforms.uVelocity, velocity.read.attach(0)); 
 blit(curl); 
  
 vorticityProgram.bind(); 
 gl.uniform2f(vorticityProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY); 
 gl.uniform1i(vorticityProgram.uniforms.uVelocity, velocity.read.attach(0)); 
 gl.uniform1i(vorticityProgram.uniforms.uCurl, curl.attach(1)); 
 gl.uniform1f(vorticityProgram.uniforms.curl, config.CURL); 
 gl.uniform1f(vorticityProgram.uniforms.dt, dt); 
 blit(velocity.write); 
 velocity.swap(); 
  
 divergenceProgram.bind(); 
 gl.uniform2f(divergenceProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY); 
 gl.uniform1i(divergenceProgram.uniforms.uVelocity, velocity.read.attach(0)); 
 blit(divergence); 
  
 clearProgram.bind(); 
 gl.uniform1i(clearProgram.uniforms.uTexture, pressure.read.attach(0)); 
 gl.uniform1f(clearProgram.uniforms.value, config.PRESSURE); 
 blit(pressure.write); 
 pressure.swap(); 
  
 pressureProgram.bind(); 
 gl.uniform2f(pressureProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY); 
 gl.uniform1i(pressureProgram.uniforms.uDivergence, divergence.attach(0)); 
  
 for (let i = 0; i < config.PRESSURE_ITERATIONS; i++) { 
 gl.uniform1i(pressureProgram.uniforms.uPressure, pressure.read.attach(1)); 
 blit(pressure.write); 
 pressure.swap(); 
 } 
  
 gradienSubtractProgram.bind(); 
 gl.uniform2f(gradienSubtractProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY); 
 gl.uniform1i(gradienSubtractProgram.uniforms.uPressure, pressure.read.attach(0)); 
 gl.uniform1i(gradienSubtractProgram.uniforms.uVelocity, velocity.read.attach(1)); 
 blit(velocity.write); 
 velocity.swap(); 
  
 advectionProgram.bind(); 
 gl.uniform2f(advectionProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY); 
  
 if (!ext.supportLinearFiltering) 
 gl.uniform2f(advectionProgram.uniforms.dyeTexelSize, velocity.texelSizeX, velocity.texelSizeY); 
  
 let velocityId = velocity.read.attach(0); 
 gl.uniform1i(advectionProgram.uniforms.uVelocity, velocityId); 
 gl.uniform1i(advectionProgram.uniforms.uSource, velocityId); 
 gl.uniform1f(advectionProgram.uniforms.dt, dt); 
 gl.uniform1f(advectionProgram.uniforms.dissipation, config.VELOCITY_DISSIPATION); 
 blit(velocity.write); 
 velocity.swap(); 
  
 if (!ext.supportLinearFiltering) 
 gl.uniform2f(advectionProgram.uniforms.dyeTexelSize, dye.texelSizeX, dye.texelSizeY); 
  
 gl.uniform1i(advectionProgram.uniforms.uVelocity, velocity.read.attach(0)); 
 gl.uniform1i(advectionProgram.uniforms.uSource, dye.read.attach(1)); 
 gl.uniform1f(advectionProgram.uniforms.dissipation, config.DENSITY_DISSIPATION); 
 blit(dye.write); 
 dye.swap(); 
 } 
  
 function render(target) { 
 gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); 
 gl.enable(gl.BLEND); 
 drawDisplay(target); 
 } 
  
 function drawDisplay(target) { 
 let width = target == null ? gl.drawingBufferWidth : target.width; 
 let height = target == null ? gl.drawingBufferHeight : target.height; 
  
 displayMaterial.bind(); 
  
 if (config.SHADING) 
 gl.uniform2f(displayMaterial.uniforms.texelSize, 1.0 / width, 1.0 / height); 
  
 gl.uniform1i(displayMaterial.uniforms.uTexture, dye.read.attach(0)); 
 blit(target); 
 } 
  
 function splatPointer(pointer) { 
 let dx = pointer.deltaX * config.SPLAT_FORCE; 
 let dy = pointer.deltaY * config.SPLAT_FORCE; 
 splat(pointer.texcoordX, pointer.texcoordY, dx, dy, pointer.color); 
 } 
  
 function clickSplat(pointer) { 
 const color = generateColor(); 
 color.r *= 10.0; 
 color.g *= 10.0; 
 color.b *= 10.0; 
 let dx = 10 * (Math.random() - 0.5); 
 let dy = 30 * (Math.random() - 0.5); 
 splat(pointer.texcoordX, pointer.texcoordY, dx, dy, color); 
 } 
  
 function splat(x, y, dx, dy, color) { 
 splatProgram.bind(); 
 gl.uniform1i(splatProgram.uniforms.uTarget, velocity.read.attach(0)); 
 gl.uniform1f(splatProgram.uniforms.aspectRatio, canvas.width / canvas.height); 
 gl.uniform2f(splatProgram.uniforms.point, x, y); 
 gl.uniform3f(splatProgram.uniforms.color, dx, dy, 0.0); 
 gl.uniform1f(splatProgram.uniforms.radius, correctRadius(config.SPLAT_RADIUS / 100.0)); 
 blit(velocity.write); 
 velocity.swap(); 
  
 gl.uniform1i(splatProgram.uniforms.uTarget, dye.read.attach(0)); 
 gl.uniform3f(splatProgram.uniforms.color, color.r, color.g, color.b); 
 blit(dye.write); 
 dye.swap(); 
 } 
  
 function correctRadius(radius) { 
 let aspectRatio = canvas.width / canvas.height; 
 if (aspectRatio > 1) 
 radius *= aspectRatio; 
 return radius; 
 } 
  
 // Event listeners for interaction 
 window.addEventListener('mousedown', e => { 
 let pointer = pointers[0]; 
 let posX = scaleByPixelRatio(e.clientX); 
 let posY = scaleByPixelRatio(e.clientY); 
 updatePointerDownData(pointer, -1, posX, posY); 
 clickSplat(pointer); 
 }); 
  
 window.addEventListener('mousemove', e => { 
 let pointer = pointers[0]; 
 let posX = scaleByPixelRatio(e.clientX); 
 let posY = scaleByPixelRatio(e.clientY); 
 updatePointerMoveData(pointer, posX, posY, pointer.color); 
 }); 
  
 window.addEventListener('touchstart', e => { 
 const touches = e.targetTouches; 
 let pointer = pointers[0]; 
 for (let i = 0; i < touches.length; i++) { 
 let posX = scaleByPixelRatio(touches[i].clientX); 
 let posY = scaleByPixelRatio(touches[i].clientY); 
 updatePointerDownData(pointer, touches[i].identifier, posX, posY); 
 } 
 }); 
  
 window.addEventListener('touchmove', e => { 
 const touches = e.targetTouches; 
 let pointer = pointers[0]; 
 for (let i = 0; i < touches.length; i++) { 
 let posX = scaleByPixelRatio(touches[i].clientX); 
 let posY = scaleByPixelRatio(touches[i].clientY); 
 updatePointerMoveData(pointer, posX, posY, pointer.color); 
 } 
 }, false); 
  
 window.addEventListener('touchend', e => { 
 const touches = e.changedTouches; 
 let pointer = pointers[0]; 
 for (let i = 0; i < touches.length; i++) { 
 updatePointerUpData(pointer); 
 } 
 }); 
  
 // Helper functions for pointer interactions 
 function updatePointerDownData(pointer, id, posX, posY) { 
 pointer.id = id; 
 pointer.down = true; 
 pointer.moved = false; 
 pointer.texcoordX = posX / canvas.width; 
 pointer.texcoordY = 1.0 - posY / canvas.height; 
 pointer.prevTexcoordX = pointer.texcoordX; 
 pointer.prevTexcoordY = pointer.texcoordY; 
 pointer.deltaX = 0; 
 pointer.deltaY = 0; 
 pointer.color = generateColor(); 
 } 
  
 function updatePointerMoveData(pointer, posX, posY, color) { 
 pointer.prevTexcoordX = pointer.texcoordX; 
 pointer.prevTexcoordY = pointer.texcoordY; 
 pointer.texcoordX = posX / canvas.width; 
 pointer.texcoordY = 1.0 - posY / canvas.height; 
 pointer.deltaX = correctDeltaX(pointer.texcoordX - pointer.prevTexcoordX); 
 pointer.deltaY = correctDeltaY(pointer.texcoordY - pointer.prevTexcoordY); 
 pointer.moved = Math.abs(pointer.deltaX) > 0 || Math.abs(pointer.deltaY) > 0; 
 pointer.color = color; 
 } 
  
 function updatePointerUpData(pointer) { 
 pointer.down = false; 
 } 
  
 function correctDeltaX(delta) { 
 let aspectRatio = canvas.width / canvas.height; 
 if (aspectRatio < 1) delta *= aspectRatio; 
 return delta; 
 } 
  
 function correctDeltaY(delta) { 
 let aspectRatio = canvas.width / canvas.height; 
 if (aspectRatio > 1) delta /= aspectRatio; 
 return delta; 
 } 
  
 // Color generation functions 
 function generateColor() { 
 let c = HSVtoRGB(Math.random(), 1.0, 1.0); 
 c.r *= 0.15; 
 c.g *= 0.15; 
 c.b *= 0.15; 
 return c; 
 } 
  
 function HSVtoRGB(h, s, v) { 
 let r, g, b, i, f, p, q, t; 
 i = Math.floor(h * 6); 
 f = h * 6 - i; 
 p = v * (1 - s); 
 q = v * (1 - f * s); 
 t = v * (1 - (1 - f) * s); 
  
 switch (i % 6) { 
 case 0: r = v, g = t, b = p; break; 
 case 1: r = q, g = v, b = p; break; 
 case 2: r = p, g = v, b = t; break; 
 case 3: r = p, g = q, b = v; break; 
 case 4: r = t, g = p, b = v; break; 
 case 5: r = v, g = p, b = q; break; 
 } 
  
 return { 
 r, 
 g, 
 b 
 }; 
 } 
  
 // Utility functions 
 function wrap(value, min, max) { 
 let range = max - min; 
 if (range == 0) return min; 
 return (value - min) % range + min; 
 } 
  
 function getResolution(resolution) { 
 let aspectRatio = gl.drawingBufferWidth / gl.drawingBufferHeight; 
 if (aspectRatio < 1) 
 aspectRatio = 1.0 / aspectRatio; 
  
 let min = Math.round(resolution); 
 let max = Math.round(resolution * aspectRatio); 
  
 if (gl.drawingBufferWidth > gl.drawingBufferHeight) 
 return { width: max, height: min }; 
 else 
 return { width: min, height: max }; 
 } 
  
 function scaleByPixelRatio(input) { 
 let pixelRatio = window.devicePixelRatio || 1; 
 return Math.floor(input * pixelRatio); 
 } 
  
 function hashCode(s) { 
 if (s.length == 0) return 0; 
 let hash = 0; 
 for (let i = 0; i < s.length; i++) { 
 hash = (hash << 5) - hash + s.charCodeAt(i); 
 hash |= 0; // Convert to 32bit integer 
 } 
 return hash; 
 } 
  
 // Start the animation 
 update(); 
  
 // Add some initial splats to make it interesting 
 setTimeout(() => { 
 for (let i = 0; i < 5; i++) { 
 const color = generateColor(); 
 color.r *= 10.0; 
 color.g *= 10.0; 
 color.b *= 10.0; 
 const x = Math.random(); 
 const y = Math.random(); 
 const dx = 1000 * (Math.random() - 0.5); 
 const dy = 1000 * (Math.random() - 0.5); 
 splat(x, y, dx, dy, color); 
 } 
 }, 100); 
  
 // Handle window resize 
 window.addEventListener('resize', () => { 
 resizeCanvas(); 
 }); 
 }; 
 })(); 
 </script> 
 </div> 
 <!-- End of JavaScript for WebGL Fluid Background Effect --> 
 <div id="snippet-niklz2ru"> 
 <!-- HTML SECTION START --> 
 <div id="webgl-fluid-background"> 
 <!-- 
 * WebGL Fluid Background Effect - HTML Structure 
 * This is the HTML structure for the fluid particle animation 
 * 
 * BLEND MODE: active 
 * Change to "inactive" in the JavaScript to disable the screen blend mode 
 --> 
  
 <!-- WEBGL Fluid particles canvas --> 
 <canvas id="fluid"></canvas> 
 </div> 
 <!-- End of HTML Structure for WebGL Fluid Background Effect --> 
 <!-- HTML SECTION END --> 
  
 <!-- CSS SECTION START --> 
 <style> 
 <div id="webgl-fluid-css"> 
 <!-- 
 * WebGL Fluid Background Effect - CSS Styles 
 * These styles position the fluid effect over the entire page 
 --> 
 <style> 
 /* Container styles to ensure proper positioning */ 
 #webgl-fluid-background { 
 position: fixed; 
 top: 0; 
 left: 0; 
 width: 100%; 
 height: 100%; 
 z-index: 99999; /* Using high z-index to ensure it's above everything */ 
 pointer-events: none; /* Allows clicks to pass through to elements below */ 
 } 
  
 /* Canvas styles for the fluid animation */ 
 #webgl-fluid-background #fluid { 
 position: fixed; 
 top: 0; 
 left: 0; 
 width: 100%; 
 height: 100%; 
 } 
 </style> 
 </div> 
 <!-- End of CSS for WebGL Fluid Background Effect --> 
 </style> 
 <!-- CSS SECTION END --> 
  
 <!-- JAVASCRIPT SECTION START --> 
 <script> 
 <div id="webgl-fluid-javascript"> 
 <!-- 
 * WebGL Fluid Background Effect - JavaScript 
 * This script creates the fluid particle animation with WebGL 
 --> 
 <script> 
 // Self-executing function to avoid global scope pollution 
 (function() { 
 // Initialize fluid animation on page load 
 window.addEventListener('load', () => { 
 initFluid(); 
  
 // Check for blend mode setting 
 const blendModeActive = true; // Change to false to disable blend mode 
  
 // Apply blend mode if active 
 if (blendModeActive) { 
 document.getElementById('fluid').style.mixBlendMode = 'screen'; 
 } 
 }); 
  
 const initFluid = () => { 
 // Get the canvas element 
 const canvas = document.getElementById('fluid'); 
  
 // Resize canvas to match window size 
 function resizeCanvas() { 
 canvas.width = window.innerWidth; 
 canvas.height = window.innerHeight; 
 } 
 resizeCanvas(); 
  
 // Configuration for the fluid simulation 
 let config = { 
 SIM_RESOLUTION: 128, 
 DYE_RESOLUTION: 1440, 
 CAPTURE_RESOLUTION: 512, 
 DENSITY_DISSIPATION: 3.5, 
 VELOCITY_DISSIPATION: 2, 
 PRESSURE: 0.1, 
 PRESSURE_ITERATIONS: 20, 
 CURL: 3, 
 SPLAT_RADIUS: 0.2, 
 SPLAT_FORCE: 6000, 
 SHADING: true, 
 COLOR_UPDATE_SPEED: 10, 
 PAUSED: false, 
 BACK_COLOR: { r: 0, g: 0, b: 0 }, 
 TRANSPARENT: true, 
 } 
  
 // Pointer prototype for tracking mouse/touch interactions 
 function pointerPrototype() { 
 this.id = -1; 
 this.texcoordX = 0; 
 this.texcoordY = 0; 
 this.prevTexcoordX = 0; 
 this.prevTexcoordY = 0; 
 this.deltaX = 0; 
 this.deltaY = 0; 
 this.down = false; 
 this.moved = false; 
 this.color = [30, 0, 300]; 
 } 
  
 let pointers = []; 
 pointers.push(new pointerPrototype()); 
  
 // Initialize WebGL context 
 const { gl, ext } = getWebGLContext(canvas); 
  
 if (!ext.supportLinearFiltering) { 
 config.DYE_RESOLUTION = 512; 
 config.SHADING = false; 
 } 
  
 function getWebGLContext(canvas) { 
 const params = { alpha: true, depth: false, stencil: false, antialias: false, preserveDrawingBuffer: false }; 
 let gl = canvas.getContext('webgl2', params); 
 const isWebGL2 = !!gl; 
 if (!isWebGL2) 
 gl = canvas.getContext('webgl', params) || canvas.getContext('experimental-webgl', params); 
  
 let halfFloat; 
 let supportLinearFiltering; 
  
 if (isWebGL2) { 
 gl.getExtension('EXT_color_buffer_float'); 
 supportLinearFiltering = gl.getExtension('OES_texture_float_linear'); 
 } else { 
 halfFloat = gl.getExtension('OES_texture_half_float'); 
 supportLinearFiltering = gl.getExtension('OES_texture_half_float_linear'); 
 } 
  
 gl.clearColor(0.0, 0.0, 0.0, 1.0); 
 const halfFloatTexType = isWebGL2 ? gl.HALF_FLOAT : halfFloat.HALF_FLOAT_OES; 
 let formatRGBA; 
 let formatRG; 
 let formatR; 
  
 if (isWebGL2) { 
 formatRGBA = getSupportedFormat(gl, gl.RGBA16F, gl.RGBA, halfFloatTexType); 
 formatRG = getSupportedFormat(gl, gl.RG16F, gl.RG, halfFloatTexType); 
 formatR = getSupportedFormat(gl, gl.R16F, gl.RED, halfFloatTexType); 
 } else { 
 formatRGBA = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloatTexType); 
 formatRG = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloatTexType); 
 formatR = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloatTexType); 
 } 
  
 return { 
 gl, 
 ext: { 
 formatRGBA, 
 formatRG, 
 formatR, 
 halfFloatTexType, 
 supportLinearFiltering 
 } 
 }; 
 } 
  
 function getSupportedFormat(gl, internalFormat, format, type) { 
 if (!supportRenderTextureFormat(gl, internalFormat, format, type)) { 
 switch (internalFormat) { 
 case gl.R16F: 
 return getSupportedFormat(gl, gl.RG16F, gl.RG, type); 
 case gl.RG16F: 
 return getSupportedFormat(gl, gl.RGBA16F, gl.RGBA, type); 
 default: 
 return null; 
 } 
 } 
  
 return { 
 internalFormat, 
 format 
 } 
 } 
  
 function supportRenderTextureFormat(gl, internalFormat, format, type) { 
 let texture = gl.createTexture(); 
 gl.bindTexture(gl.TEXTURE_2D, texture); 
 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); 
 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); 
 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 
 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 
 gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, 4, 4, 0, format, type, null); 
  
 let fbo = gl.createFramebuffer(); 
 gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); 
 gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); 
  
 let status = gl.checkFramebufferStatus(gl.FRAMEBUFFER); 
 return status == gl.FRAMEBUFFER_COMPLETE; 
 } 
  
 class Material { 
 constructor(vertexShader, fragmentShaderSource) { 
 this.vertexShader = vertexShader; 
 this.fragmentShaderSource = fragmentShaderSource; 
 this.programs = []; 
 this.activeProgram = null; 
 this.uniforms = []; 
 } 
  
 setKeywords(keywords) { 
 let hash = 0; 
 for (let i = 0; i < keywords.length; i++) 
 hash += hashCode(keywords[i]); 
  
 let program = this.programs[hash]; 
 if (program == null) { 
 let fragmentShader = compileShader(gl.FRAGMENT_SHADER, this.fragmentShaderSource, keywords); 
 program = createProgram(this.vertexShader, fragmentShader); 
 this.programs[hash] = program; 
 } 
  
 if (program == this.activeProgram) return; 
  
 this.uniforms = getUniforms(program); 
 this.activeProgram = program; 
 } 
  
 bind() { 
 gl.useProgram(this.activeProgram); 
 } 
 } 
  
 class Program { 
 constructor(vertexShader, fragmentShader) { 
 this.uniforms = {}; 
 this.program = createProgram(vertexShader, fragmentShader); 
 this.uniforms = getUniforms(this.program); 
 } 
  
 bind() { 
 gl.useProgram(this.program); 
 } 
 } 
  
 function createProgram(vertexShader, fragmentShader) { 
 let program = gl.createProgram(); 
 gl.attachShader(program, vertexShader); 
 gl.attachShader(program, fragmentShader); 
 gl.linkProgram(program); 
  
 if (!gl.getProgramParameter(program, gl.LINK_STATUS)) 
 console.trace(gl.getProgramInfoLog(program)); 
  
 return program; 
 } 
  
 function getUniforms(program) { 
 let uniforms = []; 
 let uniformCount = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); 
  
 for (let i = 0; i < uniformCount; i++) { 
 let uniformName = gl.getActiveUniform(program, i).name; 
 uniforms[uniformName] = gl.getUniformLocation(program, uniformName); 
 } 
  
 return uniforms; 
 } 
  
 function compileShader(type, source, keywords) { 
 source = addKeywords(source, keywords); 
 const shader = gl.createShader(type); 
 gl.shaderSource(shader, source); 
 gl.compileShader(shader); 
  
 if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) 
 console.trace(gl.getShaderInfoLog(shader)); 
  
 return shader; 
 } 
  
 function addKeywords(source, keywords) { 
 if (keywords == null) return source; 
 let keywordsString = ''; 
 keywords.forEach(keyword => { 
 keywordsString += '#define ' + keyword + '\n'; 
 }); 
 return keywordsString + source; 
 } 
  
 // Load shaders 
 const baseVertexShader = compileShader(gl.VERTEX_SHADER, ` 
 precision highp float; 
 attribute vec2 aPosition; 
 varying vec2 vUv; 
 varying vec2 vL; 
 varying vec2 vR; 
 varying vec2 vT; 
 varying vec2 vB; 
 uniform vec2 texelSize; 
 void main () { 
 vUv = aPosition * 0.5 + 0.5; 
 vL = vUv - vec2(texelSize.x, 0.0); 
 vR = vUv + vec2(texelSize.x, 0.0); 
 vT = vUv + vec2(0.0, texelSize.y); 
 vB = vUv - vec2(0.0, texelSize.y); 
 gl_Position = vec4(aPosition, 0.0, 1.0); 
 } 
 `); 
  
 const blurVertexShader = compileShader(gl.VERTEX_SHADER, ` 
 precision highp float; 
 attribute vec2 aPosition; 
 varying vec2 vUv; 
 varying vec2 vL; 
 varying vec2 vR; 
 uniform vec2 texelSize; 
 void main () { 
 vUv = aPosition * 0.5 + 0.5; 
 float offset = 1.33333333; 
 vL = vUv - texelSize * offset; 
 vR = vUv + texelSize * offset; 
 gl_Position = vec4(aPosition, 0.0, 1.0); 
 } 
 `); 
  
 const blurShader = compileShader(gl.FRAGMENT_SHADER, ` 
 precision mediump float; 
 precision mediump sampler2D; 
 varying vec2 vUv; 
 varying vec2 vL; 
 varying vec2 vR; 
 uniform sampler2D uTexture; 
 void main () { 
 vec4 sum = texture2D(uTexture, vUv) * 0.29411764; 
 sum += texture2D(uTexture, vL) * 0.35294117; 
 sum += texture2D(uTexture, vR) * 0.35294117; 
 gl_FragColor = sum; 
 } 
 `); 
  
 const copyShader = compileShader(gl.FRAGMENT_SHADER, ` 
 precision mediump float; 
 precision mediump sampler2D; 
 varying highp vec2 vUv; 
 uniform sampler2D uTexture; 
 void main () { 
 gl_FragColor = texture2D(uTexture, vUv); 
 } 
 `); 
  
 const clearShader = compileShader(gl.FRAGMENT_SHADER, ` 
 precision mediump float; 
 precision mediump sampler2D; 
 varying highp vec2 vUv; 
 uniform sampler2D uTexture; 
 uniform float value; 
 void main () { 
 gl_FragColor = value * texture2D(uTexture, vUv); 
 } 
 `); 
  
 const colorShader = compileShader(gl.FRAGMENT_SHADER, ` 
 precision mediump float; 
 uniform vec4 color; 
 void main () { 
 gl_FragColor = color; 
 } 
 `); 
  
 const displayShaderSource = ` 
 precision highp float; 
 precision highp sampler2D; 
 varying vec2 vUv; 
 varying vec2 vL; 
 varying vec2 vR; 
 varying vec2 vT; 
 varying vec2 vB; 
 uniform sampler2D uTexture; 
 uniform sampler2D uDithering; 
 uniform vec2 ditherScale; 
 uniform vec2 texelSize; 
 vec3 linearToGamma (vec3 color) { 
 color = max(color, vec3(0)); 
 return max(1.055 * pow(color, vec3(0.416666667)) - 0.055, vec3(0)); 
 } 
 void main () { 
 vec3 c = texture2D(uTexture, vUv).rgb; 
 #ifdef SHADING 
 vec3 lc = texture2D(uTexture, vL).rgb; 
 vec3 rc = texture2D(uTexture, vR).rgb; 
 vec3 tc = texture2D(uTexture, vT).rgb; 
 vec3 bc = texture2D(uTexture, vB).rgb; 
 float dx = length(rc) - length(lc); 
 float dy = length(tc) - length(bc); 
 vec3 n = normalize(vec3(dx, dy, length(texelSize))); 
 vec3 l = vec3(0.0, 0.0, 1.0); 
 float diffuse = clamp(dot(n, l) + 0.7, 0.7, 1.0); 
 c *= diffuse; 
 #endif 
 float a = max(c.r, max(c.g, c.b)); 
 gl_FragColor = vec4(c, a); 
 } 
 `; 
  
 const splatShader = compileShader(gl.FRAGMENT_SHADER, ` 
 precision highp float; 
 precision highp sampler2D; 
 varying vec2 vUv; 
 uniform sampler2D uTarget; 
 uniform float aspectRatio; 
 uniform vec3 color; 
 uniform vec2 point; 
 uniform float radius; 
 void main () { 
 vec2 p = vUv - point.xy; 
 p.x *= aspectRatio; 
 vec3 splat = exp(-dot(p, p) / radius) * color; 
 vec3 base = texture2D(uTarget, vUv).xyz; 
 gl_FragColor = vec4(base + splat, 1.0); 
 } 
 `); 
  
 const advectionShader = compileShader(gl.FRAGMENT_SHADER, ` 
 precision highp float; 
 precision highp sampler2D; 
 varying vec2 vUv; 
 uniform sampler2D uVelocity; 
 uniform sampler2D uSource; 
 uniform vec2 texelSize; 
 uniform vec2 dyeTexelSize; 
 uniform float dt; 
 uniform float dissipation; 
 vec4 bilerp (sampler2D sam, vec2 uv, vec2 tsize) { 
 vec2 st = uv / tsize - 0.5; 
 vec2 iuv = floor(st); 
 vec2 fuv = fract(st); 
 vec4 a = texture2D(sam, (iuv + vec2(0.5, 0.5)) * tsize); 
 vec4 b = texture2D(sam, (iuv + vec2(1.5, 0.5)) * tsize); 
 vec4 c = texture2D(sam, (iuv + vec2(0.5, 1.5)) * tsize); 
 vec4 d = texture2D(sam, (iuv + vec2(1.5, 1.5)) * tsize); 
 return mix(mix(a, b, fuv.x), mix(c, d, fuv.x), fuv.y); 
 } 
 void main () { 
 #ifdef MANUAL_FILTERING 
 vec2 coord = vUv - dt * bilerp(uVelocity, vUv, texelSize).xy * texelSize; 
 vec4 result = bilerp(uSource, coord, dyeTexelSize); 
 #else 
 vec2 coord = vUv - dt * texture2D(uVelocity, vUv).xy * texelSize; 
 vec4 result = texture2D(uSource, coord); 
 #endif 
 float decay = 1.0 + dissipation * dt; 
 gl_FragColor = result / decay; 
 }`, 
 ext.supportLinearFiltering ? null : ['MANUAL_FILTERING'] 
 ); 
  
 const divergenceShader = compileShader(gl.FRAGMENT_SHADER, ` 
 precision mediump float; 
 precision mediump sampler2D; 
 varying highp vec2 vUv; 
 varying highp vec2 vL; 
 varying highp vec2 vR; 
 varying highp vec2 vT; 
 varying highp vec2 vB; 
 uniform sampler2D uVelocity; 
 void main () { 
 float L = texture2D(uVelocity, vL).x; 
 float R = texture2D(uVelocity, vR).x; 
 float T = texture2D(uVelocity, vT).y; 
 float B = texture2D(uVelocity, vB).y; 
 vec2 C = texture2D(uVelocity, vUv).xy; 
 if (vL.x < 0.0) { L = -C.x; } 
 if (vR.x > 1.0) { R = -C.x; } 
 if (vT.y > 1.0) { T = -C.y; } 
 if (vB.y < 0.0) { B = -C.y; } 
 float div = 0.5 * (R - L + T - B); 
 gl_FragColor = vec4(div, 0.0, 0.0, 1.0); 
 } 
 `); 
  
 const curlShader = compileShader(gl.FRAGMENT_SHADER, ` 
 precision mediump float; 
 precision mediump sampler2D; 
 varying highp vec2 vUv; 
 varying highp vec2 vL; 
 varying highp vec2 vR; 
 varying highp vec2 vT; 
 varying highp vec2 vB; 
 uniform sampler2D uVelocity; 
 void main () { 
 float L = texture2D(uVelocity, vL).y; 
 float R = texture2D(uVelocity, vR).y; 
 float T = texture2D(uVelocity, vT).x; 
 float B = texture2D(uVelocity, vB).x; 
 float vorticity = R - L - T + B; 
 gl_FragColor = vec4(0.5 * vorticity, 0.0, 0.0, 1.0); 
 } 
 `); 
  
 const vorticityShader = compileShader(gl.FRAGMENT_SHADER, ` 
 precision highp float; 
 precision highp sampler2D; 
 varying vec2 vUv; 
 varying vec2 vL; 
 varying vec2 vR; 
 varying vec2 vT; 
 varying vec2 vB; 
 uniform sampler2D uVelocity; 
 uniform sampler2D uCurl; 
 uniform float curl; 
 uniform float dt; 
 void main () { 
 float L = texture2D(uCurl, vL).x; 
 float R = texture2D(uCurl, vR).x; 
 float T = texture2D(uCurl, vT).x; 
 float B = texture2D(uCurl, vB).x; 
 float C = texture2D(uCurl, vUv).x; 
 vec2 force = 0.5 * vec2(abs(T) - abs(B), abs(R) - abs(L)); 
 force /= length(force) + 0.0001; 
 force *= curl * C; 
 force.y *= -1.0; 
 vec2 velocity = texture2D(uVelocity, vUv).xy; 
 velocity += force * dt; 
 velocity = min(max(velocity, -1000.0), 1000.0); 
 gl_FragColor = vec4(velocity, 0.0, 1.0); 
 } 
 `); 
  
 const pressureShader = compileShader(gl.FRAGMENT_SHADER, ` 
 precision mediump float; 
 precision mediump sampler2D; 
 varying highp vec2 vUv; 
 varying highp vec2 vL; 
 varying highp vec2 vR; 
 varying highp vec2 vT; 
 varying highp vec2 vB; 
 uniform sampler2D uPressure; 
 uniform sampler2D uDivergence; 
 void main () { 
 float L = texture2D(uPressure, vL).x; 
 float R = texture2D(uPressure, vR).x; 
 float T = texture2D(uPressure, vT).x; 
 float B = texture2D(uPressure, vB).x; 
 float C = texture2D(uPressure, vUv).x; 
 float divergence = texture2D(uDivergence, vUv).x; 
 float pressure = (L + R + B + T - divergence) * 0.25; 
 gl_FragColor = vec4(pressure, 0.0, 0.0, 1.0); 
 } 
 `); 
  
 const gradientSubtractShader = compileShader(gl.FRAGMENT_SHADER, ` 
 precision mediump float; 
 precision mediump sampler2D; 
 varying highp vec2 vUv; 
 varying highp vec2 vL; 
 varying highp vec2 vR; 
 varying highp vec2 vT; 
 varying highp vec2 vB; 
 uniform sampler2D uPressure; 
 uniform sampler2D uVelocity; 
 void main () { 
 float L = texture2D(uPressure, vL).x; 
 float R = texture2D(uPressure, vR).x; 
 float T = texture2D(uPressure, vT).x; 
 float B = texture2D(uPressure, vB).x; 
 vec2 velocity = texture2D(uVelocity, vUv).xy; 
 velocity.xy -= vec2(R - L, T - B); 
 gl_FragColor = vec4(velocity, 0.0, 1.0); 
 } 
 `); 
  
 const blit = (() => { 
 gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer()); 
 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, -1, 1, 1, 1, 1, -1]), gl.STATIC_DRAW); 
 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, gl.createBuffer()); 
 gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 0, 2, 3]), gl.STATIC_DRAW); 
 gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0); 
 gl.enableVertexAttribArray(0); 
  
 return (target, clear = false) => { 
 if (target == null) { 
 gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); 
 gl.bindFramebuffer(gl.FRAMEBUFFER, null); 
 } else { 
 gl.viewport(0, 0, target.width, target.height); 
 gl.bindFramebuffer(gl.FRAMEBUFFER, target.fbo); 
 } 
  
 if (clear) { 
 gl.clearColor(0.0, 0.0, 0.0, 1.0); 
 gl.clear(gl.COLOR_BUFFER_BIT); 
 } 
  
 gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0); 
 } 
 })(); 
  
 function CHECK_FRAMEBUFFER_STATUS() { 
 let status = gl.checkFramebufferStatus(gl.FRAMEBUFFER); 
 if (status != gl.FRAMEBUFFER_COMPLETE) 
 console.trace("Framebuffer error: " + status); 
 } 
  
 // Initialize variable and programs 
 let dye; 
 let velocity; 
 let divergence; 
 let curl; 
 let pressure; 
  
 const blurProgram = new Program(blurVertexShader, blurShader); 
 const copyProgram = new Program(baseVertexShader, copyShader); 
 const clearProgram = new Program(baseVertexShader, clearShader); 
 const colorProgram = new Program(baseVertexShader, colorShader); 
 const splatProgram = new Program(baseVertexShader, splatShader); 
 const advectionProgram = new Program(baseVertexShader, advectionShader); 
 const divergenceProgram = new Program(baseVertexShader, divergenceShader); 
 const curlProgram = new Program(baseVertexShader, curlShader); 
 const vorticityProgram = new Program(baseVertexShader, vorticityShader); 
 const pressureProgram = new Program(baseVertexShader, pressureShader); 
 const gradienSubtractProgram = new Program(baseVertexShader, gradientSubtractShader); 
  
 const displayMaterial = new Material(baseVertexShader, displayShaderSource); 
  
 function initFramebuffers() { 
 let simRes = getResolution(config.SIM_RESOLUTION); 
 let dyeRes = getResolution(config.DYE_RESOLUTION); 
  
 const texType = ext.halfFloatTexType; 
 const rgba = ext.formatRGBA; 
 const rg = ext.formatRG; 
 const r = ext.formatR; 
 const filtering = ext.supportLinearFiltering ? gl.LINEAR : gl.NEAREST; 
  
 gl.disable(gl.BLEND); 
  
 if (dye == null) 
 dye = createDoubleFBO(dyeRes.width, dyeRes.height, rgba.internalFormat, rgba.format, texType, filtering); 
 else 
 dye = resizeDoubleFBO(dye, dyeRes.width, dyeRes.height, rgba.internalFormat, rgba.format, texType, filtering); 
  
 if (velocity == null) 
 velocity = createDoubleFBO(simRes.width, simRes.height, rg.internalFormat, rg.format, texType, filtering); 
 else 
 velocity = resizeDoubleFBO(velocity, simRes.width, simRes.height, rg.internalFormat, rg.format, texType, filtering); 
  
 divergence = createFBO(simRes.width, simRes.height, r.internalFormat, r.format, texType, gl.NEAREST); 
 curl = createFBO(simRes.width, simRes.height, r.internalFormat, r.format, texType, gl.NEAREST); 
 pressure = createDoubleFBO(simRes.width, simRes.height, r.internalFormat, r.format, texType, gl.NEAREST); 
 } 
  
 function createFBO(w, h, internalFormat, format, type, param) { 
 gl.activeTexture(gl.TEXTURE0); 
 let texture = gl.createTexture(); 
 gl.bindTexture(gl.TEXTURE_2D, texture); 
 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, param); 
 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, param); 
 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 
 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 
 gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, w, h, 0, format, type, null); 
  
 let fbo = gl.createFramebuffer(); 
 gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); 
 gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); 
 gl.viewport(0, 0, w, h); 
 gl.clear(gl.COLOR_BUFFER_BIT); 
  
 let texelSizeX = 1.0 / w; 
 let texelSizeY = 1.0 / h; 
  
 return { 
 texture, 
 fbo, 
 width: w, 
 height: h, 
 texelSizeX, 
 texelSizeY, 
 attach(id) { 
 gl.activeTexture(gl.TEXTURE0 + id); 
 gl.bindTexture(gl.TEXTURE_2D, texture); 
 return id; 
 } 
 }; 
 } 
  
 function createDoubleFBO(w, h, internalFormat, format, type, param) { 
 let fbo1 = createFBO(w, h, internalFormat, format, type, param); 
 let fbo2 = createFBO(w, h, internalFormat, format, type, param); 
  
 return { 
 width: w, 
 height: h, 
 texelSizeX: fbo1.texelSizeX, 
 texelSizeY: fbo1.texelSizeY, 
 get read() { 
 return fbo1; 
 }, 
 set read(value) { 
 fbo1 = value; 
 }, 
 get write() { 
 return fbo2; 
 }, 
 set write(value) { 
 fbo2 = value; 
 }, 
 swap() { 
 let temp = fbo1; 
 fbo1 = fbo2; 
 fbo2 = temp; 
 } 
 } 
 } 
  
 function resizeFBO(target, w, h, internalFormat, format, type, param) { 
 let newFBO = createFBO(w, h, internalFormat, format, type, param); 
 copyProgram.bind(); 
 gl.uniform1i(copyProgram.uniforms.uTexture, target.attach(0)); 
 blit(newFBO); 
 return newFBO; 
 } 
  
 function resizeDoubleFBO(target, w, h, internalFormat, format, type, param) { 
 if (target.width == w && target.height == h) 
 return target; 
  
 target.read = resizeFBO(target.read, w, h, internalFormat, format, type, param); 
 target.write = createFBO(w, h, internalFormat, format, type, param); 
 target.width = w; 
 target.height = h; 
 target.texelSizeX = 1.0 / w; 
 target.texelSizeY = 1.0 / h; 
  
 return target; 
 } 
  
 function updateKeywords() { 
 let displayKeywords = []; 
 if (config.SHADING) displayKeywords.push("SHADING"); 
 displayMaterial.setKeywords(displayKeywords); 
 } 
  
 updateKeywords(); 
 initFramebuffers(); 
  
 let lastUpdateTime = Date.now(); 
 let colorUpdateTimer = 0.0; 
  
 function update() { 
 const dt = calcDeltaTime(); 
  
 if (resizeCanvas()) 
 initFramebuffers(); 
  
 updateColors(dt); 
 applyInputs(); 
 step(dt); 
 render(null); 
  
 requestAnimationFrame(update); 
 } 
  
 function calcDeltaTime() { 
 let now = Date.now(); 
 let dt = (now - lastUpdateTime) / 1000; 
 dt = Math.min(dt, 0.016666); 
 lastUpdateTime = now; 
 return dt; 
 } 
  
 function resizeCanvas() { 
 let width = scaleByPixelRatio(canvas.clientWidth); 
 let height = scaleByPixelRatio(canvas.clientHeight); 
  
 if (canvas.width != width || canvas.height != height) { 
 canvas.width = width; 
 canvas.height = height; 
 return true; 
 } 
  
 return false; 
 } 
  
 function updateColors(dt) { 
 colorUpdateTimer += dt * config.COLOR_UPDATE_SPEED; 
  
 if (colorUpdateTimer >= 1) { 
 colorUpdateTimer = wrap(colorUpdateTimer, 0, 1); 
 pointers.forEach(p => { 
 p.color = generateColor(); 
 }); 
 } 
 } 
  
 function applyInputs() { 
 pointers.forEach(p => { 
 if (p.moved) { 
 p.moved = false; 
 splatPointer(p); 
 } 
 }); 
 } 
  
 function step(dt) { 
 gl.disable(gl.BLEND); 
  
 curlProgram.bind(); 
 gl.uniform2f(curlProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY); 
 gl.uniform1i(curlProgram.uniforms.uVelocity, velocity.read.attach(0)); 
 blit(curl); 
  
 vorticityProgram.bind(); 
 gl.uniform2f(vorticityProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY); 
 gl.uniform1i(vorticityProgram.uniforms.uVelocity, velocity.read.attach(0)); 
 gl.uniform1i(vorticityProgram.uniforms.uCurl, curl.attach(1)); 
 gl.uniform1f(vorticityProgram.uniforms.curl, config.CURL); 
 gl.uniform1f(vorticityProgram.uniforms.dt, dt); 
 blit(velocity.write); 
 velocity.swap(); 
  
 divergenceProgram.bind(); 
 gl.uniform2f(divergenceProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY); 
 gl.uniform1i(divergenceProgram.uniforms.uVelocity, velocity.read.attach(0)); 
 blit(divergence); 
  
 clearProgram.bind(); 
 gl.uniform1i(clearProgram.uniforms.uTexture, pressure.read.attach(0)); 
 gl.uniform1f(clearProgram.uniforms.value, config.PRESSURE); 
 blit(pressure.write); 
 pressure.swap(); 
  
 pressureProgram.bind(); 
 gl.uniform2f(pressureProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY); 
 gl.uniform1i(pressureProgram.uniforms.uDivergence, divergence.attach(0)); 
  
 for (let i = 0; i < config.PRESSURE_ITERATIONS; i++) { 
 gl.uniform1i(pressureProgram.uniforms.uPressure, pressure.read.attach(1)); 
 blit(pressure.write); 
 pressure.swap(); 
 } 
  
 gradienSubtractProgram.bind(); 
 gl.uniform2f(gradienSubtractProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY); 
 gl.uniform1i(gradienSubtractProgram.uniforms.uPressure, pressure.read.attach(0)); 
 gl.uniform1i(gradienSubtractProgram.uniforms.uVelocity, velocity.read.attach(1)); 
 blit(velocity.write); 
 velocity.swap(); 
  
 advectionProgram.bind(); 
 gl.uniform2f(advectionProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY); 
  
 if (!ext.supportLinearFiltering) 
 gl.uniform2f(advectionProgram.uniforms.dyeTexelSize, velocity.texelSizeX, velocity.texelSizeY); 
  
 let velocityId = velocity.read.attach(0); 
 gl.uniform1i(advectionProgram.uniforms.uVelocity, velocityId); 
 gl.uniform1i(advectionProgram.uniforms.uSource, velocityId); 
 gl.uniform1f(advectionProgram.uniforms.dt, dt); 
 gl.uniform1f(advectionProgram.uniforms.dissipation, config.VELOCITY_DISSIPATION); 
 blit(velocity.write); 
 velocity.swap(); 
  
 if (!ext.supportLinearFiltering) 
 gl.uniform2f(advectionProgram.uniforms.dyeTexelSize, dye.texelSizeX, dye.texelSizeY); 
  
 gl.uniform1i(advectionProgram.uniforms.uVelocity, velocity.read.attach(0)); 
 gl.uniform1i(advectionProgram.uniforms.uSource, dye.read.attach(1)); 
 gl.uniform1f(advectionProgram.uniforms.dissipation, config.DENSITY_DISSIPATION); 
 blit(dye.write); 
 dye.swap(); 
 } 
  
 function render(target) { 
 gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); 
 gl.enable(gl.BLEND); 
 drawDisplay(target); 
 } 
  
 function drawDisplay(target) { 
 let width = target == null ? gl.drawingBufferWidth : target.width; 
 let height = target == null ? gl.drawingBufferHeight : target.height; 
  
 displayMaterial.bind(); 
  
 if (config.SHADING) 
 gl.uniform2f(displayMaterial.uniforms.texelSize, 1.0 / width, 1.0 / height); 
  
 gl.uniform1i(displayMaterial.uniforms.uTexture, dye.read.attach(0)); 
 blit(target); 
 } 
  
 function splatPointer(pointer) { 
 let dx = pointer.deltaX * config.SPLAT_FORCE; 
 let dy = pointer.deltaY * config.SPLAT_FORCE; 
 splat(pointer.texcoordX, pointer.texcoordY, dx, dy, pointer.color); 
 } 
  
 function clickSplat(pointer) { 
 const color = generateColor(); 
 color.r *= 10.0; 
 color.g *= 10.0; 
 color.b *= 10.0; 
 let dx = 10 * (Math.random() - 0.5); 
 let dy = 30 * (Math.random() - 0.5); 
 splat(pointer.texcoordX, pointer.texcoordY, dx, dy, color); 
 } 
  
 function splat(x, y, dx, dy, color) { 
 splatProgram.bind(); 
 gl.uniform1i(splatProgram.uniforms.uTarget, velocity.read.attach(0)); 
 gl.uniform1f(splatProgram.uniforms.aspectRatio, canvas.width / canvas.height); 
 gl.uniform2f(splatProgram.uniforms.point, x, y); 
 gl.uniform3f(splatProgram.uniforms.color, dx, dy, 0.0); 
 gl.uniform1f(splatProgram.uniforms.radius, correctRadius(config.SPLAT_RADIUS / 100.0)); 
 blit(velocity.write); 
 velocity.swap(); 
  
 gl.uniform1i(splatProgram.uniforms.uTarget, dye.read.attach(0)); 
 gl.uniform3f(splatProgram.uniforms.color, color.r, color.g, color.b); 
 blit(dye.write); 
 dye.swap(); 
 } 
  
 function correctRadius(radius) { 
 let aspectRatio = canvas.width / canvas.height; 
 if (aspectRatio > 1) 
 radius *= aspectRatio; 
 return radius; 
 } 
  
 // Event listeners for interaction 
 window.addEventListener('mousedown', e => { 
 let pointer = pointers[0]; 
 let posX = scaleByPixelRatio(e.clientX); 
 let posY = scaleByPixelRatio(e.clientY); 
 updatePointerDownData(pointer, -1, posX, posY); 
 clickSplat(pointer); 
 }); 
  
 window.addEventListener('mousemove', e => { 
 let pointer = pointers[0]; 
 let posX = scaleByPixelRatio(e.clientX); 
 let posY = scaleByPixelRatio(e.clientY); 
 updatePointerMoveData(pointer, posX, posY, pointer.color); 
 }); 
  
 window.addEventListener('touchstart', e => { 
 const touches = e.targetTouches; 
 let pointer = pointers[0]; 
 for (let i = 0; i < touches.length; i++) { 
 let posX = scaleByPixelRatio(touches[i].clientX); 
 let posY = scaleByPixelRatio(touches[i].clientY); 
 updatePointerDownData(pointer, touches[i].identifier, posX, posY); 
 } 
 }); 
  
 window.addEventListener('touchmove', e => { 
 const touches = e.targetTouches; 
 let pointer = pointers[0]; 
 for (let i = 0; i < touches.length; i++) { 
 let posX = scaleByPixelRatio(touches[i].clientX); 
 let posY = scaleByPixelRatio(touches[i].clientY); 
 updatePointerMoveData(pointer, posX, posY, pointer.color); 
 } 
 }, false); 
  
 window.addEventListener('touchend', e => { 
 const touches = e.changedTouches; 
 let pointer = pointers[0]; 
 for (let i = 0; i < touches.length; i++) { 
 updatePointerUpData(pointer); 
 } 
 }); 
  
 // Helper functions for pointer interactions 
 function updatePointerDownData(pointer, id, posX, posY) { 
 pointer.id = id; 
 pointer.down = true; 
 pointer.moved = false; 
 pointer.texcoordX = posX / canvas.width; 
 pointer.texcoordY = 1.0 - posY / canvas.height; 
 pointer.prevTexcoordX = pointer.texcoordX; 
 pointer.prevTexcoordY = pointer.texcoordY; 
 pointer.deltaX = 0; 
 pointer.deltaY = 0; 
 pointer.color = generateColor(); 
 } 
  
 function updatePointerMoveData(pointer, posX, posY, color) { 
 pointer.prevTexcoordX = pointer.texcoordX; 
 pointer.prevTexcoordY = pointer.texcoordY; 
 pointer.texcoordX = posX / canvas.width; 
 pointer.texcoordY = 1.0 - posY / canvas.height; 
 pointer.deltaX = correctDeltaX(pointer.texcoordX - pointer.prevTexcoordX); 
 pointer.deltaY = correctDeltaY(pointer.texcoordY - pointer.prevTexcoordY); 
 pointer.moved = Math.abs(pointer.deltaX) > 0 || Math.abs(pointer.deltaY) > 0; 
 pointer.color = color; 
 } 
  
 function updatePointerUpData(pointer) { 
 pointer.down = false; 
 } 
  
 function correctDeltaX(delta) { 
 let aspectRatio = canvas.width / canvas.height; 
 if (aspectRatio < 1) delta *= aspectRatio; 
 return delta; 
 } 
  
 function correctDeltaY(delta) { 
 let aspectRatio = canvas.width / canvas.height; 
 if (aspectRatio > 1) delta /= aspectRatio; 
 return delta; 
 } 
  
 // Color generation functions 
 function generateColor() { 
 let c = HSVtoRGB(Math.random(), 1.0, 1.0); 
 c.r *= 0.15; 
 c.g *= 0.15; 
 c.b *= 0.15; 
 return c; 
 } 
  
 function HSVtoRGB(h, s, v) { 
 let r, g, b, i, f, p, q, t; 
 i = Math.floor(h * 6); 
 f = h * 6 - i; 
 p = v * (1 - s); 
 q = v * (1 - f * s); 
 t = v * (1 - (1 - f) * s); 
  
 switch (i % 6) { 
 case 0: r = v, g = t, b = p; break; 
 case 1: r = q, g = v, b = p; break; 
 case 2: r = p, g = v, b = t; break; 
 case 3: r = p, g = q, b = v; break; 
 case 4: r = t, g = p, b = v; break; 
 case 5: r = v, g = p, b = q; break; 
 } 
  
 return { 
 r, 
 g, 
 b 
 }; 
 } 
  
 // Utility functions 
 function wrap(value, min, max) { 
 let range = max - min; 
 if (range == 0) return min; 
 return (value - min) % range + min; 
 } 
  
 function getResolution(resolution) { 
 let aspectRatio = gl.drawingBufferWidth / gl.drawingBufferHeight; 
 if (aspectRatio < 1) 
 aspectRatio = 1.0 / aspectRatio; 
  
 let min = Math.round(resolution); 
 let max = Math.round(resolution * aspectRatio); 
  
 if (gl.drawingBufferWidth > gl.drawingBufferHeight) 
 return { width: max, height: min }; 
 else 
 return { width: min, height: max }; 
 } 
  
 function scaleByPixelRatio(input) { 
 let pixelRatio = window.devicePixelRatio || 1; 
 return Math.floor(input * pixelRatio); 
 } 
  
 function hashCode(s) { 
 if (s.length == 0) return 0; 
 let hash = 0; 
 for (let i = 0; i < s.length; i++) { 
 hash = (hash << 5) - hash + s.charCodeAt(i); 
 hash |= 0; // Convert to 32bit integer 
 } 
 return hash; 
 } 
  
 // Start the animation 
 update(); 
  
 // Add some initial splats to make it interesting 
 setTimeout(() => { 
 for (let i = 0; i < 5; i++) { 
 const color = generateColor(); 
 color.r *= 10.0; 
 color.g *= 10.0; 
 color.b *= 10.0; 
 const x = Math.random(); 
 const y = Math.random(); 
 const dx = 1000 * (Math.random() - 0.5); 
 const dy = 1000 * (Math.random() - 0.5); 
 splat(x, y, dx, dy, color); 
 } 
 }, 100); 
  
 // Handle window resize 
 window.addEventListener('resize', () => { 
 resizeCanvas(); 
 }); 
 }; 
 })(); 
 </script> 
 </div> 
 <!-- End of JavaScript for WebGL Fluid Background Effect --> 
 </script> 
 <!-- JAVASCRIPT SECTION END --> 
 </div> 

WebGL Fluid Background Effect

This widget creates an interactive fluid simulation that responds to mouse/touch movements with dynamic, colorful particle animations. The effect runs using WebGL for hardware-accelerated rendering and sits as an overlay above your web content.

Implementation

The WebGL Fluid Effect creates a canvas that covers the entire viewport and renders a real-time physics-based fluid simulation. When users interact with the page, the fluid responds with realistic movement and color transitions. The effect uses high-performance WebGL shaders for smooth animation even on complex pages.

HTML Structure

The widget uses a minimal HTML structure:

  • A main container div#webgl-fluid-background that holds everything
  • A canvas element canvas#fluid where the WebGL rendering occurs
  • Internal styling and JavaScript for self-contained functionality
CSS Styling

The CSS handles positioning and interaction:

  • Uses position: fixed with top: 0; left: 0; to cover the entire viewport
  • Sets z-index: 99999 to position above all other page content
  • Applies pointer-events: none to allow clicks to pass through to elements underneath
  • Optionally uses mix-blend-mode: screen for enhanced visual integration with content
JavaScript Functionality

The JavaScript handles the complex WebGL implementation:

  • WebGL Context: Sets up a WebGL rendering context with appropriate parameters
  • Shader Programs: Implements multiple shader programs for:
    • Fluid dynamics physics simulation
    • Particle rendering and coloring
    • Velocity, pressure, and curl calculations
  • Interaction Handling: Captures mouse/touch events to influence the fluid simulation
  • Animation Loop: Uses requestAnimationFrame for smooth rendering
  • Responsive Design: Automatically adjusts to window resizing
  • Performance Optimization: Implements resolution scaling for different devices
Blend Mode Toggle

The effect includes a configurable blend mode:

  • Controlled through the blendModeActive variable in JavaScript
  • When active, applies mix-blend-mode: screen for a light-blending effect
  • When inactive, renders as a standard overlay without color blending
  • Toggle by changing the variable from true to false
Customization Options

You can customize various aspects of the fluid simulation:

  • Resolution: Adjust SIM_RESOLUTION (lower for better performance, higher for better quality)
  • Speed: Modify VELOCITY_DISSIPATION to change how quickly the fluid movement decays
  • Color Intensity: Change DENSITY_DISSIPATION to control color persistence
  • Effect Strength: Adjust SPLAT_FORCE to change how strongly mouse movements affect the fluid
  • Visual Style: Toggle SHADING for different lighting effects
  • Z-Index: Change the z-index value to position the effect at different layers
Browser Compatibility

The widget supports most modern browsers:

  • Uses WebGL 2 with fallback to WebGL 1 for older browsers
  • Automatically detects and adapts to browser capabilities
  • Supports both desktop and mobile devices with touch detection
  • Adjusts performance settings based on device capabilities
  • Works across Chrome, Firefox, Safari, and Edge
Integration Tips

When adding to your website:

  • Place the code in a global container that's not restricted by parent element dimensions
  • For Duda sites, add as an HTML widget directly to the page structure
  • Consider reducing SIM_RESOLUTION on pages with heavy content
  • Test on mobile devices to ensure performance meets expectations
  • Adjust the z-index if certain elements need to appear below the effect
Resource Details:
WebGL Fluid Background Effect creates an interactive, colorful fluid simulation that floats above your website content. The effect responds to mouse/touch movements with dynamic particle animations that flow naturally across the screen. Features customizable blend modes, responsive design, and click-through functionality so users can interact with your site normally. Implemented with high-performance WebGL shaders for smooth animation on any device. Simple HTML/CSS/JS implementation with adjustable settings for simulation quality, color intensity, and effect positioning.

Last Updated:

April 12th 2025

Category:

Image Display

Need Help?

Join Slack

Callum Wells
Callum Wells
i
Creator Credits
We always strive to credit creators as accurately as possible. While similar concepts might appear online, we aim to provide proper and respectful attribution.
A black padlock with a keyhole on a white background.

NO-ACCESS

Only for Desktop Users

This page is only available on desktop.

Please Log in on a desktop to gain access.