#version 330 core
out vec4 FragColor;
in vec2 vUV;
uniform vec3 iResolution;
uniform float iTime;
uniform float uAlpha;
uniform int uNumParticles;
uniform float uGravity;
uniform float uBurstDensity;

// Fireworks - Multi-Burst Particle Explosions
// by Wrennly - https://wrennly.com
// License: MIT

#define COLOR_A      vec3(1.000, 0.420, 0.616)
#define COLOR_B      vec3(0.424, 0.388, 1.000)
#define COLOR_C      vec3(1.000, 0.843, 0.000)

#define TAU 6.28318530718
#define PI  3.14159265
#define NUM_BURSTS    50

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

float stars(vec2 uv) {
  vec2 cell = floor(uv * 30.0);
  vec2 local = fract(uv * 30.0) - 0.5;
  float star = 0.0;
  vec2 starPos = vec2(hash(cell) - 0.5, hash(cell * 1.3) - 0.5) * 0.7;
  float d = length(local - starPos);
  float brightness = hash(cell * 2.7);
  if (brightness > 0.93) {
    star = smoothstep(0.03, 0.0, d) * (brightness - 0.93) / 0.07 * 0.4;
  }
  return star;
}

vec3 fireworkBurst(vec2 uv, float burstIdx, float time, vec3 cA, vec3 cB, vec3 cC) {
  float seed = burstIdx * 13.37;
  float cx = hash1(seed + 1.0) * 1.2 - 0.6;
  float cy = hash1(seed + 2.0) * 0.3 + 0.1;
  vec2 center = vec2(cx, cy);
  float period = 2.5 + hash1(seed + 3.0) * 2.0;
  float timeOffset = hash1(seed + 4.0) * period;
  float phase = fract((time + timeOffset) / period);
  float colorSel = hash1(seed + 5.0);
  vec3 burstColor = colorSel < 0.33 ? cA : (colorSel < 0.66 ? cB : cC);
  vec3 burstColor2 = hash1(seed + 6.0) < 0.5 ? cA : cC;
  vec3 result = vec3(0.0);

  if (phase < 0.15) {
    float launchProgress = phase / 0.15;
    vec2 launchBase = vec2(cx, -0.55);
    vec2 launchHead = mix(launchBase, center, launchProgress);
    float trailLen = 0.12;
    vec2 trailTail = launchHead - vec2(0.0, trailLen * launchProgress);
    vec2 pa = uv - trailTail;
    vec2 ba = launchHead - trailTail;
    float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
    float d = length(pa - ba * h);
    float trail = exp(-d * 200.0) * launchProgress;
    float headGlow = exp(-length(uv - launchHead) * 80.0) * 1.5;
    result += (burstColor * trail + vec3(1.0) * headGlow) * smoothstep(0.0, 0.02, phase);
  }

  if (phase >= 0.13 && phase < 0.7) {
    float burstPhase = (phase - 0.15) / 0.55;
    burstPhase = clamp(burstPhase, 0.0, 1.0);
    float burstTime = burstPhase * 1.2;
    float fadeOut = 1.0 - smoothstep(0.4, 1.0, burstPhase);
    float flash = exp(-burstPhase * 12.0);
    result += vec3(1.0, 0.95, 0.8) * flash * exp(-length(uv - center) * 20.0);
    for (int i = 0; i < 128; i++) {
      if (i >= uNumParticles) break;
      float fi = float(i);
      float angle = hash1(seed + fi * 0.1 + 10.0) * TAU;
      float spd = 0.3 + hash1(seed + fi * 0.1 + 20.0) * 0.4;
      vec2 dir = vec2(cos(angle), sin(angle)) * spd;
      vec2 particlePos = center + dir * burstTime;
      particlePos.y -= uGravity * burstTime * burstTime;
      float trailT = max(burstTime - 0.08, 0.0);
      vec2 trailPos = center + dir * trailT;
      trailPos.y -= uGravity * trailT * trailT;
      float d = length(uv - particlePos);
      float glow = exp(-d * 100.0) * fadeOut;
      vec2 tpa = uv - trailPos;
      vec2 tba = particlePos - trailPos;
      float th = clamp(dot(tpa, tba) / dot(tba, tba), 0.0, 1.0);
      float td = length(tpa - tba * th);
      float trailGlow = exp(-td * 150.0) * fadeOut * 0.4 * (1.0 - th);
      float sparkle = hash(floor(uv * 300.0 + fi + iTime * 30.0));
      sparkle = smoothstep(0.98, 1.0, sparkle) * exp(-d * 40.0) * fadeOut * 0.8;
      vec3 pColor = mix(burstColor, burstColor2, hash1(seed + fi * 0.1 + 30.0));
      result += pColor * glow + pColor * trailGlow + vec3(1.0, 0.95, 0.9) * sparkle;
    }
  }

  if (phase >= 0.7 && phase < 0.9) {
    float fadeLate = 1.0 - smoothstep(0.7, 0.9, phase);
    float burstTime = 1.2 + (phase - 0.7) * 0.5;
    for (int i = 0; i < 128; i++) {
      if (i >= uNumParticles) break;
      float fi = float(i);
      float angle = hash1(seed + fi * 0.1 + 10.0) * TAU;
      float spd = 0.3 + hash1(seed + fi * 0.1 + 20.0) * 0.4;
      vec2 dir = vec2(cos(angle), sin(angle)) * spd;
      vec2 particlePos = center + dir * burstTime;
      particlePos.y -= uGravity * burstTime * burstTime;
      float d = length(uv - particlePos);
      float glow = exp(-d * 120.0) * fadeLate * 0.3;
      vec3 pColor = mix(burstColor, burstColor2, hash1(seed + fi * 0.1 + 30.0));
      result += pColor * glow;
    }
  }

  return result;
}

void mainImage(out vec4 fragColor, in vec2 fragCoord) {
  vec2 st = fragCoord / iResolution.xy;
  vec2 uv = st - 0.5;
  float aspect = iResolution.x / iResolution.y;
  uv.x *= aspect;
  float t = iTime;
  vec3 color = vec3(0.01, 0.01, 0.025);
  float starVal = stars(st);
  color += vec3(0.7, 0.75, 1.0) * starVal;
  int activeBursts = int(clamp(uBurstDensity * 3.0 + 2.0, 2.0, float(NUM_BURSTS)));
  for (int i = 0; i < NUM_BURSTS; i++) {
    if (i >= activeBursts) break;
    color += fireworkBurst(uv, float(i), t, COLOR_A, COLOR_B, COLOR_C);
  }
  if (uv.y < -0.45) {
    vec2 reflUv = vec2(uv.x, -0.9 - uv.y);
    vec3 refl = vec3(0.0);
    for (int i = 0; i < NUM_BURSTS; i++) {
      if (i >= activeBursts) break;
      refl += fireworkBurst(reflUv, float(i), t, COLOR_A, COLOR_B, COLOR_C);
    }
    float reflFade = smoothstep(-0.45, -0.55, uv.y);
    color += refl * 0.15 * reflFade;
  }
  float vig = 1.0 - length(st - 0.5) * 0.4;
  color *= vig;
  color = color / (color + 0.6) * 1.3;
  fragColor = vec4(color, 1.0);
}

void main()
{
    vec2 fragCoord = vUV * iResolution.xy;
    mainImage(FragColor, fragCoord);
    FragColor.a *= uAlpha;
}
