{
  "name": "Super Wobble",
  "type": "fixed-intro",
  "duration": 1.371,
  "curve": "Linear None",
  "passes": [
    {
      "name": "Super Wobble",
      "inputs": [
        "default"
      ],
      "glsl": "

varying vec2 sourceTextureCoordinate;

uniform vec2 outputSize;
uniform float maxZoom;
uniform float progress;

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

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;

  // scale progress so the animation fills the whole duration
  float prog = progress * zoomOutEnd;

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

  if (prog > rotStart && prog < rotEnd){
    float t = (prog - 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 progress;
  float2 output_size;
  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);
}

fragment fragmentOut fragmentShader(fragmentIn in [[stage_in]],
 constant Uniforms & uniforms [[buffer(1)]],
 DefaultInputs defaultInputs) {

  float2 uv = in.sourceTextureCoordinate;
  float2 ratio = uniforms.output_size.xy / uniforms.output_size.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;

  float prog = uniforms.progress * zoomOutEnd;

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

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

  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
        }
      }
    }
  ]
}
