// Vertex Shader
const vsText = `#version 300 es
in vec4 aPosition;
in vec2 aUv;

out vec2 outUv;

void main() {
    gl_Position = aPosition;
    outUv = aUv;
}
`;

// Constants Shader
const constantsShader = `
#define AIR 0.0
#define SMOKE 1.0
#define WATER 2.0
#define LAVA 3.0
#define SAND 4.0
#define GLITTER 5.0
#define STONE 6.0
#define WALL 7.0
#define ICE 8.0

const vec3 bgColor = pow(vec3(31, 34, 36) / 255.0, vec3(2));
const float ambientTemperature = 10.0;
const float smokeCoolingRate = 1.0;
const float lavaCoolingRate = 2.0;
const float smokeTemperature = 500.0;
const float lavaTemperature = 10000.0;
const float stoneMeltingPoint = 1200.0;
const float sandMeltingPoint = 800.0;
const float waterFreezingPoint = 0.0;
const float waterBoilingPoint = 100.0;
const float iceTemperature = -10.0;
const float temperatureTransferRate = 0.01;
`;

// Common Shader Functions
const commonShader = `
const float EPSILON = 1e-4;
const float PI = acos(-1.);
const float TAU = PI * 2.0;

float safeacos(float x) { return acos(clamp(x, -1.0, 1.0)); }

float saturate(float x) { return clamp(x, 0., 1.); }
vec2 saturate(vec2 x) { return clamp(x, vec2(0), vec2(1)); }
vec3 saturate(vec3 x) { return clamp(x, vec3(0), vec3(1)); }

float sqr(float x) { return x*x; }
vec2 sqr(vec2 x) { return x*x; }
vec3 sqr(vec3 x) { return x*x; }

float luminance(vec3 col) { return dot(col, vec3(0.2126729, 0.7151522, 0.0721750)); }

mat2 rot2D(float a) {
    float c = cos(a);
    float s = sin(a);
    return mat2(c, s, -s, c);
}

float smin(float d1, float d2, float k) {
    float h = clamp(0.5 + 0.5*(d2-d1)/k, 0.0, 1.0);
    return mix(d2, d1, h) - k*h*(1.0-h);
}

float smax(float d1, float d2, float k) {
    float h = clamp(0.5 - 0.5*(d2-d1)/k, 0.0, 1.0);
    return mix(d2, d1, h) + k*h*(1.0-h);
}

vec3 palette(float t) {
    vec3 a = vec3(0.5);
    vec3 b = vec3(0.5);
    vec3 c = vec3(1.0, 1.0, 1.0);
    vec3 d = vec3(0.0, 0.33, 0.67);
    return a + b * cos(TAU * (c * t + d));
}

float hash12(vec2 p) {
    vec3 p3 = fract(vec3(p.xyx) * .1031);
    p3 += dot(p3, p3.yzx + 33.33);
    return fract((p3.x + p3.y) * p3.z);
}

float hash13(vec3 p3) {
    p3 = fract(p3 * .1031);
    p3 += dot(p3, p3.zyx + 31.32);
    return fract((p3.x + p3.y) * p3.z);
}

vec2 hash22(vec2 p) {
    vec3 p3 = fract(vec3(p.xyx) * vec3(.1031, .1030, .0973));
    p3 += dot(p3, p3.yzx+33.33);
    return fract((p3.xx+p3.yz)*p3.zy);
}

vec2 hash23(vec3 p3) {
    p3 = fract(p3 * vec3(.1031, .1030, .0973));
    p3 += dot(p3, p3.yzx+33.33);
    return fract((p3.xx+p3.yz)*p3.zy);
}

vec3 hash33(vec3 p3) {
    p3 = fract(p3 * vec3(.1031, .1030, .0973));
    p3 += dot(p3, p3.yxz+33.33);
    return fract((p3.xxy + p3.yxx)*p3.zyx);
}

vec4 hash43(vec3 p) {
    vec4 p4 = fract(vec4(p.xyzx) * vec4(.1031, .1030, .0973, .1099));
    p4 += dot(p4, p4.wzxy+33.33);
    return fract((p4.xxyz+p4.yzzw)*p4.zywx);
}

vec3 RGBtoHCV(vec3 RGB) {
    vec4 P = (RGB.g < RGB.b) ? vec4(RGB.bg, -1.0, 2.0/3.0) : vec4(RGB.gb, 0.0, -1.0/3.0);
    vec4 Q = (RGB.r < P.x) ? vec4(P.xyw, RGB.r) : vec4(RGB.r, P.yzx);
    float C = Q.x - min(Q.w, Q.y);
    float H = abs((Q.w - Q.y) / (6.0 * C + EPSILON) + Q.z);
    return vec3(H, C, Q.x);
}

vec3 RGBtoHSV(vec3 RGB) {
    vec3 HCV = RGBtoHCV(RGB);
    float S = HCV.y / (HCV.z + EPSILON);
    return vec3(HCV.x, S, HCV.z);
}

vec3 RGBtoHSL(vec3 RGB) {
    vec3 HCV = RGBtoHCV(RGB);
    float L = HCV.z - HCV.y * 0.5;
    float S = HCV.y / (1.0 - abs(L * 2.0 - 1.0) + EPSILON);
    return vec3(HCV.x, S, L);
}

vec3 HUEtoRGB(float H) {
    float R = abs(H * 6.0 - 3.0) - 1.0;
    float G = 2.0 - abs(H * 6.0 - 2.0);
    float B = 2.0 - abs(H * 6.0 - 4.0);
    return saturate(vec3(R,G,B));
}

vec3 HSVtoRGB(vec3 HSV) {
    vec3 RGB = HUEtoRGB(HSV.x);
    return ((RGB - 1.0) * HSV.y + 1.0) * HSV.z;
}

vec3 HSLtoRGB(vec3 HSL) {
    vec3 RGB = HUEtoRGB(HSL.x);
    float C = (1.0 - abs(2.0 * HSL.z - 1.0)) * HSL.y;
    return (RGB - 0.5) * C + HSL.z;
}

vec3 sRGBToLinear(vec3 col) {
    return mix(pow((col + 0.055) / 1.055, vec3(2.4)), col / 12.92, lessThan(col, vec3(0.04045)));
}

vec3 linearTosRGB(vec3 col) {
    return mix(1.055 * pow(col, vec3(1.0 / 2.4)) - 0.055, col * 12.92, lessThan(col, vec3(0.0031308)));
}
`;

