{
  "name": "Super Wobble",
  "duration": 3,
  "curve": "Linear None",
  "passes": [
    {
      "name": "Super Wobble",
      "inputs": [
        "default"
      ],
      "glsl": "precision highp float;

varying vec2 textureCoordinate;
varying vec2 sourceTextureCoordinate;

// default uniforms for all filters
uniform float time;
uniform float strength;
uniform vec2 outputSize; // size of the output fbo
uniform vec2 inputSize; // size of the original default input texture
uniform vec2 tex0Size; // size of tex0 for this pass

uniform float maxZoom;

// default uniforms for transitions and video effects
uniform float progress; // normalized 0-1

// default uniforms for video effects
uniform float duration; // in seconds
uniform float clipDuration; // in seconds

const float PI  = 3.14159265359;

vec2 rotate(vec2 p, float a){
  float s = sin(a);
  float c = cos(a);
  return vec2(c * p.x - s * p.y, s * p.x + c * p.y);
}

float easeOutExpo(float t){
  return (t >= 1.0) ? 1.0 : 1.0 - pow(2.0, -10.0 * t);
}

// effect uniforms
void main() {

  vec2 uv = sourceTextureCoordinate;
  vec2 ratio = outputSize.xy / outputSize.y;
  vec2 center = vec2(0.5,0.5);

  const float zoomInEnd = 0.1257;
  const float rotStart = 0.06285;
  const float rotEnd = 0.354;
  const float zoomOutEnd = 0.457;
  float scale;
  float angle = 0.0;

  if (progress < zoomInEnd){
    float t = smoothstep(0.0,1.0, progress / zoomInEnd);
    scale = mix(1.0, maxZoom, t);
  } else if (progress < rotEnd) {
    scale = maxZoom;
  } else if (progress < zoomOutEnd){
    float t = smoothstep(0.0,1.0, (progress - rotEnd) / (zoomOutEnd - rotEnd));
    scale = mix(maxZoom, 1.0, easeOutExpo(t));
  }else {
    scale = 1.0;
  }

  if (progress > rotStart && progress < rotEnd){
    float t = (progress - rotStart) / (rotEnd - rotStart);
    float osc = sin(t * PI * 12.0);
    angle = radians(30.0) * osc;
  }

  uv = uv - center;
  uv *= ratio;
  uv = rotate(uv, angle);
  uv /= ratio;
  uv = uv / scale + center;

  vec4 color = sampleInput(uv);
  gl_FragColor = toOutputFormat(color);
}
",
"metal": "
        using namespace metal;

        struct fragmentIn {
            float2 sourceTextureCoordinate [[user(locn1)]];
        };

        struct fragmentOut {
            float4 _gl_FragColor [[color(0)]];
        };

        typedef struct {
            float4x4 content_transform;
            float4x4 texture_transform;
            float strength;
            float progress;
            float time_sec;
            float duration_sec;
            float clip_duration_sec;
            float2 inputSize;
            float2 outputSize;
            float2 tex0Size;
            float maxZoom;
        } Uniforms;

        constant float PI = 3.14159265359;

        float2 rotate(float2 p, float a) {
            float s = sin(a);
            float c = cos(a);
            return float2(c * p.x - s * p.y, s * p.x + c * p.y);
        }

        float easeOutExpo(float t) {
            return (t >= 1.0) ? 1.0 : 1.0 - pow(2.0, -10.0 * t);
        }

        // effect uniforms
        fragment fragmentOut fragmentShader(fragmentIn in [[stage_in]],
                                          constant Uniforms & uniforms [[buffer(1)]],
                                          DefaultInputs defaultInputs) {
            float2 uv = in.sourceTextureCoordinate;
            float2 ratio = uniforms.outputSize.xy / uniforms.outputSize.y;
            float2 center = float2(0.5,0.5);

            float zoomInEnd = 0.1257;
            float rotStart = 0.06285;
            float rotEnd = 0.354;
            float zoomOutEnd = 0.457;
            float scale;
            float angle = 0.0;

            if (uniforms.progress < zoomInEnd) {
                float t = smoothstep(0.0,1.0, uniforms.progress / zoomInEnd);
                scale = mix(1.0, uniforms.maxZoom, t);
            } else if (uniforms.progress < rotEnd) {
                scale = uniforms.maxZoom;
            } else if (uniforms.progress < zoomOutEnd) {
                float t = smoothstep(0.0,1.0, (uniforms.progress - rotEnd) / (zoomOutEnd - rotEnd));
                scale = mix(uniforms.maxZoom, 1.0, easeOutExpo(t));
            } else {
                scale = 1.0;
            }

            if (uniforms.progress > rotStart && uniforms.progress < rotEnd) {
                float t = (uniforms.progress - rotStart) / (rotEnd - rotStart);
                float osc = sin(t * PI * 12.0);
                angle = (30.0 * PI / 180.0) * osc; // Convert degrees to radians manually
            }

            uv = uv - center;
            uv *= ratio;
            uv = rotate(uv, angle);
            uv /= ratio;
            uv = uv / scale + center;

            float4 color = sampleInput(defaultInputs, uv);
            return {toOutputFormat(color)};
        }
  ",
      "uniforms": {
        "maxZoom": {
          "value": 2
        }
      }
    }
  ]
}
