#version 410 core

uniform float time; // в секундах
uniform vec2 resolution; // разрешение экрана (в пикселях)
uniform vec3 spectrum; // спектр: X=Низкие, Y=Средние, Z=Высокие
uniform sampler2D pass_texture; // Предыдущий кадр 

#define iTime time
#define PI 3.14159265359

layout(location = 0) out vec4 out_color;

float hash(vec2 p) {
    return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);
}

float hash(float n) { 
    return fract(sin(n) * 43758.5453123); 
}

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

float fbm(vec2 p) {
    float value = 0.0;
    float amplitude = 0.5;
    float frequency = 1.0;
    for (int i = 0; i < 4; i++) {
        value += amplitude * noise(p * frequency);
        frequency *= 2.0;
        amplitude *= 0.5;
    }
    return value;
}

mat2 rot(float a) {
    return mat2(cos(a), -sin(a), sin(a), cos(a));
}

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

vec2 crtCurve(vec2 uv) {
    uv = uv * 2.0 - 1.0;
    vec2 distortion = vec2(0.12, 0.15); 
    uv += uv * (uv.yx * uv.yx) * distortion;
    return uv * 0.5 + 0.5;
}

float drawLine(float dist, float thickness, float glowRadius) {
    float beam = smoothstep(thickness, 0.0, dist);
    float glow = smoothstep(glowRadius, 0.0, dist) * 0.5;
    return beam + glow;
}

float getGrid(vec2 uv, float bassMultiplier) {
    float perspective = 0.5 / (uv.y + 0.7); 
    if (uv.y < -0.68) return 0.0;
    vec2 gridUV = vec2(uv.x * perspective, perspective + time * 1.5 + bassMultiplier * 0.8);
    float density = 4.0 + bassMultiplier * 2.0;
    vec2 lines = abs(fract(gridUV * density - 0.5) - 0.5) / (fwidth(gridUV) * density);
    float grid = 1.0 - min(lines.x, lines.y);
    float fade = smoothstep(-0.7, 0.2, uv.y);
    return clamp(grid, 0.0, 1.0) * fade * (0.2 + bassMultiplier * 0.3);
}

float matrixChar(vec2 st, float charId) {
    vec2 grid = floor(st * 4.0);
    if (grid.x < 0.0 || grid.x > 3.0 || grid.y < 0.0 || grid.y > 3.0) return 0.0;
    float h = hash(vec2(grid + charId * 13.0));
    return step(0.5, h);
}

float map(vec3 p) {
    float bass = pow(spectrum.x, 1.5) * 4.0;
    float mids = pow(spectrum.y, 1.5) * 5.0;
    p.z -= time * 5.0; 
    p.xy *= matrixRot(p.z * 0.12 + mids * 0.4); 
    float a = atan(p.y, p.x);
    float r = length(p.xy);
    float tunnelRadius = 3.2 + bass * 1.2;
    float sectors = 6.0;
    float zRepeat = mod(p.z, 2.5) - 1.25;
    float dTunnel = tunnelRadius - r;
    dTunnel -= sin(a * sectors) * 0.4 * (1.0 - step(0.8, abs(zRepeat)));
    vec3 cPos = p;
    cPos.xy *= matrixRot(time * 0.3);
    cPos = mod(cPos, 5.0) - 2.5;
    float dCubes = length(max(abs(cPos) - vec3(0.3 + bass * 0.4), 0.0));
    return min(dTunnel, dCubes);
}