// Main Fragment Shader
const fsText = `#version 300 es
precision highp float;

uniform vec2 resolution;
uniform float time;
uniform int frame;
uniform vec4 mouse;
uniform int materialType;
uniform float brushRadius;
uniform float gravity; // New uniform for gravity
uniform sampler2D tex;

in vec2 outUv;
out vec4 fragColor;

${constantsShader}
${commonShader}

ivec2 getOffset(int frame) {
    int i = frame % 4;
    if (i == 0) return ivec2(0, 0);
    else if (i == 1) return ivec2(1, 1);
    else if (i == 2) return ivec2(0, 1);
    return ivec2(1, 0);
}

vec4 getData(ivec2 p) {
    if (p.x < 0 || p.y < 0 || p.x >= int(resolution.x) || p.y >= int(resolution.y))
        return vec4(ambientTemperature, 0.0, 0.0, WALL);
    return texelFetch(tex, p, 0);
}

vec4 createParticle(float id) {
    float temperature = ambientTemperature;
    float velocityY = 0.0; // Initialize vertical velocity

    if (id == SMOKE) {
        temperature = smokeTemperature;
        velocityY = 1.0; // Upward initial velocity for smoke
    }
    else if (id == LAVA) {
        temperature = lavaTemperature;
        velocityY = -0.5; // Downward initial velocity for lava
    }
    else if (id == ICE) {
        temperature = iceTemperature;
        velocityY = -1.0; // Downward initial velocity for ice
    }
    else if (id == SAND || id == WATER) {
        velocityY = -1.0; // Downward initial velocity for sand and water
    }

    return vec4(temperature, velocityY, 0.0, id);
}

void main() {
    // Initial frame setup
    if (frame == 0) {
        float r = hash12(gl_FragCoord.xy);
        float id = AIR;
        if (r < 0.15) id = SAND;
        else if (r < 0.25) id = SMOKE;

        fragColor = createParticle(id);
        return;
    }

    // Handle mouse input
    if (mouse.x > 0.0) {
        float d = length(gl_FragCoord.xy - mouse.xy);
        if (d < brushRadius) {
            fragColor = createParticle(float(materialType));
            return;
        }
    }

    // Main simulation logic
    ivec2 offset = getOffset(frame);
    ivec2 fc = ivec2(gl_FragCoord.xy) + offset;
    ivec2 p = (fc / 2) * 2 - offset;
    ivec2 xy = fc % 2;
    int i = xy.x + xy.y * 2;

    vec4 t00 = getData(p);
    vec4 t10 = getData(p + ivec2(1, 0));
    vec4 t01 = getData(p + ivec2(0, 1));
    vec4 t11 = getData(p + ivec2(1, 1));

    // Apply gravity and update velocities
    if (t00.a != WALL && t00.a != AIR) {
        // Update velocity based on gravity
        t00.g -= gravity * 0.016; // Assuming 60fps, deltaTime = 1/60 ≈ 0.016

        // Apply material-specific behavior
        if (t00.a == SMOKE) {
            // Smoke rises, so it resists gravity
            t00.g += gravity * 0.032;
        } else if (t00.a == LAVA) {
            // Lava is affected by gravity but also has some upward force due to heat
            t00.g += gravity * 0.008 * (t00.r / lavaTemperature);
        }

        // Clamp velocity to prevent excessive speeds
        t00.g = clamp(t00.g, -10.0, 10.0);
    }

    // Temperature calculations
    vec4 neighbors[4] = vec4[4](t00, t10, t01, t11);
    for (int idx = 0; idx < 4; idx++) {
        float tempDiff = neighbors[idx].r - t00.r;
        neighbors[idx].r -= tempDiff * temperatureTransferRate;
        t00.r += tempDiff * temperatureTransferRate;
    }

    // Material-specific temperature changes
    if (t00.a == SMOKE) t00.r -= smokeCoolingRate;
    else if (t00.a == LAVA) t00.r -= lavaCoolingRate;

    // Phase transitions
    if (t00.a == STONE && t00.r > stoneMeltingPoint) t00 = createParticle(LAVA);
    else if (t00.a == SAND && t00.r > sandMeltingPoint) t00 = createParticle(STONE);
    else if (t00.a == WATER && t00.r > waterBoilingPoint) t00 = createParticle(SMOKE);
    else if (t00.a == WATER && t00.r <= waterFreezingPoint) t00 = createParticle(ICE);
    else if (t00.a == LAVA && t00.r <= ambientTemperature + 50.0) t00 = createParticle(STONE);
    else if (t00.a == ICE && t00.r > waterFreezingPoint) t00 = createParticle(WATER);
    else if (t00.a == SMOKE && t00.r <= ambientTemperature + 10.0) t00 = createParticle(AIR);

    // Temperature limits
    t00.r = clamp(t00.r, -100.0, 1500.0);

    fragColor = i == 0 ? t00 :
                i == 1 ? t10 :
                i == 2 ? t01 : t11;
}
`;

