ERROR: 0:55: 'iceTemperature' : undeclared identifier
ERROR: 0:121: 'hash12' : no matching overloaded function found
// 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 = 30.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.001;
`;

// 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;
uniform sampler2D tex;

in vec2 outUv;
out vec4 fragColor;

#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 float ambientTemperature = 30.0;
const float smokeTemperature = 500.0;
const float lavaTemperature = 10000.0;

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

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;

    if (id == SMOKE) {
        temperature = smokeTemperature;
        velocityY = 1.0;
    }
    else if (id == LAVA) {
        temperature = lavaTemperature;
        velocityY = -0.5;
    }
    else if (id == ICE) {
        temperature = iceTemperature;
        velocityY = -1.0;
    }
    else if (id == SAND || id == WATER) {
        velocityY = -1.0;
    }

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

void main() {
    ivec2 coord = ivec2(gl_FragCoord.xy);

    // Handle mouse input
    if (mouse.z > 0.0) { // Check if mouse button is pressed
        if (distance(gl_FragCoord.xy, mouse.xy) < brushRadius) {
            fragColor = createParticle(float(materialType));
            return;
        }
    }

    vec4 currentData = getData(coord);

    // Only process half of the pixels each frame to avoid conflicts
    ivec2 offset = getOffset(frame);
    if ((coord.x + coord.y) % 2 != (offset.x + offset.y) % 2) {
        fragColor = currentData;
        return;
    }

    // Apply material-specific behaviors
    if (currentData.a == WALL || currentData.a == AIR) {
        fragColor = currentData;
        return;
    }

    // Update vertical velocity due to gravity
    currentData.g -= gravity; // Gravity pulls particles downwards

    // Clamp velocity
    currentData.g = clamp(currentData.g, -5.0, 5.0);

    // Calculate the intended new position
    int dy = currentData.g >= 0.0 ? int(floor(currentData.g)) : int(ceil(currentData.g));
    dy = clamp(dy, -1, 1); // Limit movement to one cell per frame

    ivec2 newPos = coord + ivec2(0, dy);

    // Check bounds
    if (newPos.y < 0 || newPos.y >= int(resolution.y)) {
        fragColor = currentData;
        return;
    }

    vec4 targetData = getData(newPos);

    // Movement logic
    if (targetData.a == AIR) {
        // Move particle to new position
        // Since we cannot write to arbitrary positions, we'll swap the data
        fragColor = targetData; // Set current cell to AIR
    } else {
        // Try to move diagonally
        bool moved = false;

        // Randomly choose left or right
        int dir = int(floor(hash12(gl_FragCoord.xy + float(frame)) * 2.0)) * 2 - 1; // -1 or 1

        for (int i = 0; i < 2; i++) {
            ivec2 diagonalPos = coord + ivec2(dir, dy);
            if (diagonalPos.x >= 0 && diagonalPos.x < int(resolution.x) &&
                diagonalPos.y >= 0 && diagonalPos.y < int(resolution.y)) {
                vec4 diagonalData = getData(diagonalPos);
                if (diagonalData.a == AIR) {
                    // Move particle diagonally
                    fragColor = diagonalData; // Set current cell to AIR
                    moved = true;
                    break;
                }
            }
            // Try the other direction
            dir = -dir;
        }

        if (!moved) {
            // Cannot move, stay in place
            fragColor = currentData;
            return;
        }
    }

    // In the next frame, the particle will appear in the new position
}
`;

// 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);
}
`;