// --- ШЕЙДЕР 1 ---
vec3 renderShader1(vec2 fragCoord) {
    float bass   = spectrum.x * 1.5;
    float mids   = spectrum.y * 1.5;
    float highs  = spectrum.z * 1.5;
    float volume = (bass + mids + highs) / 3.0;
    
    vec2 uvN = fragCoord / resolution.xy;
    uvN = crtCurve(uvN);
    
    if (uvN.x < 0.0 || uvN.x > 1.0 || uvN.y < 0.0 || uvN.y > 1.0) {
        return vec3(0.0);
    }

    float glitchLine = sin(uvN.y * 10.0 + time * 5.0) * sin(uvN.y * 3.0 - time * 10.0);
    float glitchThreshold = step(0.93 - highs * 0.1, hash(vec2(floor(time * 12.0), 1.0))); 
    float xOffset = glitchLine * glitchThreshold * (0.015 + highs * 0.03);
    
    float blockY = floor(uvN.y * 25.0); 
    float blockNoise = hash(vec2(blockY, floor(time * 16.0)));
    float tearTrigger = step(0.96 - (mids + highs) * 0.08, blockNoise);
    float tearOffset = (hash(vec2(blockY, 2.0)) - 0.5) * (0.08 + bass * 0.1) * tearTrigger;
    
    float totalXOffset = xOffset + tearOffset;
    float scanlines = sin(uvN.y * resolution.y * 1.5 + time * bass * 2.0) * (0.06 + bass * 0.02);
    
    vec2 glitchUV = uvN + vec2(totalXOffset, 0.0);
    vec2 uv = (glitchUV * 2.0 - 1.0) * (resolution / resolution.y);

    float perspective = 0.5 / (uv.y + 0.7); 
    vec2 gridUV = vec2(uv.x * perspective, perspective - time * 0.8 - (bass * 0.5));
    vec2 gridLines = abs(fract(gridUV - 0.5) - 0.5) / fwidth(gridUV);
    float lineFactor = min(gridLines.x, gridLines.y);
    float gridMask = 1.0 - min(lineFactor, 1.0);
    
    float gridIntensity = (0.02 + mids * 0.1 + bass * 0.2) * gridMask;
    gridIntensity *= smoothstep(-0.6, 0.4, uv.y);
    
    float sceneGray = 0.01 + gridIntensity;
    float baseRadius = 0.23 + bass * 0.12;
    float thickness = 0.012 + highs * 0.005; 
    const int TRAIL_STEPS = 8; 
    float trailIntensity = 0.06; 
    float ringGlowTotal = 0.0;

    for (int i = 0; i < TRAIL_STEPS; i++) {
        float t = time - float(i) * trailIntensity;
        float ageFactor = float(i) / float(TRAIL_STEPS);
        float weight = 1.0 - ageFactor;
        
        vec2 distortedUV = uv;
        if (i > 0) {
            float distortStrength = ageFactor * (0.1 + mids * 0.3); 
            distortedUV.x += sin(uv.y * 12.0 + t * 8.0) * distortStrength;
            distortedUV.y += cos(uv.x * 12.0 + t * 8.0) * distortStrength;
        }
        
        vec2 center = vec2(cos(t * 2.2 + volume) * 0.4, sin(t * 1.8 + volume) * 0.25);
        float radius = baseRadius + sin(t * 4.0) * 0.05;
        float dist = length(distortedUV - center);
        float distToRing = abs(dist - radius) - thickness;
        
        float glowSize = 0.02 + bass * 0.01; 
        float glow = glowSize / max(distToRing, 0.001);
        glow *= weight;
        
        float ringBrightness = mix(0.75, 0.35 * (1.0 + mids), ageFactor);
        ringGlowTotal += ringBrightness * glow;
    }
    
    sceneGray += ringGlowTotal;
    sceneGray = 1.0 - exp(-sceneGray * 1.6);

    float finalGray = sceneGray * (1.0 + volume * 0.05);
    float noiseVal = (hash(uvN + time) - 0.5) * (0.04 + highs * 0.1);
    finalGray += noiseVal;
    finalGray -= scanlines;

    float vignette = uvN.x * uvN.y * (1.0 - uvN.x) * (1.0 - uvN.y);
    vignette = clamp(pow(16.0 * vignette, 0.35), 0.0, 1.0); 
    finalGray *= vignette;

    float edgeCutoff = smoothstep(0.0, 0.01, uvN.x) * smoothstep(1.0, 0.99, uvN.x) *
                       smoothstep(0.0, 0.01, uvN.y) * smoothstep(1.0, 0.99, uvN.y);
    finalGray *= edgeCutoff;

    finalGray = clamp(finalGray, 0.0, 1.0);
    finalGray = pow(finalGray, 1.4); 
    finalGray = smoothstep(0.05, 0.95, finalGray); 

    return vec3(finalGray);
}