// JFA Initialization Shader
const jfaInitShaderText = `#version 300 es
precision highp float;

uniform vec2 resolution;
uniform sampler2D dataTex;

${constantsShader}

layout(location = 0) out vec4 fragColorR;
layout(location = 1) out vec4 fragColorG;
layout(location = 2) out vec4 fragColorB;

void main() {
    vec2 uv = gl_FragCoord.xy / resolution;
    vec4 data = texture(dataTex, uv);

    fragColorR = vec4(-1, -1, 0, 0);
    fragColorG = vec4(-1, -1, 0, 0);
    fragColorB = vec4(-1, -1, 0, 0);

    if (data.a <= LAVA || data.a == GLITTER) {
        fragColorR.xy = gl_FragCoord.xy;
        fragColorG.xy = gl_FragCoord.xy;
        fragColorB.xy = gl_FragCoord.xy;
    }

    if (data.a == SMOKE) {
        fragColorR.w = fragColorG.w = fragColorB.w = 6.0;
    } else if (data.a == WATER) {
        fragColorR.w = 9.0;
        fragColorG.w = 6.0;
        fragColorB.w = 4.0;
    } else if (data.a == LAVA) {
        fragColorR.w = 0.0;
        fragColorG.w = 11.0;
        fragColorB.w = 14.0;
    } else if (data.a == GLITTER) {
        fragColorR.w = 4.0 - data.r * 4.0;
        fragColorG.w = 4.0 - data.g * 4.0;
        fragColorB.w = 4.0 - data.b * 4.0;
    }
}
`;

// JFA Main Shader
const jfaShaderText = `#version 300 es
precision highp float;

uniform float stepSize;
uniform vec2 resolution;
uniform sampler2D texR;
uniform sampler2D texG;
uniform sampler2D texB;

uniform int passCount;
uniform int passIndex;

layout(location = 0) out vec4 fragColorR;
layout(location = 1) out vec4 fragColorG;
layout(location = 2) out vec4 fragColorB;

float distanceFunc(vec2 a, vec2 b)
{
    return abs(a.x - b.x) + abs(a.y - b.y);
    //return max(abs(a.x - b.x), abs(a.y - b.y));
    //return distance(a, b);
}

void main()
{
    vec2 fc = gl_FragCoord.xy;

    vec4 bestR = vec4(0,0,1e3,0);
    vec4 bestG = vec4(0,0,1e3,0);
    vec4 bestB = vec4(0,0,1e3,0);

    for (int x = -1; x <= 1; x++)
    {
        for (int y = -1; y <= 1; y++)
        {
            vec2 p = fc + vec2(x, y) * stepSize;

            vec4 dataR = texture(texR, p / resolution);
            vec4 dataG = texture(texG, p / resolution);
            vec4 dataB = texture(texB, p / resolution);

            if (dataR.xy != vec2(-1) && dataR.xy == clamp(dataR.xy, vec2(0.5), resolution-0.5))
            {
                float dist = distanceFunc(fc, dataR.xy) + dataR.w;
                if (dist < bestR.z)
                {
                    bestR = dataR;
                    bestR.z = dist;
                }
            }
            if (dataG.xy != vec2(-1) && dataG.xy == clamp(dataG.xy, vec2(0.5), resolution-0.5))
            {
                float dist = distanceFunc(fc, dataG.xy) + dataG.w;
                if (dist < bestG.z)
                {
                    bestG = dataG;
                    bestG.z = dist;
                }
            }
            if (dataB.xy != vec2(-1) && dataB.xy == clamp(dataB.xy, vec2(0.5), resolution-0.5))
            {
                float dist = distanceFunc(fc, dataB.xy) + dataB.w;
                if (dist < bestB.z)
                {
                    bestB = dataB;
                    bestB.z = dist;
                }
            }
        }
    }

    fragColorR = vec4(bestR.xy, bestR.z != 1e3 ? bestR.z : 1e3, bestR.w);
    fragColorG = vec4(bestG.xy, bestG.z != 1e3 ? bestG.z : 1e3, bestG.w);
    fragColorB = vec4(bestB.xy, bestB.z != 1e3 ? bestB.z : 1e3, bestB.w);

    if (passIndex == passCount - 1)
    {
        if (bestR.xy == vec2(-1))
            fragColorR.z = 1e3;
        if (bestG.xy == vec2(-1))
            fragColorG.z = 1e3;
        if (bestB.xy == vec2(-1))
            fragColorB.z = 1e3;
    }
}
`;

