#version 300 es
precision highp float;

uniform vec2 u_resolution;
uniform float u_time;
uniform vec2 u_pointer;   // 0–1, eased; drifts by itself while nobody is pointing
uniform vec3 u_click;     // xy = last press (0–1), z = seconds since that press
uniform float u_seed;
out vec4 outColor;

// Integer hash: a given seed renders the same work on every GPU.
uint hashu(uint x) {
    x ^= x >> 16; x *= 0x7feb352dU;
    x ^= x >> 15; x *= 0x846ca68bU;
    x ^= x >> 16;
    return x;
}

float seeded(uint k) {
    return float(hashu(uint(u_seed) * 0x9e3779b9U + k)) / 4294967295.0;
}

vec2 toField(vec2 q) {
    return (2.0 * q - 1.0) * u_resolution / min(u_resolution.x, u_resolution.y);
}

float dither(vec2 p) {
    return (fract(52.9829189 * fract(dot(p, vec2(0.06711056, 0.00583715)))) - 0.5) / 255.0;
}

float lattice(vec2 cell) {
    uvec2 c = uvec2(ivec2(cell) + 32768);
    return float(hashu(c.x * 0x8da6b343U ^ hashu(c.y ^ uint(u_seed) * 0x9e3779b9U))) / 4294967295.0;
}

float noise2d(vec2 p) {
    vec2 i = floor(p), f = fract(p);
    f = f * f * (3.0 - 2.0 * f);
    return mix(mix(lattice(i), lattice(i + vec2(1.0, 0.0)), f.x),
               mix(lattice(i + vec2(0.0, 1.0)), lattice(i + 1.0), f.x), f.y);
}

float fbm(vec2 p) {
    float value = 0.0;
    float weight = 0.52;
    mat2 turn = mat2(0.80, -0.60, 0.60, 0.80);
    for (int i = 0; i < 5; i++) {
        value += weight * noise2d(p);
        p = turn * p * 2.03 + 7.1;
        weight *= 0.5;
    }
    return value;
}

void main() {
    vec2 uv = gl_FragCoord.xy / u_resolution.xy;
    vec2 aspect = vec2(u_resolution.x / u_resolution.y, 1.0);
    float scale = 2.6 + 1.2 * seeded(1u);
    vec2 p = (uv - 0.5) * aspect * scale;

    // The pointer is a stone in the current: the flow parts around it.
    vec2 away = p - (u_pointer - 0.5) * aspect * scale;
    p += away * 0.35 * exp(-1.6 * dot(away, away));

    float drift = u_time * 0.035;
    vec2 warp = vec2(fbm(p + drift), fbm(p + vec2(4.1, 1.7) - drift));
    float field = fbm(p + (1.3 + 0.8 * seeded(2u)) * warp + 10.0 * seeded(3u));
    vec2 press = (u_click.xy - 0.5) * aspect * scale;
    float age = u_click.z;
    field += 0.05 * exp(-age) * sin(length(p - press) * 9.0 - age * 6.0) * exp(-0.5 * dot(p - press, p - press));

    // Relief lighting from screen-space slope; contours keep a constant pixel width.
    float slope = fwidth(field);
    vec2 gradient = vec2(dFdx(field), dFdy(field)) * u_resolution.y;
    vec3 normal = normalize(vec3(-0.12 * gradient, 1.0));
    float light = clamp(dot(normal, normalize(vec3(-0.45, 0.6, 0.66))), 0.0, 1.0);

    vec3 abyss = vec3(0.010, 0.035, 0.045);
    vec3 deep = vec3(0.018, 0.11, 0.12);
    vec3 teal = vec3(0.05, 0.40, 0.37);
    vec3 shoal = vec3(0.38, 0.60, 0.50);
    vec3 salt = vec3(0.90, 0.88, 0.76);
    vec3 color = mix(abyss, deep, smoothstep(0.26, 0.42, field));
    color = mix(color, teal, smoothstep(0.44, 0.58, field));
    color = mix(color, shoal, smoothstep(0.62, 0.74, field));
    color *= 0.8 + 0.45 * light;

    float isobath = abs(fract(field * 16.0) - 0.5) / max(slope * 16.0, 1e-4);
    float lines = (1.0 - smoothstep(0.6, 1.4, isobath)) * smoothstep(0.42, 0.55, field);
    float foam = 1.0 - smoothstep(slope * 0.8, slope * 2.6, abs(field - 0.6));
    color = mix(color, salt, max(lines * 0.16, foam * 0.85));
    color *= 1.0 - 0.3 * smoothstep(0.55, 1.0, length((uv - 0.5) * aspect));
    outColor = vec4(color + dither(gl_FragCoord.xy), 1.0);
}