// --- ШЕЙДЕР 2 ---
vec3 renderShader2(vec2 fragCoord) {
    vec2 uv = (fragCoord * 2.0 - resolution.xy) / resolution.y;
    float bass  = pow(spectrum.x, 1.5) * 4.0;
    float mids  = pow(spectrum.y, 1.5) * 5.0;
    float highs = pow(spectrum.z, 1.5) * 6.0;

    vec2 centerUV = uv - vec2(0.0, 0.1);
    float radius = length(centerUV);
    float angle = atan(centerUV.y, centerUV.x);
    float grayScaleValue = 0.0;

    grayScaleValue = max(grayScaleValue, getGrid(uv, bass));

    float baseRadius = 0.35 + sin(time * 2.0) * 0.05 + bass * 0.05;
    float wave = baseRadius + sin(angle * (10.0 + floor(bass)) + time * 6.0) * (mids * 0.15);
    wave += sin(angle * 180.0 - time * 40.0) * (highs * 0.025);
    
    float radarCircle = drawLine(abs(radius - wave), 0.005 + highs * 0.005, 0.03 + mids * 0.05);
    grayScaleValue = max(grayScaleValue, radarCircle);

    const int numMarkers = 32;
    float tails = 0.0;

    for (int j = 0; j < numMarkers; j++) {
        float markerAngle = (float(j) / float(numMarkers)) * 2.0 * PI;
        float angleDiff = abs(angle - markerAngle);
        if (angleDiff > PI) angleDiff = 2.0 * PI - angleDiff;
        float segmentAudio = mix(bass, mix(mids, highs, hash(float(j))), hash(float(j + 3)));
        float startRadius = wave;
        float endRadius = startRadius + 0.02 + segmentAudio * 0.65;

        if (radius >= startRadius && radius <= endRadius) {
            float beamWidth = 0.01 + highs * 0.005;
            float distToBeam = angleDiff * radius;
            float fadeAlongTail = 1.0 - smoothstep(startRadius, endRadius, radius);
            float phosphorGhost = 0.4 + 0.6 * sin(time * 8.0 - radius * 15.0 + float(j));
            float beam = smoothstep(beamWidth, 0.0, distToBeam);
            float glow = smoothstep(beamWidth * 8.0, 0.0, distToBeam) * 0.6;
            tails += (beam + glow) * fadeAlongTail * phosphorGhost;
        }
    }
    grayScaleValue = max(grayScaleValue, tails);

    float screenAfterglow = smoothstep(0.8, 0.0, radius) * (bass * 0.2 + mids * 0.1);
    screenAfterglow *= 0.7 + 0.3 * sin(time * 12.0);
    grayScaleValue += screenAfterglow;

    float scanlines = sin(fragCoord.y * 1.5) * 0.2 + 0.8;
    grayScaleValue *= scanlines;

    float glitchTrigger = step(0.990 - highs * 0.015, hash(floor(uv.y * 40.0) + time * 5.0));
    grayScaleValue += glitchTrigger * highs * 0.4;

    vec2 d = fragCoord / resolution.xy;
    float vignette = 0.1 + 0.9 * pow(16.0 * d.x * d.y * (1.0 - d.x) * (1.0 - d.y), 0.35);
    grayScaleValue *= vignette;

    return vec3(clamp(grayScaleValue, 0.0, 1.0));
}

// --- ШЕЙДЕР 3 ---
vec3 renderShader3(vec2 fragCoord) {
    vec2 uv = (fragCoord * 2.0 - resolution.xy) / min(resolution.x, resolution.y);
    vec2 screenUV = fragCoord / resolution.xy;

    float bass  = clamp(spectrum.x, 0.0, 2.0);
    float mids  = clamp(spectrum.y, 0.0, 2.0);
    float highs = clamp(spectrum.z, 0.0, 2.0);

    float currentRadius = length(uv);
    float currentFrame = 0.0;

    if (currentRadius >= 0.001) {
        vec2 dir = uv / currentRadius;
        float R = 0.35 + mids * 0.15;
        float r_wheel = 0.12 + mids * 0.1;
        float d = 0.20 + highs * 0.15;
        float k = R / r_wheel;
        
        float t_rot = time * (0.2 + mids * 0.4);
        float cosT = cos(t_rot), sinT = sin(t_rot);
        vec2 rotatedDir = vec2(dir.x * cosT - dir.y * sinT, dir.x * sinT + dir.y * cosT);
        
        float spiroX = (R + r_wheel) * rotatedDir.x - d * cos(k * (rotatedDir.x + time * 0.02));
        float spiroY = (R + r_wheel) * rotatedDir.y - d * sin(k * (rotatedDir.y + time * 0.02));
        float targetRadius = length(vec2(spiroX, spiroY));
        
        vec2 noiseUV = uv * (8.0 + bass * 35.0) + vec2(time * 0.2, -time * 0.1);
        float fractalNoise = fbm(noiseUV) * (0.02 + bass * 0.3);
        targetRadius += fractalNoise;
        
        float dist = abs(currentRadius - targetRadius);
        float thickness = 0.003 + (mids * 0.01) + (fractalNoise * 0.05);
        float line = smoothstep(thickness, 0.0, dist);
        float glow = exp(-dist * (30.0 - highs * 15.0)) * 0.25;
        
        currentFrame = clamp(line + glow, 0.0, 1.0);
    }

    vec2 trailUV = (screenUV - 0.5) * (0.992 - bass * 0.005) + 0.5;
    vec3 prevFrameColor = texture(pass_texture, trailUV).rgb;
    
    float fadeRate = 0.91 + (highs * 0.04);
    fadeRate = clamp(fadeRate, 0.85, 0.97);
    
    vec3 finalColor = max(vec3(currentFrame), prevFrameColor * fadeRate);
    return smoothstep(0.02, 1.0, finalColor);
}