// Render Shader Text
const renderShaderText = `#version 300 es
precision highp float;

uniform vec2 texResolution;
uniform float texScale;
uniform vec2 resolution;
uniform sampler2D tex;
uniform sampler2D shadowTexR;
uniform sampler2D shadowTexG;
uniform sampler2D shadowTexB;
uniform float scale;
uniform float time;  // Added this line

${constantsShader}
${commonShader}  // Added this line

out vec4 fragColor;

vec2 getCoordsAA(vec2 uv) {
    float w = 1.0;
    vec2 fl = floor(uv + 0.5);
    vec2 fr = fract(uv + 0.5);
    vec2 aa = fwidth(uv) * w * 0.5;
    fr = smoothstep(0.5 - aa, 0.5 + aa, fr);
    return fl + fr - 0.5;
}

// Removed duplicate linearTosRGB function definition here

vec3 getColor(float materialId, float temperature) {
    vec3 color = vec3(0.0);

    if (materialId == AIR) {
        color = bgColor;
    }
    else if (materialId == SMOKE) {
        color = vec3(0.2, 0.2, 0.2);
    }
    else if (materialId == WATER) {
        color = vec3(0.0, 0.3, 0.8);
    }
    else if (materialId == LAVA) {
        float t = clamp((temperature - 500.0) / 500.0, 0.0, 1.0);
        color = mix(vec3(1.0, 0.5, 0.0), vec3(1.0, 1.0, 0.0), t);
    }
    else if (materialId == SAND) {
        color = vec3(0.86, 0.62, 0.27);
    }
    else if (materialId == GLITTER) {
        color = palette(fract(time * 0.1));  // Now using corrected palette function
    }
    else if (materialId == STONE) {
        color = vec3(0.5, 0.5, 0.5);
    }
    else if (materialId == WALL) {
        color = bgColor * 0.5;
    }
    else if (materialId == ICE) {
        color = vec3(0.8, 0.9, 1.0);
    }

    // Apply temperature effect
    if (temperature > ambientTemperature) {
        float t = clamp((temperature - ambientTemperature) / 1000.0, 0.0, 1.0);
        color = mix(color, vec3(1.0, 0.5, 0.0), t);
    }
    else if (temperature < ambientTemperature) {
        float t = clamp((ambientTemperature - temperature) / 100.0, 0.0, 1.0);
        color = mix(color, vec3(0.5, 0.6, 1.0), t);
    }

    return color;
}

void main() {
    vec2 uv = gl_FragCoord.xy / (texResolution * texScale);

    uv -= 0.5;
    uv *= scale;
    uv += 0.5;

    vec2 fc = uv * texResolution;

    vec4 data = texture(tex, getCoordsAA(fc) / texResolution);
    vec4 dataUp = texture(tex, getCoordsAA(fc + vec2(0, 1)) / texResolution);
    vec4 dataDown = texture(tex, getCoordsAA(fc - vec2(0, 1)) / texResolution);

    float hig = float(data.a > dataUp.a);
    float dropSha = 1.0 - float(data.a > dataDown.a);

    vec3 color = getColor(data.a, data.r);

    vec4 shaDataR = texture(shadowTexR, uv);
    vec4 shaDataG = texture(shadowTexG, uv);
    vec4 shaDataB = texture(shadowTexB, uv);

    float shaR = shaDataR.xy != vec2(-1) ? shaDataR.z : 16.0;
    float shaG = shaDataG.xy != vec2(-1) ? shaDataG.z : 16.0;
    float shaB = shaDataB.xy != vec2(-1) ? shaDataB.z : 16.0;

    vec3 sha = clamp(1.0 - vec3(shaR, shaG, shaB) / 16.0, vec3(0.0), vec3(1.0));
    sha *= sha;

    color *= 0.5 * max(hig, dropSha) + 0.5;
    color *= sha * 1.0 + 0.2;
    color += color * 0.4 * hig;

    fragColor = vec4(linearTosRGB(color), 1);
}
`;

// Blit Shader (if needed)
const fsBlitText = `#version 300 es
precision highp float;

uniform vec2 resolution;
uniform sampler2D tex;

out vec4 fragColor;

void main() {
    vec2 uv = gl_FragCoord.xy / resolution;

    fragColor = vec4(1.0 - texture(tex, uv).rgb, 1);
}
`;

