/* global jQuery, TRX_ADDONS_STORAGE */

(function() {

	"use strict";

	// Settings
	var planeClassPrefix = 'trx_addons_image_effects_on_';		// class prefix of wrappers with image

	var globalCanvas = false;									// true - one canvas for all images is used (twitches when scrolling the page),
																// false - separate canvas for each image is created (smooth scrolling, but WebKit limit: maximum of 16 objects per page)

	var permanentDrawing = globalCanvas || false;				// true - permanent redraw images on canvas,
																// false - redraw only on ready and on hover

	var curtains = null;										// global curtains object (used if globalCanvas is true)

	var $document = jQuery(document);

	var firstLoad = false;

	var sliderInited = false;


	window.addEventListener( 'load', function() {
		if ( typeof trx_addons_apply_filters == 'function' ) {
			globalCanvas = trx_addons_apply_filters( 'trx_addons_filter_image_effects_use_global_canvas', globalCanvas );
			permanentDrawing = globalCanvas || false;
		}
		firstLoad = true;
		create_planes();
	} );

	function create_planes() {

		// not available in the edit mode of Elementor
		if ( typeof window.elementorFrontend !== 'undefined' && elementorFrontend.isEditMode() ) {
			return;
		}
		// exit if a module 'Curtains' is not available
		if ( typeof window.Curtains == 'undefined' ) {
			return;
		}

		// get our plane element
		var planeElements = document.querySelectorAll( '[class*="' + planeClassPrefix + '"]:not(.trx_addons_image_effects_inited)'
														+ ( jQuery('body').hasClass( 'allow_lazy_load' )
															? '.lazyload_inited'
															: '' )
														);

		// exit if no image effects are present on the current page
		if ( planeElements.length === 0 ) return;

		// create global canvas and append it to the body
		if ( globalCanvas ) {
			curtains = create_canvas( document.body );
		}

		// create planes and handle them
		trx_addons_when_images_loaded( jQuery( planeElements ), function() {
			var effect, total = 0;
			for (var i = 0; i < planeElements.length; i++) {
				if ( ! firstLoad && planeElements[i].closest( '.elementor-section-stretched' ) ) continue;
				if ( ! sliderInited && planeElements[i].closest( '.slider-slide' ) ) continue;
				total++;
				if ( ! planeElements[i].classList.contains( 'trx_addons_image_effects_inited' ) && jQuery( planeElements[i] ).parents(':hidden').length === 0 ) {
					effect = get_effect_name( planeElements[i] );
					if ( effect && typeof window['trx_addons_image_effects_callback_' + effect] == 'function' ) {
						window['trx_addons_image_effects_callback_' + effect]( curtains, planeElements[i], i, planeElements.length );
						planeElements[i].classList.add("trx_addons_image_effects_inited");
					}
				}
			}
			// mark body as all planes are loaded
			if ( total === planeElements.length ) {
				document.body.classList.add("trx_addons_image_effects_planes_loaded");
				$document.trigger('action.trx_addons_image_effects_inited', [planeElements]);
			}
		} );
	}

	$document.on( 'action.got_ajax_response', function() {
		if ( firstLoad ) {
			create_planes();
		}
	} );

	$document.on( 'action.init_hidden_elements', function() {
		if ( firstLoad ) {
			create_planes();
		}
	} );

	$document.on( 'action.slider_inited', function() {
		if ( ! sliderInited ) {
			sliderInited = true;
			create_planes();
		}
	} );

	$document.on( 'action.init_lazy_load_elements', function( e, element ) {
		var parent = element.parents('[class*="trx_addons_image_effects_on_"]:not(.trx_addons_image_effects_inited)');
		if ( parent.length > 0 ) {
			parent.addClass('lazyload_inited');
			// Hide and load image
			parent.css({'opacity': 0, 'transition': 'opacity 0s ease'});
			// Re-create canvas elements
			create_planes();
			// Fade in image
			setTimeout(function(){
				// Show loaded image	
				parent.css({'opacity': 1, 'transition': 'opacity 0.3s ease'});
				// Remove styles
				setTimeout(function(){
					parent.css({'opacity': '', 'transition': ''});
				}, 300);
			}, 100);
		}	
	} );

	$document.on('action.before_remove_content action.deactivate_tab', function(e, cont) {
		cont.find( '.trx_addons_image_effects_inited' ).each( function() {
			var $self = jQuery( this ),
				$canvas = $self.find('.trx_addons_image_effects_canvas'),
				curtains = $self.data('curtains');
			if ( ! globalCanvas ) {
				if ( curtains ) {
					curtains.dispose();
					curtains = null;
					$self.removeData( 'curtains' );
				}
			} else {
				var plane = $self.data('curtains-plane');
				if ( plane ) {
					curtains.removePlane( plane );
					plane = null;
					$self.removeData('curtains-plane');
				}
			}
			$canvas.remove();
		});
	});

	$document.on('action.after_add_content action.activate_tab', function(e, cont) {
		cont.find( '.trx_addons_image_effects_inited' ).each( function() {
			jQuery( this )
				.removeClass('trx_addons_image_effects_inited')
				.find( '[id^="trx_addons_image_effects_canvas_"]' ).remove().end()
				.find( '.trx_addons_image_effects_holder' ).removeClass('trx_addons_image_effects_holder').end();
		});
	});


	// Utilities
	//-------------------------------------------

	// Return name of image effect for element
	function get_effect_name( elm ) {
		var name = '';
		for ( var i=0; i < elm.classList.length; i++ ) {
			if ( elm.classList[i].indexOf(planeClassPrefix) === 0 ) {
				name = elm.classList[i].substring( planeClassPrefix.length );
				break;
			}
		}
		return name;
	}

	// Return mouse coordinates relative to the current image
	function get_mouse_position_from_event( e ) {
		var mouse = {};
		// touch event
		if (e.targetTouches) {
			mouse.x = globalCanvas ? e.targetTouches[0].clientX : e.targetTouches[0].layerX;
			mouse.y = globalCanvas ? e.targetTouches[0].clientY : e.targetTouches[0].layerY;

		// mouse event
		} else {
												// In Chrome with custom scroll mouse coordinates in layerXY are incorrect
												// but in Mozilla mouse properties offsetXY always empty (equal to 0)
			mouse.x = globalCanvas ? e.clientX : ( e.offsetX !== 0 || e.offsetY !== 0 ? e.offsetX : e.layerX );
			mouse.y = globalCanvas ? e.clientY : ( e.offsetX !== 0 || e.offsetY !== 0 ? e.offsetY : e.layerY );
		}
		return mouse;
	}

	// Convert our mouse/touch position to coordinates relative to the vertices of the plane
	function mouse_to_plane_coords( plane, mpos ) {
		var w = plane.htmlElement.clientWidth, 	cw = w / 2,
			h = plane.htmlElement.clientHeight,	ch = h / 2;
		return {
				x:  ( mpos.x - cw ) / cw,
				y: -( mpos.y - ch ) / ch
			};
	}

	// Add canvas holder
	var total = 0;
	function create_canvas( item ) {

		// Append canvas holder to the item
		var id = 'trx_addons_image_effects_canvas_'+total++,
			div = document.createElement("div");
		div.setAttribute('id', id);
		div.setAttribute('class', 'trx_addons_image_effects_canvas');
		item.appendChild(div);

		// Set up our WebGL context and append the canvas to our wrapper
		var webGLCurtain = new Curtains({
			watchScroll: globalCanvas,
			premultipliedAlpha: true,	// to avoid gray edge on images in effects 'waves', 'smudge', etc. (who changes the image border geometry)
			container: id	// if not specified - library create own canvas container
		});

		// Handling errors
		webGLCurtain
			.onError(function() {
				// we will add a class to the document body to display original images
				document.body.classList.add("no-curtains", "trx_addons_image_effects_planes_loaded");
			})
			.onContextLost(function() {
				// on context lost, try to restore the context
				webGLCurtain.restoreContext();
			});

		return webGLCurtain;
	}



	// Effect 'Waves'
	//-------------------------------------------
	
	// Create plane.
	// Attention! All callbacks must be in a global scope!
	window.trx_addons_image_effects_callback_waves = function( curtains_global, elm, index, total ) {

		var curtains = curtains_global ? curtains_global : null;

		var waveForceMin = 0,
			waveForceMax = 7;

		var waveFactor = elm.getAttribute('data-image-effect-waves-factor') || 4;

		var mouseIn = false;

		var scaleOnHover = elm.getAttribute('data-image-effect-scale') > 0,
			scaleFactor = 0;

		var paddingOnHover = typeof trx_addons_apply_filters == 'function'
								? trx_addons_apply_filters( 'trx_addons_filter_image_effects_padding', 0.04, 'waves' )
								: 0.04;

		var effectStrength = Math.max( 5.0, Math.min( 50.0, elm.getAttribute('data-image-effect-strength') || 30.0 ) );

		var plane = null,
			parent = null,
			img_all = elm.querySelectorAll( 'img:not([class*="trx_addons_image_effects_"])' ),
			img = img_all.length > 1
					? elm.querySelector( 'img:not([class*="avatar"]):not([class*="trx_addons_extended_taxonomy_img"])' )
					: elm.querySelector( 'img' );

		if ( img ) {
			if ( img_all.length > 1 ) {
				jQuery( img ).wrap( '<div class="trx_addons_image_effects_holder"></div>' );
			}
			parent = img.parentNode;
			if ( img_all.length == 1 ) {
				parent.classList.add('trx_addons_image_effects_holder');
			}
//			img.setAttribute('crossorigin', 'anonymous');
			img.setAttribute('data-sampler', 'wavesTexture');
			if ( img.getAttribute( 'decoding' ) == 'async' ) {
				img.setAttribute( 'decoding', 'sync' );
			}
			if ( ! globalCanvas ) {
				curtains = create_canvas(parent);
				if ( curtains ) jQuery(elm).data('curtains', curtains);
			}
			if ( curtains ) {
				if ( ! permanentDrawing ) curtains.disableDrawing();
				plane = curtains.addPlane( parent, get_params( elm, img, parent ) );
				if ( plane ) {
					jQuery(elm).data('curtains-plane', plane);
					handle_plane( plane );
				}
			}
		}

		// Return plane params
		function get_params() {
			var vs = `
				#ifdef GL_ES
					precision mediump float;
				#endif

				// those are the mandatory attributes that the lib sets
				attribute vec3 aVertexPosition;
				attribute vec2 aTextureCoord;

				// those are mandatory uniforms that the lib sets and that contain our model view and projection matrix
				uniform mat4 uMVMatrix;
				uniform mat4 uPMatrix;

				// our texture matrix uniform
				uniform mat4 wavesTextureMatrix;

				// if you want to pass your vertex and texture coords to the fragment shader
				varying vec3 vVertexPosition;
				varying vec2 vTextureCoord;

				// effect control vars declared inside our javascript
				uniform float uTime;
				uniform float uMouseMoveStrength;
				uniform float uEffectStrength;
				uniform float uWaveFactor;
				uniform vec2 uMousePosition;
				uniform float hoveringWaveForce;
				uniform float uPadding;

				void main() {
					vec3 vertexPosition = aVertexPosition;
					float distanceFromMouse = distance(uMousePosition, vec2(vertexPosition.x, vertexPosition.y));
					float waveSinusoid = cos(uWaveFactor * (distanceFromMouse - (uTime / 75.0)));
					float distanceStrength = 0.4 / (distanceFromMouse + 0.4);
					float distortionEffect = distanceStrength * waveSinusoid * uMouseMoveStrength / uEffectStrength;
					vertexPosition.z +=  distortionEffect;
					vertexPosition.x +=  distortionEffect * (uMousePosition.x - vertexPosition.x) * hoveringWaveForce * 3.0;
					vertexPosition.y +=  distortionEffect * (uMousePosition.y - vertexPosition.y) * hoveringWaveForce;
					gl_Position = uPMatrix * uMVMatrix * vec4(vertexPosition, (1.0 + uPadding));
					vTextureCoord = (wavesTextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy;
					vVertexPosition = vertexPosition;
				}
			`;
			
			var fs = `
				#ifdef GL_ES
					precision mediump float;
				#endif
				// get our varying variables
				varying vec3 vVertexPosition;
				varying vec2 vTextureCoord;

				// our texture sampler
				uniform sampler2D wavesTexture;

				// effect control vars
				uniform float displacement;

				void main() {
					if ( false ) {
						float intensity = 1.0;
						vec2 textureCoord = vTextureCoord;
						vec4 image1 = texture2D(wavesTexture, textureCoord);
						vec4 image2 = texture2D(wavesTexture, textureCoord);
						vec4 texture1 = texture2D(wavesTexture, vec2(textureCoord.x, textureCoord.y + displacement * (image2 * intensity)));
						vec4 texture2 = texture2D(wavesTexture, vec2(textureCoord.x, textureCoord.y + (1.0 - displacement) * (image1 * intensity)));
						vec4 result = mix(texture1, texture2, displacement);
						gl_FragColor = result;
					} else {
						vec2 textureCoord = vTextureCoord;
						gl_FragColor = texture2D(wavesTexture, textureCoord);
					}
				}
			`;

			return {
				vertexShader: vs,
				fragmentShader: fs,
				widthSegments: 10,
				heightSegments: 10,
				uniforms: {
					time: {
						name: "uTime",
						type: "1f",
						value: 0
					},
					mousePosition: {
						name: "uMousePosition",
						type: "2f",
						value: [-.5, .5]
					},
					mouseMoveStrength: {
						name: "uMouseMoveStrength",
						type: "1f",
						value: .2
					},
					effectStrength: {
						name: "uEffectStrength",
						type: "1f",
						value: effectStrength
					},
					displacement: {
						name: "displacement",
						type: "1f",
						value: 0
					},
					waveFactor: {
						name: "uWaveFactor",
						type: "1f",
						value: waveFactor
					},
					hoveringWaveForce: {
						name: "hoveringWaveForce",
						type: "1f",
						value: waveForceMin
					},
					padding: {
						name: "uPadding",
						type: "1f",
						value: 0
					}
				}
			};
		}

		// handle plane
		function handle_plane( plane ) {
			plane
				.onLoading(function() {
//					console.log('Waves onLoading');
				})
				.onReady(function() {
//					console.log('Waves onReady: Plane '+index+' of '+total+' is ready');
					// first render plane
					if ( ! permanentDrawing ) curtains.needRender();
					// init tweens
					plane.tweenForce = null;
					plane.tweenScale = null;
					plane.tweenPadding = null;
					// now that our plane is ready we can listen to mouse move event
					function mouse_move() {
						if ( ! mouseIn ) {
							mouse_enter();
						}
					}
					function mouse_enter() {
						if ( ! mouseIn ) {
							mouseIn = true;
							change_wave_force( waveForceMax );
							change_scale( 1 );
							if ( paddingOnHover ) {
								change_padding( paddingOnHover );
							}
						}
					}
					function mouse_leave() {
						if ( mouseIn ) {
							mouseIn = false;
							change_wave_force( waveForceMin );
							change_scale( 0 );
							if ( paddingOnHover ) {
								change_padding( 0 );
							}
						}
					}
					elm.addEventListener("mousemove",  mouse_move );
					elm.addEventListener("touchmove",  mouse_move );
					elm.addEventListener("mouseenter", mouse_enter );
					elm.addEventListener("touchstart", mouse_enter );
					elm.addEventListener("mouseleave", mouse_leave );
					elm.addEventListener("touchend",   mouse_leave );
				})
				.onRender(function() {
//					if ( globalCanvas ) plane.updatePosition();
					plane.uniforms.time.value++;

				})
				.onAfterResize(function() {
//					console.log('Waves afterResize: Plane '+index+' of '+total+' is resized');
				});

			// Change wave force value
			function change_wave_force( to ) {
				if ( plane.tweenForce ) {
					trx_addons_tween_stop( plane.tweenForce );
				}
				plane.tweenForce = trx_addons_tween_value( {
					start: plane.uniforms.hoveringWaveForce.value,
					end: to,
					time: 1.25,
					callbacks: {
						onUpdate: function(value) {
							plane.uniforms.hoveringWaveForce.value = value;
						},
						onComplete: function() {
							trx_addons_tween_stop( plane.tweenForce );
							plane.tweenForce = null;
						}
					}
				} );
			}

			// Change padding value
			function change_padding( to ) {
				if ( plane.tweenPadding ) {
					trx_addons_tween_stop( plane.tweenPadding );
				}
				plane.tweenPadding = trx_addons_tween_value( {
					start: plane.uniforms.padding.value,
					end: to,
					time: 1.25,
					callbacks: {
						onUpdate: function(value) {
							plane.uniforms.padding.value = value;
						},
						onComplete: function() {
							trx_addons_tween_stop( plane.tweenPadding );
							plane.tweenPadding = null;
						}
					}
				} );
			}

			// Change scale
			function change_scale( to ) {
				if ( plane.tweenScale ) {
					trx_addons_tween_stop( plane.tweenScale );
				}
				if ( ! permanentDrawing ) {
					curtains.enableDrawing();
				}
				plane.tweenScale = trx_addons_tween_value( {
					start: scaleFactor,
					end: to,
					time: 1.25,
					callbacks: {
						onUpdate: function(value) {
							scaleFactor = value;
							if ( scaleOnHover ) {
								plane.textures && plane.textures[0].setScale(1 + value / 12, 1 + value / 12);
							}
						},
						onComplete: function() {
							trx_addons_tween_stop( plane.tweenScale );
							plane.tweenScale = null;
							if ( ! permanentDrawing && to === 0 ) {
								curtains.disableDrawing();
							}
						}
					}
				} );
			}
		}
	};



	// Effect 'Waves2'
	//-------------------------------------------
	
	// Create plane.
	// Attention! All callbacks must be in a global scope!
	window.trx_addons_image_effects_callback_waves2 = function( curtains_global, elm, index, total ) {

		var curtains = curtains_global ? curtains_global : null;

		var waveForceMin = 0,
			waveForceMax = 2;

		var waveFactor = elm.getAttribute('data-image-effect-waves-factor') || 4;

		var mouseIn = false;

		var scaleOnHover = elm.getAttribute('data-image-effect-scale') > 0,
			scaleFactor = 0;

		var paddingOnHover = typeof trx_addons_apply_filters == 'function'
								? trx_addons_apply_filters( 'trx_addons_filter_image_effects_padding', 0.04, 'waves2' )
								: 0.04;

		var effectStrength = Math.max( 5.0, Math.min( 50.0, elm.getAttribute('data-image-effect-strength') || 30.0 ) );

		var plane = null,
			parent = null,
			img_all = elm.querySelectorAll( 'img:not([class*="trx_addons_image_effects_"])' ),
			img = img_all.length > 1
					? elm.querySelector( 'img:not([class*="avatar"]):not([class*="trx_addons_extended_taxonomy_img"])' )
					: elm.querySelector( 'img' );

		if ( img ) {
			if ( img_all.length > 1 ) {
				jQuery( img ).wrap( '<div class="trx_addons_image_effects_holder"></div>' );
			}
			parent = img.parentNode;
			if ( img_all.length == 1 ) {
				parent.classList.add('trx_addons_image_effects_holder');
			}
//			img.setAttribute('crossorigin', 'anonymous');
			img.setAttribute('data-sampler', 'waves2Texture');
			if ( img.getAttribute( 'decoding' ) == 'async' ) {
				img.setAttribute( 'decoding', 'sync' );
			}
			if ( ! globalCanvas ) {
				curtains = create_canvas(parent);
				if ( curtains ) jQuery(elm).data('curtains', curtains);
			}
			if ( curtains ) {
				if ( ! permanentDrawing ) curtains.disableDrawing();
				plane = curtains.addPlane( parent, get_params( elm, img, parent ) );
				if ( plane ) {
					jQuery(elm).data('curtains-plane', plane);
					handle_plane( plane );
				}
			}
		}

		// Return plane params
		function get_params( elm, img, parent ) {
			var vs = `
				#ifdef GL_ES
					precision mediump float;
				#endif

				// default mandatory variables
				attribute vec3 aVertexPosition;
				attribute vec2 aTextureCoord;

				uniform mat4 uMVMatrix;
				uniform mat4 uPMatrix;

				// our texture matrix
				uniform mat4 waves2TextureMatrix;

				// custom variables
				varying vec3 vVertexPosition;
				varying vec2 vTextureCoord;
				uniform float uTime;
				uniform vec2 uResolution;
				uniform vec2 uMousePosition;
				uniform float uMouseMoveStrength;
				uniform float uEffectStrength;
				uniform float uWaveFactor;
				uniform float uPadding;

				void main() {
					vec3 vertexPosition = aVertexPosition;
					// get the distance between our vertex and the mouse position
					float distanceFromMouse = distance(uMousePosition, vec2(vertexPosition.x, vertexPosition.y));
					// calculate our wave effect
					float waveSinusoid = cos(uWaveFactor * (distanceFromMouse - (uTime / 75.0)));
					// attenuate the effect based on mouse distance
					float distanceStrength = 0.4 / (distanceFromMouse + 0.4);
					// calculate our distortion effect
					float distortionEffect = distanceStrength * waveSinusoid * uMouseMoveStrength / uEffectStrength;
					// apply it to our vertex position
					vertexPosition.z += distortionEffect;
					vertexPosition.x += distortionEffect * (uMousePosition.x - vertexPosition.x) * (uResolution.x / uResolution.y);
					vertexPosition.y += distortionEffect * (uMousePosition.y - vertexPosition.y);
					gl_Position = uPMatrix * uMVMatrix * vec4(vertexPosition, (1.0 + uPadding));
					// varyings
					vTextureCoord = (waves2TextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy;
					vVertexPosition = vertexPosition;
				}
			`;
			var fs = `
				#ifdef GL_ES
					precision mediump float;
				#endif

				varying vec3 vVertexPosition;
				varying vec2 vTextureCoord;
				uniform sampler2D waves2Texture;

				void main() {
					// apply our texture
					vec4 finalColor = texture2D(waves2Texture, vTextureCoord);
					// fake shadows based on vertex position along Z axis
					finalColor.rgb -= clamp(-vVertexPosition.z, 0.0, 1.0);
					// fake lights based on vertex position along Z axis
					finalColor.rgb += clamp(vVertexPosition.z, 0.0, 1.0);
					// handling premultiplied alpha (useful if we were using a png with transparency)
					finalColor = vec4(finalColor.rgb * finalColor.a, finalColor.a);
					gl_FragColor = finalColor;
				}
			`;

			return {
				vertexShader: vs,
				fragmentShader: fs,
				widthSegments: 20,
				heightSegments: 20,
				uniforms: {
					resolution: { // resolution of our plane
						name: "uResolution",
						type: "2f", // notice this is an length 2 array of floats
						value: [ parent.clientWidth, parent.clientHeight ]
					},
					time: { // time uniform that will be updated at each draw call
						name: "uTime",
						type: "1f",
						value: 0
					},
					mousePosition: { // our mouse position
						name: "uMousePosition",
						type: "2f", // again an array of floats
						value: [-.5, .5]
					},
					mouseMoveStrength: { // the mouse move strength
						name: "uMouseMoveStrength",
						type: "1f",
						value: 0
					},
					effectStrength: { // the effect strength
						name: "uEffectStrength",
						type: "1f",
						value: effectStrength
					},
					waveFactor: {    // waves frequency
						name: "uWaveFactor",
						type: "1f",
						value: waveFactor
					},
					padding: { // padding around image on hover
						name: "uPadding",
						type: "1f",
						value: 0
					}
				}
			};
		}

		// handle plane
		function handle_plane( plane ) {
			plane
				.onLoading(function() {
					//console.log(plane.loadingManager.sourcesLoaded);
				})
				.onReady(function() {
//					console.log('Plane '+index+' of '+total+' is ready');
					// set a fov of 35 to reduce perspective
					plane.setPerspective(35);
					// first render plane
					if ( ! permanentDrawing ) curtains.needRender();
					// init tweens
					plane.tween = null;
					plane.tweenScale = null;
					// now that our plane is ready we can listen to mouse move event
					function mouse_move() {
						if ( ! mouseIn ) {
							mouse_enter();
						}
					}
					function mouse_enter() {
						if ( ! mouseIn ) {
							mouseIn = true;
							change_wave_force( waveForceMax );
							change_scale( 1 );
							if ( paddingOnHover ) {
								change_padding( paddingOnHover );
							}
						}
					}
					function mouse_leave() {
						if ( mouseIn ) {
							mouseIn = false;
							change_wave_force( waveForceMin );
							change_scale( 0 );
							if ( paddingOnHover ) {
								change_padding( 0 );
							}
						}
					}
					elm.addEventListener("mousemove",  mouse_move );
					elm.addEventListener("touchmove",  mouse_move );
					elm.addEventListener("mouseenter", mouse_enter );
					elm.addEventListener("touchstart", mouse_enter );
					elm.addEventListener("mouseleave", mouse_leave );
					elm.addEventListener("touchend",   mouse_leave );
				})
				.onRender(function() {
					// increment our time uniform
					plane.uniforms.time.value++;
				})
				.onAfterResize(function() {
//					console.log('Plane '+index+' of '+total+' is resized');
					/* Commented, because set width and height to 0 inside slider
					var planeBoundingRect = plane.getBoundingRect();
					plane.uniforms.resolution.value = [ planeBoundingRect.width, planeBoundingRect.height ];
					*/
				});
		}

		// Change wave force value
		function change_wave_force( to ) {
			if ( plane.tween ) {
				trx_addons_tween_stop( plane.tween );
			}
			plane.tween = trx_addons_tween_value( {
				start: plane.uniforms.mouseMoveStrength.value,
				end: to,
				time: 1.25,
				callbacks: {
					onUpdate: function(value) {
						plane.uniforms.mouseMoveStrength.value = value;
					},
					onComplete: function() {
						trx_addons_tween_stop( plane.tween );
						plane.tween = null
					}
				}
			} );
		}

		// Change padding value
		function change_padding( to ) {
			if ( plane.tweenPadding ) {
				trx_addons_tween_stop( plane.tweenPadding );
			}
			plane.tweenPadding = trx_addons_tween_value( {
				start: plane.uniforms.padding.value,
				end: to,
				time: 1.25,
				callbacks: {
					onUpdate: function(value) {
						plane.uniforms.padding.value = value;
					},
					onComplete: function() {
						trx_addons_tween_stop( plane.tweenPadding );
						plane.tweenPadding = null;
					}
				}
			} );
		}

		// Change scale
		function change_scale( to ) {
			if ( plane.tweenScale ) {
				trx_addons_tween_stop( plane.tweenScale );
			}
			if ( ! permanentDrawing ) {
				curtains.enableDrawing();
			}
			plane.tweenScale = trx_addons_tween_value( {
				start: scaleFactor,
				end: to,
				time: 1.25,
				callbacks: {
					onUpdate: function(value) {
						scaleFactor = value;
						if ( scaleOnHover ) {
							plane.textures && plane.textures[0].setScale(1 + value / 12, 1 + value / 12);
						}
					},
					onComplete: function() {
						trx_addons_tween_stop( plane.tweenScale );
						plane.tweenScale = null;
						if ( ! permanentDrawing && to === 0 ) {
							curtains.disableDrawing();
						}
					}
				}
			} );
		}
	};



	// Effect 'Ripple'
	//-------------------------------------------
	
	// Create plane.
	// Attention! All callbacks must be in a global scope!
	window.trx_addons_image_effects_callback_ripple = function( curtains_global, elm, index, total ) {

		var curtains = curtains_global ? curtains_global : null;

		// Common vars
		var mouseIn = false;

		var scaleOnHover = elm.getAttribute('data-image-effect-scale') > 0,
			scaleFactor = 0;

		var effectStrength = Math.max( 5.0, Math.min( 50.0, elm.getAttribute('data-image-effect-strength') || 30.0 ) );

		// Effect-specific vars
		var wavesDirection = Math.max(0.0, Math.min(1.0, elm.getAttribute('data-image-effect-waves-direction') ) );

		// Curtains init
		var plane = null,
			parent = null,
			img_all = elm.querySelectorAll( 'img:not([class*="trx_addons_image_effects_"])' ),
			img = img_all.length > 1
					? elm.querySelector( 'img:not([class*="avatar"]):not([class*="trx_addons_extended_taxonomy_img"])' )
					: elm.querySelector( 'img' );

		if ( img ) {
			if ( img_all.length > 1 ) {
				jQuery( img ).wrap( '<div class="trx_addons_image_effects_holder"></div>' );
			}
			parent = img.parentNode;
			if ( img_all.length == 1 ) {
				parent.classList.add('trx_addons_image_effects_holder');
			}
//			img.setAttribute('crossorigin', 'anonymous');
			img.setAttribute('data-sampler', 'rippleTexture');
			if ( img.getAttribute( 'decoding' ) == 'async' ) {
				img.setAttribute( 'decoding', 'sync' );
			}
			if ( ! globalCanvas ) {
				curtains = create_canvas(parent);
				if ( curtains ) jQuery(elm).data('curtains', curtains);
			}
			if ( curtains ) {
				if ( ! permanentDrawing ) curtains.disableDrawing();
				plane = curtains.addPlane( parent, get_params( elm, img, parent ) );
				if ( plane ) {
					jQuery(elm).data('curtains-plane', plane);
					handle_plane( plane );
				}
			}
		}

		// Return plane params
		function get_params() {
			var vs = `

				#ifdef GL_ES
					precision mediump float;
				#endif

				// those are the mandatory attributes that the lib sets
				attribute vec3 aVertexPosition;
				attribute vec2 aTextureCoord;

				// those are mandatory uniforms that the lib sets and that contain our model view and projection matrix
				uniform mat4 uMVMatrix;
				uniform mat4 uPMatrix;

				// our texture matrix uniform
				uniform mat4 rippleTextureMatrix;

				// if you want to pass your vertex and texture coords to the fragment shader
				varying vec3 vVertexPosition;
				varying vec2 vTextureCoord;

				void main() {
					// get the vertex position from its attribute
					vec3 vertexPosition = aVertexPosition;
					
					// set its position based on projection and model view matrix
					gl_Position = uPMatrix * uMVMatrix * vec4(vertexPosition, 1.0);

					// set the varying variables
					// thanks to the texture matrix we will be able to calculate accurate texture coords
					// so that our texture will always fit our plane without being distorted
					vTextureCoord = (rippleTextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy;
					vVertexPosition = vertexPosition;
				}
			`;
			
			var fs = `
				#ifdef GL_ES
					precision mediump float;
				#endif

				// get our varying variables
				varying vec3 vVertexPosition;
				varying vec2 vTextureCoord;

				// our texture sampler
				uniform sampler2D rippleTexture;

				// effect control vars declared inside our javascript
				uniform float uTime;			// time iterator to change waves position
				uniform float uEffectStrength;	// 5.0 - 50.0 - waves amplitude
				uniform float uWavesForce;		// 0.0 - 1.0 - fadeIn/fadeOut on mouse hover
				uniform float uWavesDirection;	// 0 - horizontal, 1 - vertical

				void main() {
					// get our texture coords
					vec2 textureCoord = vTextureCoord;

					// displace our pixels along both axis based on our time uniform and texture UVs
					// this will create a kind of water surface effect
					// try to comment a line or change the constants to see how it changes the effect
					// reminder : textures coords are ranging from 0.0 to 1.0 on both axis
					const float PI = 3.141592;

					textureCoord.x += (
										sin(textureCoord.x * 10.0 * ( 2.0 - uWavesDirection ) + ((uTime * (PI / 3.0)) * 0.031))
										+ sin(textureCoord.y * 10.0 * ( 2.0 - uWavesDirection ) + ((uTime * (PI / 2.489)) * 0.017))
										) / uEffectStrength / ( 2.5 + 1.5 * uWavesDirection ) * uWavesForce;	// * 0.0075;

					textureCoord.y += (
										sin(textureCoord.y * 20.0 / ( 2.0 - uWavesDirection ) + ((uTime * (PI / 2.023)) * 0.023))
										+ sin(textureCoord.x * 20.0 / ( 2.0 - uWavesDirection ) + ((uTime * (PI / 3.1254)) * 0.037))
										) / uEffectStrength / ( 2.5 + 1.5 * ( 1.0 - uWavesDirection ) ) * uWavesForce;	// * 0.0125;

					gl_FragColor = texture2D(rippleTexture, textureCoord);
				}
			`;

			return {
				vertexShader: vs,		// our vertex shader ID
				fragmentShader: fs,		// our fragment shader ID
//				widthSegments: 10,
//				heightSegments: 10,
				uniforms: {				// variables passed to shaders
					time: {
						name: "uTime",
						type: "1f",
						value: 0
					},
					effectStrength: {
						name: "uEffectStrength",
						type: "1f",
						value: effectStrength
					},
					wavesForce: {
						name: "uWavesForce",
						type: "1f",
						value: 0
					},
					wavesDirection: {
						name: "uWavesDirection",
						type: "1f",
						value: wavesDirection
					}
				}
			};
		}

		// handle plane
		function handle_plane( plane ) {
			plane
				.onLoading(function() {
//					console.log(plane.loadingManager.sourcesLoaded);
				})
				.onReady(function() {
//					console.log('Plane '+index+' of '+total+' is ready');
					// first render plane
					if ( ! permanentDrawing ) curtains.needRender();
					// init tweens
					plane.tweenForce = null;
					plane.tweenScale = null;
					// now that our plane is ready we can listen to mouse move event
					function mouse_move(e) {
						if ( ! mouseIn ) {
							mouse_enter();
						}
					}
					function mouse_enter() {
						if ( ! mouseIn ) {
							mouseIn = true;
							change_waves_force( 1 );
							change_scale( 1 );
						}
					}
					function mouse_leave() {
						if ( mouseIn ) {
							mouseIn = false;
							change_waves_force( 0 );
							change_scale( 0 );
						}
					}
					elm.addEventListener("mousemove",  mouse_move );
					elm.addEventListener("touchmove",  mouse_move );
					elm.addEventListener("mouseenter", mouse_enter );
					elm.addEventListener("touchstart", mouse_enter );
					elm.addEventListener("mouseleave", mouse_leave );
					elm.addEventListener("touchend",   mouse_leave );
				})
				.onRender(function() {
					plane.uniforms.time.value++;

				})
				.onAfterResize(function() {
//					console.log('Plane '+index+' of '+total+' is resized');
				});

			// Change wave force value
			function change_waves_force( to ) {
				if ( plane.tweenForce ) {
					trx_addons_tween_stop( plane.tweenForce );
				}
				plane.tweenForce = trx_addons_tween_value( {
					start: plane.uniforms.wavesForce.value,
					end: to,
					time: 1.25,
					callbacks: {
						onUpdate: function(value) {
							plane.uniforms.wavesForce.value = value;
						},
						onComplete: function() {
							trx_addons_tween_stop( plane.tweenForce );
							plane.tweenForce = null;
						}
					}
				} );
			}

			// Change scale
			function change_scale( to ) {
				if ( plane.tweenScale ) {
					trx_addons_tween_stop( plane.tweenScale );
				}
				if ( ! permanentDrawing ) {
					curtains.enableDrawing();
				}
				plane.tweenScale = trx_addons_tween_value( {
					start: scaleFactor,
					end: to,
					time: 1.25,
					callbacks: {
						onUpdate: function(value) {
							scaleFactor = value;
							if ( scaleOnHover ) {
								plane.textures && plane.textures[0].setScale(1 + value / 12, 1 + value / 12);
							}
						},
						onComplete: function() {
							trx_addons_tween_stop( plane.tweenScale );
							plane.tweenScale = null;
							if ( ! permanentDrawing && to === 0 ) {
								curtains.disableDrawing();
							}
						}
					}
				} );
			}
		}
	};



	// Effect 'Ripple 2'
	//-------------------------------------------
	
	// Create plane.
	// Attention! All callbacks must be in a global scope!
	window.trx_addons_image_effects_callback_ripple2 = function( curtains_global, elm, index, total ) {

		var curtains = curtains_global ? curtains_global : null;

		// Common vars
		var mouseIn = false;

		var scaleOnHover = elm.getAttribute('data-image-effect-scale') > 0,
			scaleFactor = 0;

		var effectStrength = Math.max( 5.0, Math.min( 50.0, elm.getAttribute('data-image-effect-strength') || 30.0 ) );

		// Effect-specific vars
		var wavesDirection = Math.max(0.0, Math.min(1.0, elm.getAttribute('data-image-effect-waves-direction') ) );

		// Curtains init
		var plane = null,
			parent = null,
			img_all = elm.querySelectorAll( 'img:not([class*="trx_addons_image_effects_"])' ),
			img = img_all.length > 1
					? elm.querySelector( 'img:not([class*="avatar"]):not([class*="trx_addons_extended_taxonomy_img"])' )
					: elm.querySelector( 'img' );

		if ( img ) {
			if ( img_all.length > 1 ) {
				jQuery( img ).wrap( '<div class="trx_addons_image_effects_holder"></div>' );
			}
			parent = img.parentNode;
			if ( img_all.length == 1 ) {
				parent.classList.add('trx_addons_image_effects_holder');
			}
//			img.setAttribute('crossorigin', 'anonymous');
			img.setAttribute('data-sampler', 'ripple2Texture');
			if ( img.getAttribute( 'decoding' ) == 'async' ) {
				img.setAttribute( 'decoding', 'sync' );
			}
			var displacement_url = elm.getAttribute('data-image-effect-displacement');
			if ( displacement_url ) {
				var displacement_img = document.createElement("img");
//				displacement_img.setAttribute('crossorigin', 'anonymous');
				displacement_img.setAttribute('src', displacement_url);
				displacement_img.setAttribute('data-sampler', 'ripple2Displacement');
				if ( displacement_img.getAttribute( 'decoding' ) == 'async' ) {
					displacement_img.setAttribute( 'decoding', 'sync' );
				}
				displacement_img.classList.add('trx_addons_image_effects_ripple_displacement');
				parent.appendChild(displacement_img);
				$document.on('action.after_add_content', function(e, cont) {
					cont.find( '.trx_addons_image_effects_ripple_displacement' ).remove();
				});
			}
			if ( ! globalCanvas ) {
				curtains = create_canvas(parent);
				if ( curtains ) jQuery(elm).data('curtains', curtains);
			}
			if ( curtains ) {
				if ( ! permanentDrawing ) curtains.disableDrawing();
				plane = curtains.addPlane( parent, get_params( elm, img, parent ) );
				if ( plane ) {
					jQuery(elm).data('curtains-plane', plane);
					handle_plane( plane );
				}
			}
		}

		// Return plane params
		function get_params() {
			var vs = `
				#ifdef GL_ES
					precision mediump float;
				#endif

				// default mandatory variables
				attribute vec3 aVertexPosition;
				attribute vec2 aTextureCoord;

				uniform mat4 uMVMatrix;
				uniform mat4 uPMatrix;

				// our texture matrices
				// notice how it matches our data-sampler attributes + "Matrix"
				uniform mat4 ripple2TextureMatrix;

				// varying variables
				varying vec3 vVertexPosition;

				// our displacement texture will use original texture coords attributes
				varying vec2 vDisplacementCoord;

				// our image will use texture coords based on their texture matrices
				varying vec2 vTextureCoord;

				// custom uniforms
				uniform float uTime;

				void main() {
					vec3 vertexPosition = aVertexPosition;

					gl_Position = uPMatrix * uMVMatrix * vec4(vertexPosition, 1.0);

					// varying variables
					// texture coords attributes because we want our displacement texture to be contained
					vDisplacementCoord = aTextureCoord;
					// our image texture coords based on their texture matrices
					vTextureCoord = (ripple2TextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy;
					// vertex position as usual
					vVertexPosition = vertexPosition;
				}
			`;
			
			var fs = `
				#ifdef GL_ES
					precision mediump float;
				#endif

				// all our varying variables
				varying vec3 vVertexPosition;
				varying vec2 vDisplacementCoord;
				varying vec2 vTextureCoord;

				// our textures samplers
				// notice how it matches our data-sampler attributes
				uniform sampler2D ripple2Texture;
				uniform sampler2D ripple2Displacement;

				// effect control vars declared inside our javascript
				uniform float uTime;			// time iterator to change waves position
				uniform float uEffectStrength;	// 5.0 - 50.0 - waves amplitude
				uniform float uWavesForce;		// 0.0 - 1.0 - fadeIn/fadeOut on mouse hover
				uniform float uWavesDirection;	// 0 - horizontal, 1 - vertical

				void main( void ) {

					// our displacement texture
					vec2 displacementCoords = vDisplacementCoord;

					displacementCoords = vec2( mod(displacementCoords.x - (1.0 - uWavesDirection) * uTime / ( 400.0 + uEffectStrength * 5.0 ), 1.0),
											   mod(displacementCoords.y - uWavesDirection * uTime / ( 600.0 + uEffectStrength * 5.0 ), 1.0)
											);
					vec4 displacementTexture = texture2D(ripple2Displacement, displacementCoords);

					// image texture
					vec2 textureCoords = vTextureCoord;

					// displace our pixels along both axis based on our time uniform and texture UVs
					// this will create a kind of water surface effect
					// try to comment a line or change the constants to see how it changes the effect
					// reminder : textures coords are ranging from 0.0 to 1.0 on both axis
					const float PI = 3.141592;

					textureCoords.x += 1.0 / uEffectStrength / ( 2.5 + 1.5 * uWavesDirection ) * uWavesForce * displacementTexture.r;

					textureCoords.y -= 1.0 / uEffectStrength / ( 2.0 + 1.5 * ( 1.0 - uWavesDirection ) ) * uWavesForce * displacementTexture.r;

					vec4 finalColor = texture2D(ripple2Texture, textureCoords);

					// handling premultiplied alpha and apply displacement
					finalColor = vec4(finalColor.rgb * finalColor.a, finalColor.a);

					// apply our shader
					gl_FragColor = finalColor;
				}
			`;

			return {
				vertexShader: vs,		// our vertex shader ID
				fragmentShader: fs,		// our fragment shader ID
//				widthSegments: 10,
//				heightSegments: 10,
				imageCover: false,		// our displacement texture has to fit the plane
				uniforms: {				// variables passed to shaders
					time: {
						name: "uTime",
						type: "1f",
						value: 0
					},
					effectStrength: {
						name: "uEffectStrength",
						type: "1f",
						value: effectStrength
					},
					wavesForce: {
						name: "uWavesForce",
						type: "1f",
						value: 0
					},
					wavesDirection: {
						name: "uWavesDirection",
						type: "1f",
						value: wavesDirection
					}
				}
			};
		}

		// handle plane
		function handle_plane( plane ) {
			plane
				.onLoading(function() {
//					console.log(plane.loadingManager.sourcesLoaded);
				})
				.onReady(function() {
//					console.log('Plane '+index+' of '+total+' is ready');
					// first render plane
					if ( ! permanentDrawing ) curtains.needRender();
					// init tweens
					plane.tweenForce = null;
					plane.tweenScale = null;
					// now that our plane is ready we can listen to mouse move event
					function mouse_move(e) {
						if ( ! mouseIn ) {
							mouse_enter();
						}
					}
					function mouse_enter() {
						if ( ! mouseIn ) {
							mouseIn = true;
							change_waves_force( 1 );
							change_scale( 1 );
						}
					}
					function mouse_leave() {
						if ( mouseIn ) {
							mouseIn = false;
							change_waves_force( 0 );
							change_scale( 0 );
						}
					}
					elm.addEventListener("mousemove",  mouse_move );
					elm.addEventListener("touchmove",  mouse_move );
					elm.addEventListener("mouseenter", mouse_enter );
					elm.addEventListener("touchstart", mouse_enter );
					elm.addEventListener("mouseleave", mouse_leave );
					elm.addEventListener("touchend",   mouse_leave );
				})
				.onRender(function() {
					plane.uniforms.time.value++;

				})
				.onAfterResize(function() {
//					console.log('Plane '+index+' of '+total+' is resized');
				});

			// Change wave force value
			function change_waves_force( to ) {
				if ( plane.tweenForce ) {
					trx_addons_tween_stop( plane.tweenForce );
				}
				plane.tweenForce = trx_addons_tween_value( {
					start: plane.uniforms.wavesForce.value,
					end: to,
					time: 1.25,
					callbacks: {
						onUpdate: function(value) {
							plane.uniforms.wavesForce.value = value;
						},
						onComplete: function() {
							trx_addons_tween_stop( plane.tweenForce );
							plane.tweenForce = null;
						}
					}
				} );
			}

			// Change scale
			function change_scale( to ) {
				if ( plane.tweenScale ) {
					trx_addons_tween_stop( plane.tweenScale );
				}
				if ( ! permanentDrawing ) {
					curtains.enableDrawing();
				}
				plane.tweenScale = trx_addons_tween_value( {
					start: scaleFactor,
					end: to,
					time: 1.25,
					callbacks: {
						onUpdate: function(value) {
							scaleFactor = value;
							if ( scaleOnHover ) {
								plane.textures && plane.textures[0].setScale(1 + value / 12, 1 + value / 12);
							}
						},
						onComplete: function() {
							trx_addons_tween_stop( plane.tweenScale );
							plane.tweenScale = null;
							if ( ! permanentDrawing && to === 0 ) {
								curtains.disableDrawing();
							}
						}
					}
				} );
			}
		}
	};



	// Effect 'Smudge'
	//-------------------------------------------
	
	// Create plane.
	// Attention! All callbacks must be in a global scope!
	window.trx_addons_image_effects_callback_smudge = function( curtains_global, elm, index, total ) {

		var curtains = curtains_global ? curtains_global : null;

		var enableDrawing = false;

		// track the mouse positions to send it to the shaders
		var mousePosition = {
			x: 0,
			y: 0
		};

		// we will keep track of the last position in order to calculate the movement strength/delta
		var mouseLastPosition = {
			x: 0,
			y: 0
		};

		var mouseStrength = false;

		var mouseIn = false;

		var scaleOnHover = elm.getAttribute('data-image-effect-scale') > 0,
			scaleFactor = 0;

		var paddingOnHover = typeof trx_addons_apply_filters == 'function'
								? trx_addons_apply_filters( 'trx_addons_filter_image_effects_padding', 0.04, 'smudge' )
								: 0.04;

		var effectStrength = Math.max( 5.0, Math.min( 50.0, elm.getAttribute('data-image-effect-strength') || 30.0 ) );

		var deltas = {
			max: 0,
			applied: 0
		};

		var plane = null,
			parent = null,
			img_all = elm.querySelectorAll( 'img:not([class*="trx_addons_image_effects_"])' ),
			img = img_all.length > 1
					? elm.querySelector( 'img:not([class*="avatar"]):not([class*="trx_addons_extended_taxonomy_img"])' )
					: elm.querySelector( 'img' );

		if ( img ) {
			if ( img_all.length > 1 ) {
				jQuery( img ).wrap( '<div class="trx_addons_image_effects_holder"></div>' );
			}
			parent = img.parentNode;
			if ( img_all.length == 1 ) {
				parent.classList.add('trx_addons_image_effects_holder');
			}
//			img.setAttribute('crossorigin', 'anonymous');
			img.setAttribute('data-sampler', 'smudgeTexture');
			if ( img.getAttribute( 'decoding' ) == 'async' ) {
				img.setAttribute( 'decoding', 'sync' );
			}
			if ( ! globalCanvas ) {
				curtains = create_canvas(parent);
				if ( curtains ) jQuery(elm).data('curtains', curtains);
			}
			if ( curtains ) {
				if ( ! permanentDrawing ) curtains.disableDrawing();
				plane = curtains.addPlane( parent, get_params( elm, img, parent ) );
				if ( plane ) {
					jQuery(elm).data('curtains-plane', plane);
					handle_plane( plane );
				}
			}
		}

		// Return plane params
		function get_params( elm, img, parent ) {
			var vs = `
				#ifdef GL_ES
					precision mediump float;
				#endif

				// default mandatory variables
				attribute vec3 aVertexPosition;
				attribute vec2 aTextureCoord;

				uniform mat4 uMVMatrix;
				uniform mat4 uPMatrix;

				// our texture matrix
				uniform mat4 smudgeTextureMatrix;

				// custom variables
				varying vec3 vVertexPosition;
				varying vec2 vTextureCoord;
				uniform float uTime;
				//uniform vec2 uResolution;
				uniform vec2 uMousePosition;
				uniform float uMouseMoveStrength;
				uniform float uEffectStrength;
				uniform float uPadding;

				void main() {
					vec3 vertexPosition = aVertexPosition;
					// get the distance between our vertex and the mouse position
					float distanceFromMouse = distance(uMousePosition, vec2(vertexPosition.x, vertexPosition.y));
					// attenuate the effect based on mouse distance
					float distanceStrength = 0.4 / (distanceFromMouse + 0.4);
					// calculate our distortion effect
					float distortionEffect = distanceStrength * uMouseMoveStrength / uEffectStrength;
					// apply it to our vertex position
					vertexPosition.z += distortionEffect;
					//vertexPosition.x += distortionEffect * (uMousePosition.x - vertexPosition.x) * (uResolution.x / uResolution.y);
					vertexPosition.x += distortionEffect * (uMousePosition.x - vertexPosition.x);
					vertexPosition.y += distortionEffect * (uMousePosition.y - vertexPosition.y);
					gl_Position = uPMatrix * uMVMatrix * vec4(vertexPosition, (1.0 + uPadding));
					// varyings
					vTextureCoord = (smudgeTextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy;
					vVertexPosition = vertexPosition;
				}
			`;
			var fs = `
				#ifdef GL_ES
					precision mediump float;
				#endif

				varying vec3 vVertexPosition;
				varying vec2 vTextureCoord;
				uniform sampler2D smudgeTexture;

				void main() {
					// apply our texture
					vec4 finalColor = texture2D(smudgeTexture, vTextureCoord);
					// fake shadows based on vertex position along Z axis
					finalColor.rgb -= clamp(-vVertexPosition.z, 0.0, 1.0);
					// fake lights based on vertex position along Z axis
					finalColor.rgb += clamp(vVertexPosition.z, 0.0, 1.0);
					// handling premultiplied alpha (useful if we were using a png with transparency)
					finalColor = vec4(finalColor.rgb * finalColor.a, finalColor.a);
					gl_FragColor = finalColor;
				}
			`;

			return {
				vertexShader: vs,
				fragmentShader: fs,
				widthSegments: 20,
				heightSegments: 20,
				uniforms: {
					/*
					resolution: { // resolution of our plane
						name: "uResolution",
						type: "2f", // notice this is an length 2 array of floats
						value: [ parent.clientWidth, parent.clientHeight ]
					},
					*/
					time: { // time uniform that will be updated at each draw call
						name: "uTime",
						type: "1f",
						value: 0
					},
					mousePosition: { // our mouse position
						name: "uMousePosition",
						type: "2f", // again an array of floats
						value: [ mousePosition.x, mousePosition.y ]
					},
					mouseMoveStrength: { // the mouse move strength
						name: "uMouseMoveStrength",
						type: "1f",
						value: 0
					},
					effectStrength: { // the smudge strength
						name: "uEffectStrength",
						type: "1f",
						value: effectStrength
					},
					padding: { 		// paddings around image on hover
						name: "uPadding",
						type: "1f",
						value: 0
					}
				}
			};
		}

		// handle plane
		function handle_plane( plane ) {
			plane
				.onLoading(function() {
					//console.log(plane.loadingManager.sourcesLoaded);
				})
				.onReady(function() {
//					console.log('Plane '+index+' of '+total+' is ready');
					// set a fov of 35 to reduce perspective
					plane.setPerspective(35);
					// apply a little effect once everything is ready
					deltas.max = 2;
					// first render plane
					if ( ! permanentDrawing ) curtains.needRender();
					// init tweens
					plane.tweenScale = null;
					// now that our plane is ready we can listen to mouse move event
					function mouse_move(e) {
						if ( ! mouseIn ) {
							mouse_enter();
						}
						handle_movement(e, plane);
					}
					function mouse_enter() {
						if ( ! mouseIn ) {
							mouseIn = true;
							change_scale( 1 );
							if ( paddingOnHover ) {
								change_padding(paddingOnHover);
							}
						}
					}
					function mouse_leave() {
						if ( mouseIn ) {
							mouseIn = false;
							change_scale( 0 );
							if ( paddingOnHover ) {
								change_padding(0);
							}
						}
					}
					elm.addEventListener("mousemove",  mouse_move );
					elm.addEventListener("touchmove",  mouse_move );
					elm.addEventListener("mouseenter", mouse_enter );
					elm.addEventListener("touchstart", mouse_enter );
					elm.addEventListener("mouseleave", mouse_leave );
					elm.addEventListener("touchend",   mouse_leave );
				})
				.onRender(function() {
					// increment our time uniform
					plane.uniforms.time.value++;
					// decrease both deltas by damping : if the user doesn't move the mouse, effect will fade away
					deltas.applied += (deltas.max - deltas.applied) * 0.02;
					deltas.max += (0 - deltas.max) * 0.01;
					// send the new mouse move strength value
					plane.uniforms.mouseMoveStrength.value = deltas.applied;
					if ( ! permanentDrawing && ! enableDrawing && Math.abs(deltas.applied - deltas.max) < 0.001 ) {
						curtains.disableDrawing();
					}
				})
				.onAfterResize(function() {
//					console.log('Plane '+index+' of '+total+' is resized');
					/*
					var planeBoundingRect = plane.getBoundingRect();
					plane.uniforms.resolution.value = [planeBoundingRect.width, planeBoundingRect.height];
					*/
				});
		}

		// handle the mouse move event
		function handle_movement(e, plane) {

			// update mouse last pos
			mouseLastPosition.x = mousePosition.x;
			mouseLastPosition.y = mousePosition.y;

			var mouse = {};

			// touch event
			if (e.targetTouches) {
				mouse.x = globalCanvas ? e.targetTouches[0].clientX : e.targetTouches[0].layerX;
				mouse.y = globalCanvas ? e.targetTouches[0].clientY : e.targetTouches[0].layerY;

			// mouse event
			} else {
													// In Chrome with custom scroll mouse coordinates in layerXY are incorrect
													// but in Mozilla mouse properties offsetXY always empty (equal to 0)
				mouse.x = globalCanvas ? e.clientX : ( e.offsetX !== 0 || e.offsetY !== 0 ? e.offsetX : e.layerX );
				mouse.y = globalCanvas ? e.clientY : ( e.offsetX !== 0 || e.offsetY !== 0 ? e.offsetY : e.layerY );
			}

			// lerp the mouse position a bit to smoothen the overall effect
			mousePosition.x = trx_addons_lerp(mousePosition.x, mouse.x, 0.3);
			mousePosition.y = trx_addons_lerp(mousePosition.y, mouse.y, 0.3);

			// convert our mouse/touch position to coordinates relative to the vertices of the plane
			var mouseCoords = globalCanvas
								? plane.mouseToPlaneCoords(mousePosition.x, mousePosition.y)
								: mouse_to_plane_coords( plane, mousePosition );
			

			// update our mouse position uniform
			plane.uniforms.mousePosition.value = [mouseCoords.x, mouseCoords.y];

			// calculate the mouse move strength
			if ( mouseStrength && mouseLastPosition.x && mouseLastPosition.y ) {
				var delta = Math.sqrt( Math.pow( mousePosition.x - mouseLastPosition.x, 2 ) + Math.pow( mousePosition.y - mouseLastPosition.y, 2 ) ) / 20;
				delta = Math.min(4, delta);
				// update max delta only if it increased
				if ( delta >= deltas.max ) {
					deltas.max = delta;
				}
			} else {
				deltas.max = 2;
			}
		}

		// Change padding value
		function change_padding( to ) {
			if ( plane.tweenPadding ) {
				trx_addons_tween_stop( plane.tweenPadding );
			}
			plane.tweenPadding = trx_addons_tween_value( {
				start: plane.uniforms.padding.value,
				end: to,
				time: 1.25,
				callbacks: {
					onUpdate: function(value) {
						plane.uniforms.padding.value = value;
					},
					onComplete: function() {
						trx_addons_tween_stop( plane.tweenPadding );
						plane.tweenPadding = null;
					}
				}
			} );
		}

		// Change scale
		function change_scale( to ) {
			if ( plane.tweenScale ) {
				trx_addons_tween_stop( plane.tweenScale );
			}
			if ( ! permanentDrawing ) {
				enableDrawing = true;
				curtains.enableDrawing();
			}
			plane.tweenScale = trx_addons_tween_value( {
				start: scaleFactor,
				end: to,
				time: 1.25,
				callbacks: {
					onUpdate: function(value) {
						scaleFactor = value;
						if ( scaleOnHover ) {
							plane.textures && plane.textures[0].setScale(1 + value / 12, 1 + value / 12);
						}
					},
					onComplete: function() {
						trx_addons_tween_stop( plane.tweenScale );
						plane.tweenScale = null;
						if ( ! permanentDrawing && to === 0 ) {
							enableDrawing = false;
							//curtains.disableDrawing();
						}
					}
				}
			} );
		}

	};



	// Effect 'Tint'
	//-------------------------------------------
	
	// Create plane.
	// Attention! All callbacks must be in a global scope!
	window.trx_addons_image_effects_callback_tint = function( curtains_global, elm, index, total ) {

		var curtains = curtains_global ? curtains_global : null;

		var mouseIn = false;

		var scaleOnHover = elm.getAttribute('data-image-effect-scale') > 0,
			scaleFactor = 0,
			tintColor = elm.getAttribute('data-image-effect-tint-color') || TRX_ADDONS_STORAGE['theme_accent_color'];

		var plane = null,
			parent = null,
			img_all = elm.querySelectorAll( 'img:not([class*="trx_addons_image_effects_"])' ),
			img = img_all.length > 1
					? elm.querySelector( 'img:not([class*="avatar"]):not([class*="trx_addons_extended_taxonomy_img"])' )
					: elm.querySelector( 'img' );

		if ( img ) {
			if ( img_all.length > 1 ) {
				jQuery( img ).wrap( '<div class="trx_addons_image_effects_holder"></div>' );
			}
			parent = img.parentNode;
			if ( img_all.length == 1 ) {
				parent.classList.add('trx_addons_image_effects_holder');
			}
//			img.setAttribute('crossorigin', 'anonymous');
			img.setAttribute('data-sampler', 'tintTexture');
			if ( img.getAttribute( 'decoding' ) == 'async' ) {
				img.setAttribute( 'decoding', 'sync' );
			}
			if ( ! globalCanvas ) {
				curtains = create_canvas(parent);
				if ( curtains ) jQuery(elm).data('curtains', curtains);
			}
			if ( curtains ) {
				if ( ! permanentDrawing ) curtains.disableDrawing();
				plane = curtains.addPlane( parent, get_params( elm, img, parent ) );
				if ( plane ) {
					jQuery(elm).data('curtains-plane', plane);
					handle_plane( plane );
				}
			}
		}

		// Return plane params
		function get_params( elm, img, parent ) {

			var vs = `
				#ifdef GL_ES
					precision mediump float;
				#endif

				vec3 permute(vec3 x) {
					return mod((x*34.0+1.0)*x, 289.0);
				}
				
				float snoise(vec2 v) {
					const vec4 C = vec4(0.211325, 0.366025, -0.57735, 0.02439);
					vec2 i = floor(v + dot(v, C.yy)),
						 x0 = v - i + dot(i, C.xx),
						 i1;
					i1 = x0.x > x0.y ? vec2(1.0, 0.0) : vec2(0.0,1.0);
					vec4 x12 = x0.xyxy + C.xxzz;
					x12.xy -= i1,
					i = mod(i, 289.0);
					vec3 p = permute(permute(i.y + vec3(0.0, i1.y, 1.0)) + i.x + vec3(0.0,i1.x, 1.0)),
						 m = max(0.5 - vec3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), 0.0);
					m = m * m, m = m * m;
					vec3 x = 2.0 * fract(p * C.www) - 1.0,
						 h = abs(x) - 0.5,
						 ox = floor(x + 0.5),
						 a0 = x - ox;
					m *= 1.792843 - 0.853735 * (a0 * a0 + h * h);
					vec3 g;
					g.x = a0.x * x0.x + h.x * x0.y,
					g.yz = a0.yz * x12.xz + h.yz * x12.yw;
					return 130.0 * dot(m, g);
				}
				
				attribute vec3 aVertexPosition;
				attribute vec2 aTextureCoord;

				uniform mat4 uMVMatrix, uPMatrix, tintTextureMatrix;
				uniform float uTime, uMouseOver;

				varying vec2 vTintTextureCoord;
				varying vec3 vVertexPosition, vNoise;

				void main(){
					vec3 vP = aVertexPosition;
					vec2 sUV = vec2(vP.x * 0.75, vP.y * 0.75);
					vec3 sN = vec3(snoise(sUV * 2.0 - uTime / 360.0));
					vP.z = 0.0,
					gl_Position = uPMatrix * uMVMatrix * vec4(vP, 1.0),
					vVertexPosition = vP,
					vTintTextureCoord = (tintTextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy,
					vNoise = sN;
				}
			`;
			var fs = `
				#ifdef GL_ES
					precision mediump float;
				#endif
				varying vec2 vTintTextureCoord;
				varying vec3 vVertexPosition;
				varying vec3 vNoise;

				uniform sampler2D tintTexture;
				uniform float uMouseOver;
				uniform vec3 uColor;

				void main(){
					vec4 f = texture2D(tintTexture, vTintTextureCoord);
					f.rgb -= clamp(-vVertexPosition.z / 10.0, 0.0, 1.0);
					f.rgb += clamp( vVertexPosition.z / 12.5, 0.0, 1.0);
					vec4 lC = vec4(0.299, 0.587, 0.114, 0.0),
						 l = vec4(1.0),
						 tint = vec4(uColor.r / 255.0, uColor.g / 255.0, uColor.b / 255.0, 1.0);
					float lM = dot(f, lC),
						  mN = clamp(uMouseOver * (1.0 - vNoise.r) - 1.0 + uMouseOver * 2.0, 0.0, 1.0);
					vec4 mC = (l + tint) / vec4(2.0),
						 dT = lM >= 0.45 ? mix(mC, l, smoothstep(0.45, 0.93125, lM)) : mix(tint, mC, smoothstep(-0.03125, 0.45, lM));
					f = mix(dT, f, step(0.9, mN));
					f = vec4(f.rgb * f.a, f.a);
					gl_FragColor = f;
				}
			`;

			return {
				vertexShader: vs,
				fragmentShader: fs,
				widthSegments: 5,
				heightSegments: 40,
				fov: 45,
				drawCheckMargins: {
					top: 15,
					right: 0,
					bottom: 15,
					left: 0
				},
				uniforms: {
					time: {
						name: "uTime",
						type: "1f",
						value: 0	// 180 * index
					},
					mouseOver: {
						name: "uMouseOver",
						type: "1f",
						value: 0
					},
					color: {
						name: "uColor",
						type: "3f",
						value: trx_addons_rgb2components(tintColor)
					}
				}
			};
		}

		// handle plane
		function handle_plane( plane ) {
			plane
				.onReady(function() {
//					console.log('Plane '+index+' of '+total+' is ready');
					// first render plane
					if ( ! permanentDrawing ) curtains.needRender();
					// init tweens
					plane.tween = null;
					plane.tweenScale = null;
					// now that our plane is ready we can listen to mouse move event
					function mouse_move(e) {
						if ( ! mouseIn ) {
							mouse_enter();
						}
					}
					function mouse_enter() {
						if ( ! mouseIn ) {
							mouseIn = true;
							change_mouse_over( 1 );
							change_scale( 1 );
						}
					}
					function mouse_leave() {
						if ( mouseIn ) {
							mouseIn = false;
							change_mouse_over( 0 );
							change_scale( 0 );
						}
					}
					elm.addEventListener("mousemove",  mouse_move );
					elm.addEventListener("touchmove",  mouse_move );
					elm.addEventListener("mouseenter", mouse_enter );
					elm.addEventListener("touchstart", mouse_enter );
					elm.addEventListener("mouseleave", mouse_leave );
					elm.addEventListener("touchend",   mouse_leave );
				})
				.onRender(function() {
					plane.uniforms.time.value++;
				});
		}

		// Change wave force value
		function change_mouse_over( to ) {
			if ( plane.tween ) {
				trx_addons_tween_stop( plane.tween );
			}
			plane.tween = trx_addons_tween_value( {
				start: plane.uniforms.mouseOver.value,
				end: to,
				time: 1.25,
				callbacks: {
					onUpdate: function(value) {
						plane.uniforms.mouseOver.value = value;
					},
					onComplete: function() {
						trx_addons_tween_stop( plane.tween );
						plane.tween = null
					}
				}
			} );
		}

		// Change scale
		function change_scale( to ) {
			if ( plane.tweenScale ) {
				trx_addons_tween_stop( plane.tweenScale );
			}
			if ( ! permanentDrawing ) {
				curtains.enableDrawing();
			}
			plane.tweenScale = trx_addons_tween_value( {
				start: scaleFactor,
				end: to,
				time: 1.25,
				callbacks: {
					onUpdate: function(value) {
						scaleFactor = value;
						if ( scaleOnHover ) {
							plane.textures && plane.textures[0].setScale(1 + value / 12, 1 + value / 12);
						}
					},
					onComplete: function() {
						trx_addons_tween_stop( plane.tweenScale );
						plane.tweenScale = null;
						if ( ! permanentDrawing && to === 0 ) {
							curtains.disableDrawing();
						}
					}
				}
			} );
		}
	};



	// Effect 'Swap'
	//-------------------------------------------
	
	// Create plane.
	// Attention! All callbacks must be in a global scope!
	window.trx_addons_image_effects_callback_swap = function( curtains_global, elm, index, total ) {

		var curtains = curtains_global ? curtains_global : null;

		// Common vars
		var mouseIn = false;

		var scaleOnHover = elm.getAttribute('data-image-effect-scale') > 0,
			scaleFactor = 0;

		var activeImage = 0;

		// Curtains init
		var plane = null,
			parent = null,
			img_all = elm.querySelectorAll( 'img:not([class*="trx_addons_image_effects_"])' ),
			img = img_all.length > 1
					? elm.querySelector( 'img:not([class*="avatar"]):not([class*="trx_addons_extended_taxonomy_img"])' )
					: elm.querySelector( 'img' );

		if ( img ) {
			if ( img_all.length > 1 ) {
				jQuery( img ).wrap( '<div class="trx_addons_image_effects_holder"></div>' );
			}
			parent = img.parentNode;
			if ( img_all.length == 1 ) {
				parent.classList.add('trx_addons_image_effects_holder');
			}
//			img.setAttribute('crossorigin', 'anonymous');
			img.setAttribute('data-sampler', 'swapTexture');
			if ( img.getAttribute( 'decoding' ) == 'async' ) {
				img.setAttribute( 'decoding', 'sync' );
			}
			var swap_url = elm.getAttribute('data-image-effect-swap-image');
			if ( swap_url ) {
				var swap_img = document.createElement("img");
//				swap_img.setAttribute('crossorigin', 'anonymous');
				swap_img.setAttribute('src', swap_url);
				swap_img.setAttribute('data-sampler', 'swap2Texture');
				if ( swap_img.getAttribute( 'decoding' ) == 'async' ) {
					swap_img.setAttribute( 'decoding', 'sync' );
				}
					swap_img.classList.add('trx_addons_image_effects_swap_image');
				parent.appendChild(swap_img);
				$document.on('action.after_add_content', function(e, cont) {
					cont.find( '.trx_addons_image_effects_swap_image' ).remove();
				});
			}
			var displacement_url = elm.getAttribute('data-image-effect-displacement');
			if ( displacement_url ) {
				var displacement_img = document.createElement("img");
//				displacement_img.setAttribute('crossorigin', 'anonymous');
				displacement_img.setAttribute('src', displacement_url);
				displacement_img.setAttribute('data-sampler', 'swapDisplacement');
				if ( displacement_img.getAttribute( 'decoding' ) == 'async' ) {
					displacement_img.setAttribute( 'decoding', 'sync' );
				}
					displacement_img.classList.add('trx_addons_image_effects_swap_displacement');
				parent.appendChild(displacement_img);
				$document.on('action.after_add_content', function(e, cont) {
					cont.find( '.trx_addons_image_effects_swap_displacement' ).remove();
				});
			}
			if ( ! globalCanvas ) {
				curtains = create_canvas(parent);
				if ( curtains ) jQuery(elm).data('curtains', curtains);
			}
			if ( curtains ) {
				if ( ! permanentDrawing ) curtains.disableDrawing();
				plane = curtains.addPlane( parent, get_params( elm, img, parent ) );
				if ( plane ) {
					jQuery(elm).data('curtains-plane', plane);
					handle_plane( plane );
				}
			}
		}

		// Return plane params
		function get_params() {
			var vs = `
				#ifdef GL_ES
					precision mediump float;
				#endif

				// default mandatory variables
				attribute vec3 aVertexPosition;
				attribute vec2 aTextureCoord;

				uniform mat4 uMVMatrix;
				uniform mat4 uPMatrix;

				// our texture matrices
				// notice how it matches our data-sampler attributes + "Matrix"
				uniform mat4 swapTextureMatrix;
				uniform mat4 swap2TextureMatrix;

				// varying variables
				varying vec3 vVertexPosition;

				// our displacement texture will use original texture coords attributes
				varying vec2 vTextureCoord;

				// our image will use texture coords based on their texture matrices
				varying vec2 vSwapTextureCoord;
				varying vec2 vSwap2TextureCoord;

				// custom uniforms
				uniform float uTime;

				void main() {
					vec3 vertexPosition = aVertexPosition;

					gl_Position = uPMatrix * uMVMatrix * vec4(vertexPosition, 1.0);

					// varying variables
					// texture coords attributes because we want our displacement texture to be contained
					vTextureCoord = aTextureCoord;
					// our image texture coords based on their texture matrices
					vSwapTextureCoord = (swapTextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy;
					vSwap2TextureCoord = (swap2TextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy;
					// vertex position as usual
					vVertexPosition = vertexPosition;
				}
			`;
			
			var fs = `
				#ifdef GL_ES
					precision mediump float;
				#endif

				// all our varying variables
				varying vec3 vVertexPosition;
				varying vec2 vTextureCoord;
				varying vec2 vSwapTextureCoord;
				varying vec2 vSwap2TextureCoord;

				// custom uniforms
				uniform float uTime;

				// our textures samplers
				// notice how it matches our data-sampler attributes
				uniform sampler2D swapTexture;
				uniform sampler2D swap2Texture;
				uniform sampler2D swapDisplacement;

				void main( void ) {
					// our texture coords
					vec2 textureCoords = vTextureCoord;

					// our displacement texture
					vec4 displacementTexture = texture2D(swapDisplacement, textureCoords);

					// our displacement factor is a float varying from 1 to 0 based on the timer
					float displacementFactor = 1.0 - (cos(uTime / (60.0 / 3.141592)) + 1.0) / 2.0;

					// the effect factor will tell which way we want to displace our pixels
					// the farther from the center of the videos, the stronger it will be
					vec2 effectFactor = vec2((textureCoords.x - 0.5) * 0.75, (textureCoords.y - 0.5) * 0.75);

					// calculate our displaced coordinates of the first video
					vec2 firstDisplacementCoords = vec2(vSwapTextureCoord.x - displacementFactor * (displacementTexture.r * effectFactor.x), vSwapTextureCoord.y- displacementFactor * (displacementTexture.r * effectFactor.y));
					// opposite displacement effect on the second video
					vec2 secondDisplacementCoords = vec2(vSwap2TextureCoord.x - (1.0 - displacementFactor) * (displacementTexture.r * effectFactor.x), vSwap2TextureCoord.y - (1.0 - displacementFactor) * (displacementTexture.r * effectFactor.y));

					// apply the textures
					vec4 firstDistortedColor = texture2D(swapTexture, firstDisplacementCoords);
					vec4 secondDistortedColor = texture2D(swap2Texture, secondDisplacementCoords);

					// blend both textures based on our displacement factor
					vec4 finalColor = mix(firstDistortedColor, secondDistortedColor, displacementFactor);

					// handling premultiplied alpha
					finalColor = vec4(finalColor.rgb * finalColor.a, finalColor.a);

					// apply our shader
					gl_FragColor = finalColor;
				}
			`;

			return {
				vertexShader: vs,		// our vertex shader ID
				fragmentShader: fs,		// our fragment shader ID
//				widthSegments: 10,
//				heightSegments: 10,
				imageCover: false,		// our displacement texture has to fit the plane
				uniforms: {				// variables passed to shaders
					time: {
						name: "uTime",
						type: "1f",
						value: 0
					}
				}
			};
		}

		// handle plane
		function handle_plane( plane ) {
			plane
				.onLoading(function() {
//					console.log(plane.loadingManager.sourcesLoaded);
				})
				.onReady(function() {
//					console.log('Plane '+index+' of '+total+' is ready');
					// first render plane
					if ( ! permanentDrawing ) curtains.needRender();
					// init tweens
					plane.tweenScale = null;
					// now that our plane is ready we can listen to mouse move event
					function mouse_move(e) {
						if ( ! mouseIn ) {
							mouse_enter();
						}
					}
					function mouse_enter() {
						if ( ! mouseIn ) {
							mouseIn = true;
							activeImage = 1;
							change_scale( 1 );
						}
					}
					function mouse_leave() {
						if ( mouseIn ) {
							mouseIn = false;
							activeImage = 0;
							change_scale( 0 );
						}
					}
					elm.addEventListener("mousemove",  mouse_move );
					elm.addEventListener("touchmove",  mouse_move );
					elm.addEventListener("mouseenter", mouse_enter );
					elm.addEventListener("touchstart", mouse_enter );
					elm.addEventListener("mouseleave", mouse_leave );
					elm.addEventListener("touchend",   mouse_leave );
				})
				.onRender(function() {
					plane.uniforms.time.value = activeImage == 1 ? Math.min(60, plane.uniforms.time.value + 1) : Math.max(0, plane.uniforms.time.value - 1);

				})
				.onAfterResize(function() {
//					console.log('Plane '+index+' of '+total+' is resized');
				});

			// Change scale
			function change_scale( to ) {
				if ( plane.tweenScale ) {
					trx_addons_tween_stop( plane.tweenScale );
				}
				if ( ! permanentDrawing ) {
					curtains.enableDrawing();
				}
				plane.tweenScale = trx_addons_tween_value( {
					start: scaleFactor,
					end: to,
					time: 1.25,
					callbacks: {
						onUpdate: function(value) {
							scaleFactor = value;
							if ( scaleOnHover ) {
								plane.textures && plane.textures[0].setScale(1 + value / 12, 1 + value / 12);
							}
						},
						onComplete: function() {
							trx_addons_tween_stop( plane.tweenScale );
							plane.tweenScale = null;
							if ( ! permanentDrawing ) {
								curtains.disableDrawing();
							}
						}
					}
				} );
			}
		}
	};



	// Effect 'Fish'
	//-------------------------------------------
	
	// Create plane.
	// Attention! All callbacks must be in a global scope!
	window.trx_addons_image_effects_callback_fish = function( curtains_global, elm, index, total ) {

		var curtains = curtains_global ? curtains_global : null;

		// Common vars
		var mouseIn = false;

		// track the mouse positions to send it to the shaders
		var mousePosition = {
			x: 0,
			y: 0
		};

		var scaleOnHover = elm.getAttribute('data-image-effect-scale') > 0,
			scaleFactor = 0;

		var activeImage = 0;

		// Curtains init
		var plane = null,
			parent = null,
			img_all = elm.querySelectorAll( 'img:not([class*="trx_addons_image_effects_"])' ),
			img = img_all.length > 1
					? elm.querySelector( 'img:not([class*="avatar"]):not([class*="trx_addons_extended_taxonomy_img"])' )
					: elm.querySelector( 'img' );

		if ( img ) {
			if ( img_all.length > 1 ) {
				jQuery( img ).wrap( '<div class="trx_addons_image_effects_holder"></div>' );
			}
			parent = img.parentNode;
			if ( img_all.length == 1 ) {
				parent.classList.add('trx_addons_image_effects_holder');
			}
//			img.setAttribute('crossorigin', 'anonymous');
			img.setAttribute('data-sampler', 'swapTexture');
			if ( img.getAttribute( 'decoding' ) == 'async' ) {
				img.setAttribute( 'decoding', 'sync' );
			}
			var swap_url = elm.getAttribute('data-image-effect-swap-image');
			if ( swap_url ) {
				var swap_img = document.createElement("img");
//				swap_img.setAttribute('crossorigin', 'anonymous');
				swap_img.setAttribute('src', swap_url);
				swap_img.setAttribute('data-sampler', 'swap2Texture');
				if ( swap_img.getAttribute( 'decoding' ) == 'async' ) {
					swap_img.setAttribute( 'decoding', 'sync' );
				}
					swap_img.classList.add('trx_addons_image_effects_swap_image');
				parent.appendChild(swap_img);
				$document.on('action.after_add_content', function(e, cont) {
					cont.find( '.trx_addons_image_effects_swap_image' ).remove();
				});
			}
			var displacement_url = elm.getAttribute('data-image-effect-displacement');
			if ( displacement_url ) {
				var displacement_img = document.createElement("img");
//				displacement_img.setAttribute('crossorigin', 'anonymous');
				displacement_img.setAttribute('src', displacement_url);
				displacement_img.setAttribute('data-sampler', 'swapDisplacement');
				if ( displacement_img.getAttribute( 'decoding' ) == 'async' ) {
					displacement_img.setAttribute( 'decoding', 'sync' );
				}
					displacement_img.classList.add('trx_addons_image_effects_swap_displacement');
				parent.appendChild(displacement_img);
				$document.on('action.after_add_content', function(e, cont) {
					cont.find( '.trx_addons_image_effects_swap_displacement' ).remove();
				});
			}
			if ( ! globalCanvas ) {
				curtains = create_canvas(parent);
				if ( curtains ) jQuery(elm).data('curtains', curtains);
			}
			if ( curtains ) {
				if ( ! permanentDrawing ) curtains.disableDrawing();
				plane = curtains.addPlane( parent, get_params( elm, img, parent ) );
				if ( plane ) {
					jQuery(elm).data('curtains-plane', plane);
					handle_plane( plane );
				}
			}
		}

		// Return plane params
		function get_params() {
			var vs = `
				#ifdef GL_ES
					precision mediump float;
				#endif

				// default mandatory variables
				attribute vec3 aVertexPosition;
				attribute vec2 aTextureCoord;

				uniform mat4 uMVMatrix;
				uniform mat4 uPMatrix;

				// our texture matrices
				// notice how it matches our data-sampler attributes + "Matrix"
				uniform mat4 swapTextureMatrix;
				uniform mat4 swap2TextureMatrix;

				// varying variables
				varying vec3 vVertexPosition;

				// our displacement texture will use original texture coords attributes
				varying vec2 vTextureCoord;

				// our image will use texture coords based on their texture matrices
				varying vec2 vSwapTextureCoord;
				varying vec2 vSwap2TextureCoord;

				// custom uniforms
				uniform float uTime;

				void main() {
					vec3 vertexPosition = aVertexPosition;

					gl_Position = uPMatrix * uMVMatrix * vec4(vertexPosition, 1.0);

					// varying variables
					// texture coords attributes because we want our displacement texture to be contained
					vTextureCoord = aTextureCoord;
					// our image texture coords based on their texture matrices
					vSwapTextureCoord = (swapTextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy;
					vSwap2TextureCoord = (swap2TextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy;
					// vertex position as usual
					vVertexPosition = vertexPosition;
				}
			`;
			
			var fs = `
				#ifdef GL_ES
					precision mediump float;
				#endif

				// all our varying variables
				varying vec3 vVertexPosition;
				varying vec2 vTextureCoord;
				varying vec2 vSwapTextureCoord;
				varying vec2 vSwap2TextureCoord;

				// custom uniforms
				uniform float uPR;
				uniform float uScale;
				uniform float uEffect;
				uniform float uTime;

				uniform vec2 uRes;
				uniform vec2 uMousePosition;

				// our textures samplers
				// notice how it matches our data-sampler attributes
				uniform sampler2D swapTexture;
				uniform sampler2D swap2Texture;
				uniform sampler2D swapDisplacement;

				float circle( in vec2 _st, in float _radius, in float blurriness ) {
					vec2 dist = _st;
					return 1. - smoothstep( _radius - ( _radius * blurriness ), _radius + ( _radius * blurriness ), dot( dist, dist ) * 4.0 );
				}

				void main( void ) {
					// our texture coords
					vec2 textureCoords = vTextureCoord;

					//vec2 st = textureCoords - vec2(0.5);
					// Manage the device ratio
					vec2 res = uRes * uPR;
					vec2 st = gl_FragCoord.xy / res.xy - vec2(0.5);
					// tip: use the following formula to keep the good ratio of your coordinates
					st.y *= uRes.y / uRes.x;
					
					vec2 mouse = uMousePosition * -0.5;
					// tip2: do the same for your mouse
					mouse.y *= uRes.y / uRes.x;
					//mouse *= -1.;

					vec2 circlePos = st + mouse;
					float c = circle( circlePos, 0.3 * uScale, 2. );

					// our displacement texture
					vec4 displacementTexture = texture2D(swapDisplacement, textureCoords);

					// our displacement factor is a float varying from 1 to 0 based on the timer
					float displacementFactor = ( 1.0 - (cos(uTime / (60.0 / 3.141592)) + 1.0) / 2.0 * uEffect ) * c;

					// the effect factor will tell which way we want to displace our pixels
					// the farther from the center of the videos, the stronger it will be
					vec2 effectFactor = vec2( (textureCoords.x - 0.5) * 0.75 * uEffect, (textureCoords.y - 0.5) * 0.75 * uEffect );

					// calculate our displaced coordinates of the first video
					vec2 firstDisplacementCoords = vec2(vSwapTextureCoord.x - displacementFactor * (displacementTexture.r * effectFactor.x), vSwapTextureCoord.y - displacementFactor * (displacementTexture.r * effectFactor.y));
					// opposite displacement effect on the second video
					vec2 secondDisplacementCoords = vec2(vSwap2TextureCoord.x - (1.0 - displacementFactor) * (displacementTexture.r * effectFactor.x), vSwap2TextureCoord.y - (1.0 - displacementFactor) * (displacementTexture.r * effectFactor.y));

					// apply the textures
					vec4 firstDistortedColor = texture2D(swapTexture, firstDisplacementCoords);
					vec4 secondDistortedColor = texture2D(swap2Texture, secondDisplacementCoords);

					// blend both textures based on our displacement factor
					vec4 finalColor = mix(firstDistortedColor, secondDistortedColor, displacementFactor);

					// handling premultiplied alpha
					finalColor = vec4(finalColor.rgb * finalColor.a, finalColor.a);

					// apply our shader
					gl_FragColor = finalColor;
				}
			`;
			return {
				vertexShader: vs,		// our vertex shader ID
				fragmentShader: fs,		// our fragment shader ID
//				widthSegments: 10,
//				heightSegments: 10,
				imageCover: false,		// our displacement texture has to fit the plane
				uniforms: {				// variables passed to shaders
					mousePosition: { // our mouse position
						name: "uMousePosition",
						type: "2f",
						value: [ mousePosition.x, mousePosition.y ]
					},
					imageResolution: {
						name: "uRes",
						type: "2f",
						value: [ img.clientWidth, img.clientHeight ]
					},
					pixelRatio: {
						name: "uPR",
						type: "1f",
						value: window.devicePixelRatio.toFixed(1)
					},
					scaleFactor: {
						name: "uScale",
						type: "1f",
						value: 0
					},
					useEffect: {
						name: "uEffect",
						type: "1f",
						value: displacement_url != '' ? 1 : 0
					},
					time: {
						name: "uTime",
						type: "1f",
						value: 0
					}
				}
			};
		}

		// handle plane
		function handle_plane( plane ) {
			plane
				.onLoading(function() {
//					console.log(plane.loadingManager.sourcesLoaded);
				})
				.onReady(function() {
//					console.log('Plane '+index+' of '+total+' is ready');
					// first render plane
					if ( ! permanentDrawing ) curtains.needRender();
					// init tweens
					plane.tweenScale = null;
					// now that our plane is ready we can listen to mouse move event
					function mouse_move(e) {
						if ( ! mouseIn ) {
							mouse_enter();
						}
						handle_movement(e, plane);
					}
					function mouse_enter() {
						if ( ! mouseIn ) {
							mouseIn = true;
							activeImage = 1;
							change_scale( 1 );
						}
					}
					function mouse_leave() {
						if ( mouseIn ) {
							mouseIn = false;
							activeImage = 0;
							change_scale( 0 );
						}
					}
					elm.addEventListener("mousemove",  mouse_move );
					elm.addEventListener("touchmove",  mouse_move );
					elm.addEventListener("mouseenter", mouse_enter );
					elm.addEventListener("touchstart", mouse_enter );
					elm.addEventListener("mouseleave", mouse_leave );
					elm.addEventListener("touchend",   mouse_leave );
				})
				.onRender(function() {
					plane.uniforms.time.value = activeImage == 1 ? Math.min(60, plane.uniforms.time.value + 1) : Math.max(0, plane.uniforms.time.value - 1);
				})
				.onAfterResize(function() {
//					console.log('Plane '+index+' of '+total+' is resized');
				});

			// Change scale
			function change_scale( to ) {
				if ( plane.tweenScale ) {
					trx_addons_tween_stop( plane.tweenScale );
				}
				if ( ! permanentDrawing && to == 1 ) {
					curtains.enableDrawing();
				}
				plane.tweenScale = trx_addons_tween_value( {
					start: scaleFactor,
					end: to,
					time: 1.25,
					callbacks: {
						onUpdate: function(value) {
							scaleFactor = value;
							plane.uniforms.scaleFactor.value = value;
							if ( scaleOnHover ) {
								plane.textures && plane.textures[0].setScale(1 + value / 12, 1 + value / 12);
							}
						},
						onComplete: function() {
							trx_addons_tween_stop( plane.tweenScale );
							plane.tweenScale = null;
							if ( ! permanentDrawing && to == 0 ) {
								curtains.disableDrawing();
							}
						}
					}
				} );
			}
		}

		// handle the mouse move event
		function handle_movement(e, plane) {

			var mouse = get_mouse_position_from_event( e );

			// lerp the mouse position a bit to smoothen the overall effect
			mousePosition.x = trx_addons_lerp(mousePosition.x, mouse.x, 0.3);
			mousePosition.y = trx_addons_lerp(mousePosition.y, mouse.y, 0.3);

			// convert our mouse/touch position to coordinates relative to the vertices of the plane
			var mouseCoords = globalCanvas
								? plane.mouseToPlaneCoords(mousePosition.x, mousePosition.y)
								: mouse_to_plane_coords( plane, mousePosition );
			

			// update our mouse position uniform
			plane.uniforms.mousePosition.value = [mouseCoords.x, mouseCoords.y];
		}
	};



	// Effect 'Blot'
	//-------------------------------------------
	
	// Create plane.
	// Attention! All callbacks must be in a global scope!
	window.trx_addons_image_effects_callback_blot = function( curtains_global, elm, index, total ) {

		var curtains = curtains_global ? curtains_global : null;

		// Common vars
		var mouseIn = false;

		// track the mouse positions to send it to the shaders
		var mousePosition = {
			x: 0,
			y: 0
		};

		var scaleOnHover = elm.getAttribute('data-image-effect-scale') > 0,
			scaleFactor = 0,
			tintColor = elm.getAttribute('data-image-effect-tint-color') || '';


		var effectStrength = Math.max( 5.0, Math.min( 50.0, elm.getAttribute('data-image-effect-strength') || 30.0 ) );
		var wavesDirection = Math.max(0.0, Math.min(1.0, elm.getAttribute('data-image-effect-waves-direction') ) );

		var activeImage = 0;

		// Curtains init
		var plane = null,
			parent = null,
			img_all = elm.querySelectorAll( 'img:not([class*="trx_addons_image_effects_"])' ),
			img = img_all.length > 1
					? elm.querySelector( 'img:not([class*="avatar"]):not([class*="trx_addons_extended_taxonomy_img"])' )
					: elm.querySelector( 'img' );

		if ( img ) {
			if ( img_all.length > 1 ) {
				jQuery( img ).wrap( '<div class="trx_addons_image_effects_holder"></div>' );
			}
			parent = img.parentNode;
			if ( img_all.length == 1 ) {
				parent.classList.add('trx_addons_image_effects_holder');
			}
//			img.setAttribute('crossorigin', 'anonymous');
			img.setAttribute('data-sampler', 'swapTexture');
			if ( img.getAttribute( 'decoding' ) == 'async' ) {
				img.setAttribute( 'decoding', 'sync' );
			}
			var swap_url = elm.getAttribute('data-image-effect-swap-image');
			if ( swap_url ) {
				var swap_img = document.createElement("img");
//				swap_img.setAttribute('crossorigin', 'anonymous');
				swap_img.setAttribute('src', swap_url);
				swap_img.setAttribute('data-sampler', 'swap2Texture');
				if ( swap_img.getAttribute( 'decoding' ) == 'async' ) {
					swap_img.setAttribute( 'decoding', 'sync' );
				}
					swap_img.classList.add('trx_addons_image_effects_swap_image');
				parent.appendChild(swap_img);
				$document.on('action.after_add_content', function(e, cont) {
					cont.find( '.trx_addons_image_effects_swap_image' ).remove();
				});
			}
			if ( ! globalCanvas ) {
				curtains = create_canvas(parent);
				if ( curtains ) jQuery(elm).data('curtains', curtains);
			}
			if ( curtains ) {
				if ( ! permanentDrawing ) curtains.disableDrawing();
				plane = curtains.addPlane( parent, get_params( elm, img, parent ) );
				if ( plane ) {
					jQuery(elm).data('curtains-plane', plane);
					handle_plane( plane );
				}
			}
		}

		// Return plane params
		function get_params() {
			var vs = `
				#ifdef GL_ES
					precision mediump float;
				#endif

				// default mandatory variables
				attribute vec3 aVertexPosition;
				attribute vec2 aTextureCoord;

				uniform mat4 uPMatrix;
				uniform mat4 uMVMatrix;

				// our texture matrices
				// notice how it matches our data-sampler attributes + "Matrix"
				uniform mat4 swapTextureMatrix;
				uniform mat4 swap2TextureMatrix;

				// our displacement texture will use original texture coords attributes
				varying vec2 vTextureCoord;

				// our image will use texture coords based on their texture matrices
				varying vec2 vSwapTextureCoord;
				varying vec2 vSwap2TextureCoord;

				void main() {
					// texture coords attributes because we want our displacement texture to be contained
					// our image texture coords based on their texture matrices
					vTextureCoord = aTextureCoord;
					vSwapTextureCoord = (swapTextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy;
					vSwap2TextureCoord = (swap2TextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy;

					gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
				}
			`;

			var fs = `
				#ifdef GL_ES
					precision mediump float;
				#endif

				// all our varying variables
				//varying vec3 vVertexPosition;
				varying vec2 vTextureCoord;
				varying vec2 vSwapTextureCoord;
				varying vec2 vSwap2TextureCoord;

				// custom uniforms
				uniform float uPR;
				uniform float uScale;
				uniform float uTint;
				uniform float uWavesTime;
				uniform float uTime;

				uniform vec2 uRes;
				uniform vec2 uMousePosition;
				uniform vec3 uColor;

				// our textures samplers
				// notice how it matches our data-sampler attributes
				uniform sampler2D swapTexture;
				uniform sampler2D swap2Texture;

				uniform float uEffectStrength;	// 5.0 - 50.0 - waves amplitude
				uniform float uWavesDirection;	// 0 - horizontal, 1 - vertical

				// noise 2d generator
				//---------------------------------------------------
				vec3 permute3(vec3 x) {
					return mod((x*34.0+1.0)*x, 289.0);
				}
				float snoise2(vec2 v) {
					const vec4 C = vec4(0.211325, 0.366025, -0.57735, 0.02439);
					vec2 i = floor(v + dot(v, C.yy)),
						 x0 = v - i + dot(i, C.xx),
						 i1;
					i1 = x0.x > x0.y ? vec2(1.0, 0.0) : vec2(0.0,1.0);
					vec4 x12 = x0.xyxy + C.xxzz;
					x12.xy -= i1,
					i = mod(i, 289.0);
					vec3 p = permute3(permute3(i.y + vec3(0.0, i1.y, 1.0)) + i.x + vec3(0.0,i1.x, 1.0)),
						 m = max(0.5 - vec3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), 0.0);
					m = m * m, m = m * m;
					vec3 x = 2.0 * fract(p * C.www) - 1.0,
						 h = abs(x) - 0.5,
						 ox = floor(x + 0.5),
						 a0 = x - ox;
					m *= 1.792843 - 0.853735 * (a0 * a0 + h * h);
					vec3 g;
					g.x = a0.x * x0.x + h.x * x0.y,
					g.yz = a0.yz * x12.xz + h.yz * x12.yw;
					return 130.0 * dot(m, g);
				}

				// noise 3d generator
				//-------------------------------------------
				vec3 mod289(vec3 x) {
					return x - floor(x * (1.0 / 289.0)) * 289.0;
				}
				vec4 mod289(vec4 x) {
					return x - floor(x * (1.0 / 289.0)) * 289.0;
				}
				vec4 permute4(vec4 x) {
					return mod289(((x*34.0)+1.0)*x);
				}
				vec4 taylorInvSqrt(vec4 r) {
					return 1.79284291400159 - 0.85373472095314 * r;
				}
				float snoise3(vec3 v) {
					const vec2  C = vec2(1.0/6.0, 1.0/3.0) ;
					const vec4  D = vec4(0.0, 0.5, 1.0, 2.0);
					// First corner
					vec3 i  = floor(v + dot(v, C.yyy) );
					vec3 x0 =   v - i + dot(i, C.xxx) ;
					// Other corners
					vec3 g = step(x0.yzx, x0.xyz);
					vec3 l = 1.0 - g;
					vec3 i1 = min( g.xyz, l.zxy );
					vec3 i2 = max( g.xyz, l.zxy );

					//   x0 = x0 - 0.0 + 0.0 * C.xxx;
					//   x1 = x0 - i1  + 1.0 * C.xxx;
					//   x2 = x0 - i2  + 2.0 * C.xxx;
					//   x3 = x0 - 1.0 + 3.0 * C.xxx;
					vec3 x1 = x0 - i1 + C.xxx;
					vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y
					vec3 x3 = x0 - D.yyy;      // -1.0+3.0*C.x = -0.5 = -D.y

					// Permutations
					i = mod289(i); 
					vec4 p = permute4( permute4( permute4( 
					         i.z + vec4(0.0, i1.z, i2.z, 1.0 ))
					       + i.y + vec4(0.0, i1.y, i2.y, 1.0 )) 
					       + i.x + vec4(0.0, i1.x, i2.x, 1.0 ));

					// Gradients: 7x7 points over a square, mapped onto an octahedron.
					// The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294)
					float n_ = 0.142857142857; // 1.0/7.0
					vec3  ns = n_ * D.wyz - D.xzx;

					vec4 j = p - 49.0 * floor(p * ns.z * ns.z);  //  mod(p,7*7)

					vec4 x_ = floor(j * ns.z);
					vec4 y_ = floor(j - 7.0 * x_ );    // mod(j,N)

					vec4 x = x_ *ns.x + ns.yyyy;
					vec4 y = y_ *ns.x + ns.yyyy;
					vec4 h = 1.0 - abs(x) - abs(y);

					vec4 b0 = vec4( x.xy, y.xy );
					vec4 b1 = vec4( x.zw, y.zw );

					//vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0;
					//vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0;
					vec4 s0 = floor(b0)*2.0 + 1.0;
					vec4 s1 = floor(b1)*2.0 + 1.0;
					vec4 sh = -step(h, vec4(0.0));

					vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ;
					vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ;

					vec3 p0 = vec3(a0.xy,h.x);
					vec3 p1 = vec3(a0.zw,h.y);
					vec3 p2 = vec3(a1.xy,h.z);
					vec3 p3 = vec3(a1.zw,h.w);

					//Normalise gradients
					vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));
					p0 *= norm.x;
					p1 *= norm.y;
					p2 *= norm.z;
					p3 *= norm.w;

					// Mix final noise value
					vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
					m = m * m;
					return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1), 
					                            dot(p2,x2), dot(p3,x3) ) );
				}

				// circle
				//-------------------------------------------
				float circle( in vec2 _st, in float _radius, in float blurriness ) {
					vec2 dist = _st;
					return 1. - smoothstep( _radius - ( _radius * blurriness ), _radius + ( _radius * blurriness ), dot( dist, dist ) * 4.0 );
				}

				void main( void ) {
					vec2 v_uv = vTextureCoord;
					vec2 v_sv = vSwapTextureCoord;

					// Manage the device ratio
					vec2 res = uRes * uPR;
					vec2 st = gl_FragCoord.xy / res.xy - vec2(0.5);
					// tip: use the following formula to keep the good ratio of your coordinates
					st.y *= uRes.y / uRes.x;
					
					vec2 mouse = uMousePosition * -0.5;
					// tip2: do the same for your mouse
					mouse.y *= uRes.y / uRes.x;
					//mouse *= -1.;

					vec2 circlePos = st + mouse;
					// float c = circle( circlePos, 0.3 * uScale, 2.0 ) * 2.5;
					float c = circle( circlePos, 0.3 * uScale, 2.5 ) * 2.25;

					float offx = v_uv.x + sin( v_uv.y + uTime * 0.1 );
					float offy = v_uv.y - uTime * 0.1 - cos( uTime * 0.001 ) * 0.01;
					float n = snoise3( vec3( offx, offy, uTime * 0.1 ) * 8.0 ) - 1.0;

					float finalMask = smoothstep( 0.4, 0.5, n + pow( c, 2.0 ) );

					// Add waves to the first image
					const float PI = 3.141592;

					v_sv.x += (
								sin(v_sv.x * 10.0 * ( 2.0 - uWavesDirection ) + ((uWavesTime * (PI / 3.0)) * 0.031))
								+ sin(v_sv.y * 10.0 * ( 2.0 - uWavesDirection ) + ((uWavesTime * (PI / 2.489)) * 0.017))
								) / uEffectStrength / ( 2.5 + 1.5 * uWavesDirection ) * uScale;	// * 0.0075;

					v_sv.y += (
								sin(v_sv.y * 20.0 / ( 2.0 - uWavesDirection ) + ((uWavesTime * (PI / 2.023)) * 0.023))
								+ sin(v_sv.x * 20.0 / ( 2.0 - uWavesDirection ) + ((uWavesTime * (PI / 3.1254)) * 0.037))
								) / uEffectStrength / ( 2.5 + 1.5 * ( 1.0 - uWavesDirection ) ) * uScale;	// * 0.0125;

					vec4 imageColor = texture2D( swapTexture, v_sv );

					// Add tint the second image
					vec4 hoverColor = texture2D( swap2Texture, vSwap2TextureCoord );
					vec4 tint = vec4( uColor.r / 255.0, uColor.g / 255.0, uColor.b / 255.0, 1.0 );
					hoverColor = uTint > 0.0 ? mix( hoverColor, tint, 0.5) : hoverColor;

					gl_FragColor = mix( imageColor, hoverColor, finalMask );
				}
			`;

			return {
				vertexShader: vs,		// our vertex shader ID
				fragmentShader: fs,		// our fragment shader ID
//				widthSegments: 10,
//				heightSegments: 10,
				imageCover: false,		// our displacement texture has to fit the plane
				uniforms: {				// variables passed to shaders
					mousePosition: { // our mouse position
						name: "uMousePosition",
						type: "2f",
						value: [ mousePosition.x, mousePosition.y ]
					},
					imageResolution: {
						name: "uRes",
						type: "2f",
						value: [ img.clientWidth, img.clientHeight ]
					},
					pixelRatio: {
						name: "uPR",
						type: "1f",
						value: window.devicePixelRatio.toFixed(1)
					},
					scaleFactor: {
						name: "uScale",
						type: "1f",
						value: 0
					},
					color: {
						name: "uColor",
						type: "3f",
						value: tintColor ? trx_addons_rgb2components( tintColor ) : [0, 0, 0]
					},
					useColor: {
						name: "uTint",
						type: "1f",
						value: tintColor ? 1 : 0
					},
					effectStrength: {
						name: "uEffectStrength",
						type: "1f",
						value: effectStrength
					},
					wavesDirection: {
						name: "uWavesDirection",
						type: "1f",
						value: wavesDirection
					},
					wavesTime: {
						name: "uWavesTime",
						type: "1f",
						value: 0
					},
					time: {
						name: "uTime",
						type: "1f",
						value: 0
					}
				}
			};
		}

		// handle plane
		function handle_plane( plane ) {
			plane
				.onLoading(function() {
//					console.log(plane.loadingManager.sourcesLoaded);
				})
				.onReady(function() {
//					console.log('Plane '+index+' of '+total+' is ready');
					// first render plane
					if ( ! permanentDrawing ) curtains.needRender();
					// init tweens
					plane.tweenScale = null;
					// now that our plane is ready we can listen to mouse move event
					function mouse_move(e) {
						if ( ! mouseIn ) {
							mouse_enter();
						}
						handle_movement(e, plane);
					}
					function mouse_enter() {
						if ( ! mouseIn ) {
							mouseIn = true;
							activeImage = 1;
							change_scale( 1 );
						}
					}
					function mouse_leave() {
						if ( mouseIn ) {
							mouseIn = false;
							activeImage = 0;
							change_scale( 0 );
						}
					}
					elm.addEventListener("mousemove",  mouse_move );
					elm.addEventListener("touchmove",  mouse_move );
					elm.addEventListener("mouseenter", mouse_enter );
					elm.addEventListener("touchstart", mouse_enter );
					elm.addEventListener("mouseleave", mouse_leave );
					elm.addEventListener("touchend",   mouse_leave );
				})
				.onRender(function() {
					plane.uniforms.time.value += 0.01;
					plane.uniforms.wavesTime.value++;
				})
				.onAfterResize(function() {
//					console.log('Plane '+index+' of '+total+' is resized');
				});

			// Change scale
			function change_scale( to ) {
				if ( plane.tweenScale ) {
					trx_addons_tween_stop( plane.tweenScale );
				}
				if ( ! permanentDrawing && to == 1 ) {
					curtains.enableDrawing();
				}
				plane.tweenScale = trx_addons_tween_value( {
					start: scaleFactor,
					end: to,
					time: 0.6,
					callbacks: {
						onUpdate: function(value) {
							scaleFactor = value;
							plane.uniforms.scaleFactor.value = value;
							if ( scaleOnHover ) {
								plane.textures && plane.textures[0].setScale(1 + value / 12, 1 + value / 12);
							}
						},
						onComplete: function() {
							trx_addons_tween_stop( plane.tweenScale );
							plane.tweenScale = null;
							if ( ! permanentDrawing && to == 0 ) {
								curtains.disableDrawing();
							}
						}
					}
				} );
			}
		}

		// handle the mouse move event
		function handle_movement(e, plane) {

			var mouse = get_mouse_position_from_event( e );

			// lerp the mouse position a bit to smoothen the overall effect
			mousePosition.x = trx_addons_lerp(mousePosition.x, mouse.x, 0.3);
			mousePosition.y = trx_addons_lerp(mousePosition.y, mouse.y, 0.3);

			// convert our mouse/touch position to coordinates relative to the vertices of the plane
			var mouseCoords = globalCanvas
								? plane.mouseToPlaneCoords(mousePosition.x, mousePosition.y)
								: mouse_to_plane_coords( plane, mousePosition );

			// update our mouse position uniform
			plane.uniforms.mousePosition.value = [mouseCoords.x, mouseCoords.y];
		}
	};



	// Effect 'Scroller'
	//-------------------------------------------
	
	// Create plane.
	// Attention! All callbacks must be in a global scope!
	window.trx_addons_image_effects_callback_scroller = function( curtains_global, elm, index, total ) {

		var curtains = curtains_global ? curtains_global : null;

		var mouseIn = false;

		var scaleOnHover = elm.getAttribute('data-image-effect-scale') > 0,
			scaleFactor = 0;

		var scrollDirection = 0,
			scrollY = -1;

		var effectStrength = 60 - Math.max( 5.0, Math.min( 50.0, elm.getAttribute('data-image-effect-strength') || 30 ) ),
			effectDirection = elm.getAttribute('data-image-effect-waves-direction') || 0;

		var plane = null,
			parent = null,
			img_all = elm.querySelectorAll( 'img:not([class*="trx_addons_image_effects_"])' ),
			img = img_all.length > 1
					? elm.querySelector( 'img:not([class*="avatar"]):not([class*="trx_addons_extended_taxonomy_img"])' )
					: elm.querySelector( 'img' );

		if ( img ) {
			if ( img_all.length > 1 ) {
				jQuery( img ).wrap( '<div class="trx_addons_image_effects_holder"></div>' );
			}
			parent = img.parentNode;
			if ( img_all.length == 1 ) {
				parent.classList.add('trx_addons_image_effects_holder');
			}
//			img.setAttribute('crossorigin', 'anonymous');
			img.setAttribute('data-sampler', 'scrollerTexture');
			if ( img.getAttribute( 'decoding' ) == 'async' ) {
				img.setAttribute( 'decoding', 'sync' );
			}
			if ( ! globalCanvas ) {
				curtains = create_canvas(parent);
				if ( curtains ) jQuery(elm).data('curtains', curtains);
			}
			if ( curtains ) {
				plane = curtains.addPlane( parent, get_params( elm, img, parent ) );
				if ( plane ) {
					jQuery(elm).data('curtains-plane', plane);
					handle_plane( plane );
				}
			}
		}

		// Return plane params
		function get_params( elm, img, parent ) {

			var vs = `
				#ifdef GL_ES
					precision mediump float;
				#endif

				attribute vec3 aVertexPosition;
				attribute vec2 aTextureCoord;

				uniform mat4 uMVMatrix, uPMatrix;

				uniform float uScrollSpeed;
				uniform float uEffectStrength;
				uniform float uEffectDirection;
				uniform float uPadding;

				// our texture matrices
				// notice how it matches our data-sampler attributes + "Matrix"
				uniform mat4 scrollerTextureMatrix;

				// if you want to pass your vertex and texture coords to the fragment shader
				varying vec3 vVertexPosition;
				varying vec2 vScrollerTextureCoord;

				void main(){
					vec3 vertexPosition = aVertexPosition;
					if ( uEffectDirection > 0.0 ) {
						// Vertical wave
						vertexPosition.x += sin( ( vertexPosition.y + 1.0 ) * 3.141592 ) * sin( uScrollSpeed * 3.141592 ) * 0.05 * ( uEffectStrength / 30.0 );
					} else {
						// Horizontal wave
						vertexPosition.y += sin( ( vertexPosition.x + 1.0 ) / 2.0 * 3.141592 ) * sin( uScrollSpeed * 3.141592 ) * 0.05 * ( uEffectStrength / 30.0 );
					}
					vVertexPosition = vertexPosition;
					gl_Position = uPMatrix * uMVMatrix * vec4( vertexPosition, 1.0 + uPadding * ( uEffectStrength / 30.0 ) );

					vScrollerTextureCoord = ( scrollerTextureMatrix * vec4( aTextureCoord, 0.0, 1.0 ) ).xy;
				}
			`;
			var fs = `
				#ifdef GL_ES
					precision mediump float;
				#endif

				// all our varying variables
				varying vec2 vScrollerTextureCoord;

				uniform sampler2D scrollerTexture;

				void main(){
					vec2 textureCoord = vScrollerTextureCoord;
					gl_FragColor = texture2D(scrollerTexture, textureCoord);
				}
			`;

			return {
				vertexShader: vs,
				fragmentShader: fs,
				widthSegments: 10,
				heightSegments: 10,
				uniforms: {
					padding: {
						name: "uPadding",
						type: "1f",
						value: trx_addons_apply_filters( 'trx_addons_filter_image_effects_padding', 0.05, 'scroller' )
					},
					effectStrength: {
						name: "uEffectStrength",
						type: "1f",
						value: effectStrength
					},
					effectDirection: {
						name: "uEffectDirection",
						type: "1f",
						value: effectDirection
					},
					scrollSpeed: {
						name: "uScrollSpeed",
						type: "1f",
						value: 0
					}
				}
			};
		}

		// handle plane
		function handle_plane( plane ) {
			plane
				.onReady(function() {
//					console.log('Plane '+index+' of '+total+' is ready');
					// init tweens
					plane.tween = null;
					plane.tweenScale = null;
					var scrollBusy = false,
						clear_scroll_busy = trx_addons_throttle( function() {
							scrollBusy = false;
						}, 300, true );

					function scroll_get() {
						return ( typeof window.scrollY != 'undefined' ? window.scrollY : window.pageYOffset ) || 0;
					}
					// now that our plane is ready we can listen to mouse move event
					function scroll_start(e) {
						clear_scroll_busy();
						if ( scrollY < 0 ) {
							scrollY = scroll_get();
						} else if ( ! scrollDirection ) {
							var scrollNew = scroll_get();
							scrollDirection = scrollNew > scrollY ? 1 : -1;
							scrollBusy = true;
							change_scroll( 1 );
						}
					}
					window.addEventListener("scroll",  scroll_start );
					// now that our plane is ready we can listen to mouse move event
					function mouse_move(e) {
						if ( ! mouseIn ) {
							mouse_enter();
						}
					}
					function mouse_enter() {
						if ( ! mouseIn ) {
							mouseIn = true;
							change_scale( 1 );
						}
					}
					function mouse_leave() {
						if ( mouseIn ) {
							mouseIn = false;
							change_scale( 0 );
						}
					}
					elm.addEventListener("mousemove",  mouse_move );
					elm.addEventListener("touchmove",  mouse_move );
					elm.addEventListener("mouseenter", mouse_enter );
					elm.addEventListener("touchstart", mouse_enter );
					elm.addEventListener("mouseleave", mouse_leave );
					elm.addEventListener("touchend",   mouse_leave );
				})
				.onRender(function() {
					//plane.uniforms.time.value++;
				});
		}

		// Change wave force value
		function change_scroll( to ) {
			if ( plane.tween ) {
				trx_addons_tween_stop( plane.tween );
			}
			plane.tween = trx_addons_tween_value( {
				start: plane.uniforms.scrollSpeed.value,
				end: to,
				time: 1.5,
				callbacks: {
					onUpdate: function(value) {
						plane.uniforms.scrollSpeed.value = value * scrollDirection;
					},
					onComplete: function() {
						scrollDirection = 0;
						scrollY = -1;
						plane.uniforms.scrollSpeed.value = 0;
						trx_addons_tween_stop( plane.tween );
						plane.tween = null;
					}
				}
			} );
		}

		// Change scale
		function change_scale( to ) {
			if ( plane.tweenScale ) {
				trx_addons_tween_stop( plane.tweenScale );
			}
			if ( ! permanentDrawing && to == 1 ) {
				curtains.enableDrawing();
			}
			plane.tweenScale = trx_addons_tween_value( {
				start: scaleFactor,
				end: to,
				time: 1.0,
				callbacks: {
					onUpdate: function(value) {
						scaleFactor = value;
						if ( scaleOnHover ) {
							plane.textures && plane.textures[0].setScale(1 + value / 12, 1 + value / 12);
						}
					},
					onComplete: function() {
						trx_addons_tween_stop( plane.tweenScale );
						plane.tweenScale = null;
					}
				}
			} );
		}
	};

})();