// --- ШЕЙДЕР 4 ---
vec3 renderShader4(vec2 fragCoord) {
    vec2 uv = (fragCoord - 0.5 * resolution.xy) / resolution.y;
    float bass = pow(spectrum.x, 1.5) * 4.0;
    float highs = pow(spectrum.z, 1.5) * 6.0;
    float noiseSeed = time;

    float glitchTrigger = step(0.6, highs) * hash(vec2(floor(uv.y * 30.0), noiseSeed));
    if (glitchTrigger > 0.7) {
        uv.x += (hash(vec2(uv.y, noiseSeed)) - 0.5) * 0.15 * highs;
    }

    vec3 ro = vec3(0.0, 0.0, 4.0);
    vec3 rd = normalize(vec3(uv, -1.2));
    float d = 0.0, t = 0.0, maxDist = 35.0;
    
    for(int i = 0; i < 75; i++) {
        vec3 p = ro + rd * t;
        d = map(p);
        if(d < 0.001 || t > maxDist) break;
        t += d;
    }

    vec3 color = vec3(0.0);
    bool isBackground = (t >= maxDist);
    
    if(!isBackground) {

        float depth = 1.0 - (t / maxDist);
        float ao = float(75 - int(t * 2.2)) / 75.0;
        float rawLight = depth * ao;
        float noirEdge = smoothstep(0.3, 0.35, rawLight);
        color = vec3(noirEdge);
        color += vec3(smoothstep(0.1, 0.5, highs) * 0.4 * depth);
    }

    if (isBackground || color.r < 0.1) {
        vec2 matrixUV = fragCoord / 14.0;
        float colId = floor(matrixUV.x);
        float speed = (0.5 + hash(vec2(colId, 1.0)) * 0.5) * (time * 15.0 + highs * 20.0);
        matrixUV.y += speed;
        
        float rowId = floor(matrixUV.y);
        vec2 charSubUV = fract(matrixUV);
        float charId = hash(vec2(colId, rowId + floor(time * 10.0)));
        float glyph = matrixChar(charSubUV, charId);
        float trail = fract(-matrixUV.y / 20.0 + hash(vec2(colId, 4.0)));
        trail = smoothstep(0.1, 0.9, trail);
        float matrixIntensity = glyph * trail;

        if (isBackground) {
            color = vec3(matrixIntensity * 0.8);
        } else {
            color += vec3(matrixIntensity * 0.35);
        }
    }


    float grain = hash(fragCoord + noiseSeed);
    color += vec3(grain * (0.07 + highs * 0.22));
    
    float scratch = step(0.996, hash(vec2(uv.x + floor(time * 12.0) * 0.1, 1.0)));
    color += vec3(scratch * 0.25 * hash(vec2(uv.y, noiseSeed)));
    
    float flicker = 1.0 - (hash(vec2(noiseSeed, 0.0)) * 0.12 * (1.0 + bass));
    color *= flicker;
    color *= smoothstep(1.2, 0.4, length(uv));

    return color;
}

========================================