// Main.js
(function() {

	function createShader(gl, type, source) {
					const shader = gl.createShader(type);
					gl.shaderSource(shader, source);
					gl.compileShader(shader);
					const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
	
					if (!success) {
									console.log(gl.getShaderInfoLog(shader));
									gl.deleteShader(shader);
									return undefined;
					}
					return shader;
	}
	
	function createProgram(gl, vertShader, fragShader) {
					const program = gl.createProgram();
					gl.attachShader(program, vertShader);
					gl.attachShader(program, fragShader);
					gl.linkProgram(program);
	
					const success = gl.getProgramParameter(program, gl.LINK_STATUS);
					if (!success) {
									console.log(gl.getProgramInfoLog(program));
									gl.deleteProgram(program);
									return undefined;
					}
					return program;
	}
	
	function createProgramFromString(gl, vertexShaderString, fragmentShaderString) {
	
					const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderString);
					const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderString);
	
					return createProgram(gl, vertexShader, fragmentShader);
	}
	
	function resizeCanvasToDisplaySize(canvas, pixelRatio) {
					pixelRatio = pixelRatio || 1;
					const width  = canvas.clientWidth * pixelRatio | 0;
					const height = canvas.clientHeight * pixelRatio | 0;
					if (canvas.width !== width ||  canvas.height !== height) {
									canvas.width = width;
									canvas.height = height;
									return true;
					}
					return false;
	}
	
	function mod(x, y) {
					return x - y * Math.floor(x / y);
	}
	
	function fract(x) {
					return x - Math.floor(x);
	}
	
	function clamp(x, xmin, xmax) {
					return Math.min(Math.max(x, xmin), xmax);
	}
	
	function hashi(x)
	{
					x ^= x >> 16;
					x *= 0x7feb352d;
					x ^= x >> 15;
					x *= 0x846ca68b;
					x ^= x >> 16;
					return x;
	}
	
	function hashf(p)
	{
					p = fract(p * .1031);
					p *= p + 323.333;
					p *= p + p;
					return fract(p);
	}
	
	const title = document.getElementById("title");
	const githubIcon = document.getElementById("github-icon");
	
	const urlParams = new URLSearchParams(window.location.search);
	
	if (!urlParams.has("header")) {
					title.style.display = "none";
	} else {
					githubIcon.style.display = "none";
	}
	
	const container = document.getElementById("canvas-container");
	const canvas = document.getElementById("main-canvas");
	const gl = canvas.getContext("webgl2");
	
	if (!gl) {
					console.error("WebGL2 context not available!");
	}
	
	if (!gl.getExtension('EXT_color_buffer_float')) {
					console.error('need EXT_color_buffer_float');
	}
	
	if (!gl.getExtension('OES_texture_float_linear')) {
					console.error('need OES_texture_float_linear');
	}
	
	const program = createProgramFromString(gl, vsText, fsText);
	const blitProgram = createProgramFromString(gl, vsText, renderShaderText);
	const jfaShadowProgram = createProgramFromString(gl, vsText, jfaShaderText);
	const jfaInitProgram = createProgramFromString(gl, vsText, jfaInitShaderText);
	
	const quad = [
					-1.0, -1.0, 0.0, 0.0,
					-1.0,  1.0, 0.0, 1.0,
					 1.0,  1.0, 1.0, 1.0,
					 1.0, -1.0, 1.0, 0.0
	];
	
	const posBuffer = gl.createBuffer();
	
	gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer);
	gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(quad), gl.STATIC_DRAW);
	
	const vao = gl.createVertexArray();
	
	gl.bindVertexArray(vao);
	
	const posAttribLoc = gl.getAttribLocation(program, "aPosition");
	const uvAttribLoc = gl.getAttribLocation(program, "aUv");
	
	gl.vertexAttribPointer(posAttribLoc, 2, gl.FLOAT, false, 16, 0);
	gl.enableVertexAttribArray(posAttribLoc);
	
	gl.vertexAttribPointer(uvAttribLoc, 2, gl.FLOAT, false, 16, 8);
	gl.enableVertexAttribArray(uvAttribLoc);
	
	resizeCanvasToDisplaySize(gl.canvas);
	gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
	
	const SCALE = 10;
	
	let bufferWidth = Math.ceil(gl.canvas.width / SCALE);
	let bufferHeight = Math.ceil(gl.canvas.height / SCALE);
	
	const fbo = [null, null];
	const tex = [null, null];
	
	for (let i = 0; i < 2; i++) {
	
					tex[i] = gl.createTexture();
					gl.bindTexture(gl.TEXTURE_2D, tex[i]);
					gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16F, bufferWidth, bufferHeight, 0, gl.RGBA, gl.FLOAT, null);
	
					gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
					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);
	
					fbo[i] = gl.createFramebuffer();
					gl.bindFramebuffer(gl.FRAMEBUFFER, fbo[i]);
					gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex[i], 0);
	}
	
	let shadowSwap = 0;
	const shadowFbo = [null, null];
	const shadowTexR = [null, null];
	const shadowTexG = [null, null];
	const shadowTexB = [null, null];
	
	for (let i = 0; i < 2; i++) {
	
					shadowTexR[i] = gl.createTexture();
					gl.bindTexture(gl.TEXTURE_2D, shadowTexR[i]);
					gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16F, bufferWidth, bufferHeight, 0, gl.RGBA, gl.FLOAT, null);
	
					gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
					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);
	
					shadowTexG[i] = gl.createTexture();
					gl.bindTexture(gl.TEXTURE_2D, shadowTexG[i]);
					gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16F, bufferWidth, bufferHeight, 0, gl.RGBA, gl.FLOAT, null);
	
					gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
					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);
	
					shadowTexB[i] = gl.createTexture();
					gl.bindTexture(gl.TEXTURE_2D, shadowTexB[i]);
					gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16F, bufferWidth, bufferHeight, 0, gl.RGBA, gl.FLOAT, null);
	
					gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
					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);
	
					shadowFbo[i] = gl.createFramebuffer();
					gl.bindFramebuffer(gl.FRAMEBUFFER, shadowFbo[i]);
					gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, shadowTexR[i], 0);
					gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT1, gl.TEXTURE_2D, shadowTexG[i], 0);
					gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT2, gl.TEXTURE_2D, shadowTexB[i], 0);
	
					gl.drawBuffers([gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1, gl.COLOR_ATTACHMENT2]);
	}
	
	console.log(`Screen size: ${gl.canvas.width}, ${gl.canvas.height}`);
	console.log(`Buffer size: ${bufferWidth}, ${bufferHeight}`);
	
	let mouse = {
					x: -1, y: -1,
					px: -1, py: -1,
					pressed: false
	};
	
	let materialType = 5;
	let brushRadius = 1;
	let minBrushRadius = 0.5;
	let maxBrushRadius = 5;
	let paused = false;
	
	function mouseMoved(event) {
					let x = event.clientX != null ? event.clientX : event.touches[0].clientX;
					let y = event.clientY != null ? event.clientY : event.touches[0].clientY;
					let rect = canvas.getBoundingClientRect();
					mouse.x = x - rect.left;
					mouse.y = y - rect.top;
	}
	
	function mousePressed(event) {
					let x = event.clientX != null ? event.clientX : event.touches[0].clientX;
					let y = event.clientY != null ? event.clientY : event.touches[0].clientY;
					let rect = canvas.getBoundingClientRect();
					mouse.x = x - rect.left;
					mouse.y = y - rect.top;
					mouse.px = mouse.x;
					mouse.py = mouse.y;
					mouse.pressed = true;
	}
	
	function mouseReleased(event) {
					mouse.pressed = false;
	}
	
	container.addEventListener("mousemove", mouseMoved);
	container.addEventListener("touchmove", mouseMoved);
	
	container.addEventListener("mousedown", mousePressed);
	container.addEventListener("touchstart", mousePressed);
	
	container.addEventListener("mouseup", mouseReleased);
	container.addEventListener("touchend", mouseReleased);
	
	container.addEventListener("mouseleave", mouseReleased);
	container.addEventListener("touchleave", mouseReleased);
	
	const buttons = document.querySelectorAll("#material-buttons ul li a");
	
	buttons[materialType].classList.add("selected");
	
	function setMaterialType(type) {
					if (buttons[materialType].classList.contains("selected")) {
									buttons[materialType].classList.remove("selected");
					}
					materialType = clamp(type, 0, 8);
					if (!buttons[materialType].classList.contains("selected")) {
									buttons[materialType].classList.add("selected");
					}
	}
	
	buttons.forEach((button, idx) => {
					button.addEventListener("click", (e) => {
									e.preventDefault();
									setMaterialType(parseInt(button.getAttribute("data-type")));
					});
	});
	
	document.addEventListener("keydown", (e) => {
					if (e.keyCode >= 48 && e.keyCode <= 57) {
									setMaterialType(e.keyCode - 48);
					} else if (e.keyCode >= 96 && e.keyCode <= 105) {
									setMaterialType(e.keyCode - 96);
					}
	
					if (e.keyCode == 189 || e.keyCode == 109) {
									brushRadius = clamp(brushRadius - 0.5, minBrushRadius, maxBrushRadius);
					} else if (e.keyCode == 187 || e.keyCode == 107) {
									brushRadius = clamp(brushRadius + 0.5, minBrushRadius, maxBrushRadius);
					}
	
					if (e.keyCode == 32 || e.keyCode == 27) {
									paused = !paused;
					}
	});
	
	document.addEventListener("keyup", (e) => {
	});
	
	let swap = 0;
	let frames = 0;
	let demo = true;
	let seed = Date.now();
	
	function render(time) {
	
					let resized = false;
					if (resizeCanvasToDisplaySize(gl.canvas)) {
									resized = true;
	
									gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
	
									bufferWidth = Math.ceil(gl.canvas.width / SCALE);
									bufferHeight = Math.ceil(gl.canvas.height / SCALE);
	
									for (let i = 0; i < 2; i++)
									{
													gl.bindFramebuffer(gl.FRAMEBUFFER, fbo[i]);
	
													const newTex = gl.createTexture();
													gl.bindTexture(gl.TEXTURE_2D, newTex);
													gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16F, bufferWidth, bufferHeight, 0, gl.RGBA, gl.FLOAT, null);
	
													gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
													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.bindTexture(gl.TEXTURE_2D, newTex);
													gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, 0, 0, bufferWidth, bufferHeight);
													
													gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, newTex, 0);
	
													gl.deleteTexture(tex[i]);
													tex[i] = newTex;
									}
	
									for (let i = 0; i < 2; i++)
									{
													gl.bindTexture(gl.TEXTURE_2D, shadowTexR[i]);
													gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16F, bufferWidth, bufferHeight, 0, gl.RGBA, gl.FLOAT, null);
									
													gl.bindTexture(gl.TEXTURE_2D, shadowTexG[i]);
													gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16F, bufferWidth, bufferHeight, 0, gl.RGBA, gl.FLOAT, null);
									
													gl.bindTexture(gl.TEXTURE_2D, shadowTexB[i]);
													gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16F, bufferWidth, bufferHeight, 0, gl.RGBA, gl.FLOAT, null);
									}
					}
	
					if (!paused || mouse.pressed) {
	
									gl.useProgram(program);
									gl.bindVertexArray(vao);
	
									const timeLoc = gl.getUniformLocation(program, "time");
									const frameLoc = gl.getUniformLocation(program, "frame");
									const resLoc = gl.getUniformLocation(program, "resolution");
									const texLoc = gl.getUniformLocation(program, "tex");
									const mouseLoc = gl.getUniformLocation(program, "mouse");
									const mouseButtonsLoc = gl.getUniformLocation(program, "mouseButtons");
									const materialTypeLoc = gl.getUniformLocation(program, "materialType");
									const brushRadiusLoc = gl.getUniformLocation(program, "brushRadius");
	
									let mx = (mouse.x / gl.canvas.width) * bufferWidth;
									let my = (1.0 - mouse.y / gl.canvas.height) * bufferHeight;
									let mpx = (mouse.px / gl.canvas.width) * bufferWidth;
									let mpy = (1.0 - mouse.py / gl.canvas.height) * bufferHeight;
	
									let pressed = false;
	
									if (mouse.pressed)
													demo = false;
	
									if (demo && frames > 40) {
													if (frames < 1200) {
																	let id = Math.ceil(frames / 40);
																	let type = hashf(id + seed);
																	let x = hashf(id + seed + 3.3);
																	x += Math.sin(frames * 0.1) * 0.03;
																	let y = hashf(id + seed + 5.3);
																	type = Math.floor(type * 5.0 + 2.0);
																	mx = clamp(x, 0.0, 1.0) * bufferWidth;
																	my = (0.8 + y * 0.1) * bufferHeight;
																	mpx = mx;
																	mpy = my;
																	pressed = true;
																	setMaterialType(type);
													} else {
																	demo = false;
													}
									}
	
									gl.uniform1f(timeLoc, time * 0.001);
									gl.uniform2f(resLoc, bufferWidth, bufferHeight);
									gl.uniform1i(materialTypeLoc, materialType);
									gl.uniform1f(brushRadiusLoc, brushRadius);
	
									if (mouse.pressed || pressed)
													gl.uniform4f(mouseLoc, mx, my, mpx, mpy);
									else
													gl.uniform4f(mouseLoc, -mx, -my, -mpx, -mpy);
	
									const PASSES = 3;
									for (let i = 0; i < PASSES; i++) {
													gl.bindFramebuffer(gl.FRAMEBUFFER, fbo[swap]);
													gl.viewport(0, 0, bufferWidth, bufferHeight);
	
													gl.clearColor(0, 0, 0, 0);
													gl.clear(gl.COLOR_BUFFER_BIT);
	
													gl.uniform1i(frameLoc, frames * PASSES + i);
	
													gl.uniform1i(texLoc, 0);
													gl.activeTexture(gl.TEXTURE0);
													gl.bindTexture(gl.TEXTURE_2D, tex[1 - swap]);
	
													gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
	
													swap = 1 - swap;
									}
	
									frames++;
					}
	
					if (paused && !resized && !mouse.pressed) {
									requestAnimationFrame(render);
									return;
					}
	
					{
									shadowSwap = 0;
	
									gl.bindFramebuffer(gl.FRAMEBUFFER, shadowFbo[shadowSwap]);
									gl.viewport(0, 0, bufferWidth, bufferHeight);
	
									gl.clearColor(0, 0, 0, 0);
									gl.clear(gl.COLOR_BUFFER_BIT);
	
									gl.useProgram(jfaInitProgram);
									gl.bindVertexArray(vao);
	
									const resLoc = gl.getUniformLocation(jfaInitProgram, "resolution");
									const texLoc = gl.getUniformLocation(jfaInitProgram, "dataTex");
	
									gl.uniform2f(resLoc, bufferWidth, bufferHeight);
	
									gl.uniform1i(texLoc, 0);
									gl.activeTexture(gl.TEXTURE0);
									gl.bindTexture(gl.TEXTURE_2D, tex[swap]);
	
									gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
	
									shadowSwap = 1 - shadowSwap;
					}
	
					{
									const JFA_PASSES = 4;
	
									gl.useProgram(jfaShadowProgram);
	
									const resLoc = gl.getUniformLocation(jfaShadowProgram, "resolution");
									const texLoc = gl.getUniformLocation(jfaShadowProgram, "texR");
									const texGLoc = gl.getUniformLocation(jfaShadowProgram, "texG");
									const texBLoc = gl.getUniformLocation(jfaShadowProgram, "texB");
									const stepSizeLoc = gl.getUniformLocation(jfaShadowProgram, "stepSize");
									const passCountLoc = gl.getUniformLocation(jfaShadowProgram, "passCount");
									const passIdxLoc = gl.getUniformLocation(jfaShadowProgram, "passIndex");
	
									gl.uniform2f(resLoc, bufferWidth, bufferHeight);
									gl.uniform1i(texLoc, 0);
									gl.uniform1i(texGLoc, 1);
									gl.uniform1i(texBLoc, 2);
									gl.uniform1i(passCountLoc, JFA_PASSES);
	
									for (let i = 0; i < JFA_PASSES; i++) {
	
													gl.bindFramebuffer(gl.FRAMEBUFFER, shadowFbo[shadowSwap]);
													gl.viewport(0, 0, bufferWidth, bufferHeight);
	
													gl.clearColor(0, 0, 0, 0);
													gl.clear(gl.COLOR_BUFFER_BIT);
	
													const stepSize = Math.pow(2, JFA_PASSES - i - 1);
	
													gl.uniform1f(stepSizeLoc, stepSize);
													gl.uniform1i(passIdxLoc, i);
	
													gl.activeTexture(gl.TEXTURE0);
													gl.bindTexture(gl.TEXTURE_2D, shadowTexR[1 - shadowSwap]);
													gl.activeTexture(gl.TEXTURE1);
													gl.bindTexture(gl.TEXTURE_2D, shadowTexG[1 - shadowSwap]);
													gl.activeTexture(gl.TEXTURE2);
													gl.bindTexture(gl.TEXTURE_2D, shadowTexB[1 - shadowSwap]);
	
													gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
	
													shadowSwap = 1 - shadowSwap;
									}
					}
	
					gl.bindFramebuffer(gl.FRAMEBUFFER, null);
					gl.drawBuffers([gl.BACK]);
					gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
					
					gl.clearColor(0, 0, 0, 1);
					gl.clear(gl.COLOR_BUFFER_BIT);
	
					gl.useProgram(blitProgram);
					gl.bindVertexArray(vao);
	
					const timeLoc = gl.getUniformLocation(blitProgram, "time");
					const resLoc = gl.getUniformLocation(blitProgram, "resolution");
					const texLoc = gl.getUniformLocation(blitProgram, "tex");
					const shadowTexLoc = gl.getUniformLocation(blitProgram, "shadowTexR");
					const shadowTexGLoc = gl.getUniformLocation(blitProgram, "shadowTexG");
					const shadowTexBLoc = gl.getUniformLocation(blitProgram, "shadowTexB");
					const scaleLoc = gl.getUniformLocation(blitProgram, "scale");
					const texResLoc = gl.getUniformLocation(blitProgram, "texResolution");
					const texScaleLoc = gl.getUniformLocation(blitProgram, "texScale");
	
					gl.uniform1f(timeLoc, time * 0.001);
					gl.uniform2f(resLoc, gl.canvas.width, gl.canvas.height);
					gl.uniform2f(texResLoc, bufferWidth, bufferHeight);
					gl.uniform1f(texScaleLoc, SCALE);
					gl.uniform1f(scaleLoc, 1.0);
	
					gl.uniform1i(texLoc, 0);
					gl.activeTexture(gl.TEXTURE0);
					gl.bindTexture(gl.TEXTURE_2D, tex[swap]);
	
					gl.uniform1i(shadowTexLoc, 1);
					gl.uniform1i(shadowTexGLoc, 2);
					gl.uniform1i(shadowTexBLoc, 3);
					gl.activeTexture(gl.TEXTURE1);
					gl.bindTexture(gl.TEXTURE_2D, shadowTexR[1- shadowSwap]);
					gl.activeTexture(gl.TEXTURE2);
					gl.bindTexture(gl.TEXTURE_2D, shadowTexG[1- shadowSwap]);
					gl.activeTexture(gl.TEXTURE3);
					gl.bindTexture(gl.TEXTURE_2D, shadowTexB[1- shadowSwap]);
	
					gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
	
					mouse.px = mouse.x;
					mouse.py = mouse.y;
	
					requestAnimationFrame(render);
	}
	
	requestAnimationFrame(render);
	
	})();