// переход 1
vec3 transitionAnalogStatic(vec2 fragCoord, float p, int nextShaderIdx) {
    float intensity = smoothstep(0.0, 0.5, p) * (1.0 - smoothstep(0.5, 1.0, p));
    vec2 uvN = fragCoord / resolution.xy;
    vec2 distortedCoord = fragCoord;
    
    distortedCoord.x += (hash(vec2(floor(uvN.y * 30.0), time)) - 0.5) * resolution.x * 0.12 * intensity;
    distortedCoord.y += sin(time * 20.0) * resolution.y * 0.015 * intensity;
    
    vec3 colA = (nextShaderIdx == 2) ? renderShader1(distortedCoord) : renderShader2(distortedCoord);
    vec3 colB = (nextShaderIdx == 2) ? renderShader2(distortedCoord) : renderShader3(distortedCoord);
    
    vec3 mixedColor = mix(colA, colB, step(0.5, p));
    float staticNoise = hash(fragCoord + time);
    mixedColor = mix(mixedColor, vec3(staticNoise), intensity * 0.85);
    
    float movingBar = sin(uvN.y * 3.0 - time * 5.0) * 0.5 + 0.5;
    mixedColor -= vec3(step(0.85, movingBar) * intensity * 0.4);
    
    return clamp(mixedColor, 0.0, 1.0);
}

// переход 2
vec3 transitionGlitchFlash(vec2 fragCoord, float p) {
    float intensity = smoothstep(0.0, 0.5, p) * (1.0 - smoothstep(0.5, 1.0, p));
    vec2 uvN = fragCoord / resolution.xy;
    vec2 glitchCoord = fragCoord;
    
    float blockY = floor(uvN.y * 12.0);
    if (hash(vec2(blockY, floor(time * 15.0))) < intensity) {
        glitchCoord.x += (hash(vec2(blockY, time)) - 0.5) * resolution.x * 0.25;
    }
    
    vec3 col3 = renderShader3(glitchCoord);
    vec3 col4 = renderShader4(glitchCoord);
    
    vec3 mixedColor = mix(col3, col4, step(0.5, p));
    vec3 flashColor = mixedColor + vec3(intensity * 1.6);
    
    if (intensity > 0.4 && hash(vec2(floor(time * 24.0), 1.0)) > 0.5) {
        flashColor = 1.0 - flashColor;
    }
    
    float slice = step(0.98 - intensity * 0.05, hash(vec2(uvN.y, time)));
    flashColor += vec3(slice * 0.6);
    
    return clamp(flashColor, 0.0, 1.0);
}

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

    float currentBass  = spectrum.x * 1.5;
    float currentMids  = spectrum.y * 1.5;
    float currentHighs = spectrum.z * 1.5;
    float volume = (currentBass + currentMids + currentHighs) / 3.0;
    float duration = 420.0;
    float totalDuration = duration * 4.0; 
    
    float localTime = mod(time, totalDuration);
    float blendTime = 2.0; 
    vec3 finalColor = vec3(0.0);

    //  Шейдер 1 -> Шейдер 2 
    if (localTime < duration) {
        if (localTime > duration - blendTime) {
            float progress = (localTime - (duration - blendTime)) / blendTime;
            finalColor = transitionAnalogStatic(fragCoord, progress, 2);
        } else {
            finalColor = renderShader1(fragCoord);
        }
    }
    //  Шейдер 2 -> Шейдер 3 
    else if (localTime < duration * 2.0) {
        if (localTime > (duration * 2.0) - blendTime) {
            float progress = (localTime - ((duration * 2.0) - blendTime)) / blendTime;
            finalColor = transitionAnalogStatic(fragCoord, progress, 3);
        } else {
            finalColor = renderShader2(fragCoord);
        }
    }
    // Шейдер 3 -> Шейдер 4 
    else if (localTime < duration * 3.0) {
        if (localTime > (duration * 3.0) - blendTime) {
            float progress = (localTime - ((duration * 3.0) - blendTime)) / blendTime;
            finalColor = transitionGlitchFlash(fragCoord, progress);
        } else {
            finalColor = renderShader3(fragCoord);
        }
    }
    // Шейдер 4 -> Шейдер 1
    else {
        if (localTime > totalDuration - blendTime) {
            float progress = (localTime - (totalDuration - blendTime)) / blendTime;
            finalColor = mix(renderShader4(fragCoord), renderShader1(fragCoord), progress);
        } else {
            finalColor = renderShader4(fragCoord);
        }
    }
    
    out_color = vec4(finalColor, 1.0);
}