<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<title>Falling Sand CA</title>
	<link rel="stylesheet" href="assets/css/main.css" />
</head>
<body>
	<div id="hero">
		<div id="canvas-container">
			<canvas id="main-canvas"></canvas>
			<script src="assets/js/shaders.js"></script>
			<script src="assets/js/main.js" defer></script>
			<h1 id="title"><span style="color:rgb(160, 160, 161);">Gelami</span>Salami</h1>
		</div>
		<div id="material-buttons">
			<ul>
				<li><a href="" data-type="0" title="Eraser (Key 0)"></a></li>
				<li><a href="" data-type="1" title="Smoke (Key 1)"></a></li>
				<li><a href="" data-type="2" title="Water (Key 2)"></a></li>
				<li><a href="" data-type="3" title="Lava (Key 3)"></a></li>
				<li><a href="" data-type="4" title="Sand (Key 4)"></a></li>
				<li><a href="" data-type="5" title="Glitter (Key 5)"></a></li>
				<li><a href="" data-type="6" title="Stone (Key 6)"></a></li>
				<li><a href="" data-type="7" title="Wall (Key 7)"></a></li>
			</ul>
			<!-- <div></div>
			<div></div> -->
			<!-- <input class="vertical" type="range" min="0.5" max="5" value="1" step="0.5"/> -->
		</div>
		<a id="github-icon" href="https://github.com/GelamiSalami/GPU-Falling-Sand-CA"></a>
	</div>
</body>
</html>

The HTML and Main.js are outdated.. can you update them to accompany the changes? (like a gui that tells the element's name and temperature, ice button, etc.... make all the scripts updated), give all the full scripts with nothing missing