PageSourceSearch

https://www.spaceyearth.com.sg/wp-content/plugins/trx_addonss/addons/image-effects/image-effects.js

js spaceyearth.com.sg collected 2026-09-24 12:51:41 UTC 104,705 bytes, 3,417 lines download raw bytes

1/* global jQuery, TRX_ADDONS_STORAGE */
2
3(function() {
4
5	"use strict";
6
7	// Settings
8	var planeClassPrefix = 'trx_addons_image_effects_on_';		// class prefix of wrappers with image
9
10	var globalCanvas = false;									// true - one canvas for all images is used (twitches when scrolling the page),
11																// false - separate canvas for each image is created (smooth scrolling, but WebKit limit: maximum of 16 objects per page)
12
13	var permanentDrawing = globalCanvas || false;				// true - permanent redraw images on canvas,
14																// false - redraw only on ready and on hover
15
16	var curtains = null;										// global curtains object (used if globalCanvas is true)
17
18	var $document = jQuery(document);
19
20	var firstLoad = false;
21
22	var sliderInited = false;
23
24
25	window.addEventListener( 'load', function() {
26		if ( typeof trx_addons_apply_filters == 'function' ) {
27			globalCanvas = trx_addons_apply_filters( 'trx_addons_filter_image_effects_use_global_canvas', globalCanvas );
28			permanentDrawing = globalCanvas || false;
29		}
30		firstLoad = true;
31		create_planes();
32	} );
33
34	function create_planes() {
35
36		// not available in the edit mode of Elementor
37		if ( typeof window.elementorFrontend !== 'undefined' && elementorFrontend.isEditMode() ) {
38			return;
39		}
40		// exit if a module 'Curtains' is not available
41		if ( typeof window.Curtains == 'undefined' ) {
42			return;
43		}
44
45		// get our plane element
46		var planeElements = document.querySelectorAll( '[class*="' + planeClassPrefix + '"]:not(.trx_addons_image_effects_inited)'
47														+ ( jQuery('body').hasClass( 'allow_lazy_load' )
48															? '.lazyload_inited'
49															: '' )
50														);
51
52		// exit if no image effects are present on the current page
53		if ( planeElements.length === 0 ) return;
54
55		// create global canvas and append it to the body
56		if ( globalCanvas ) {
57			curtains = create_canvas( document.body );
58		}
59
60		// create planes and handle them
61		trx_addons_when_images_loaded( jQuery( planeElements ), function() {
62			var effect, total = 0;
63			for (var i = 0; i < planeElements.length; i++) {
64				if ( ! firstLoad && planeElements[i].closest( '.elementor-section-stretched' ) ) continue;
65				if ( ! sliderInited && planeElements[i].closest( '.slider-slide' ) ) continue;
66				total++;
67				if ( ! planeElements[i].classList.contains( 'trx_addons_image_effects_inited' ) && jQuery( planeElements[i] ).parents(':hidden').length === 0 ) {
68					effect = get_effect_name( planeElements[i] );
69					if ( effect && typeof window['trx_addons_image_effects_callback_' + effect] == 'function' ) {
70						window['trx_addons_image_effects_callback_' + effect]( curtains, planeElements[i], i, planeElements.length );
71						planeElements[i].classList.add("trx_addons_image_effects_inited");
72					}
73				}
74			}
75			// mark body as all planes are loaded
76			if ( total === planeElements.length ) {
77				document.body.classList.add("trx_addons_image_effects_planes_loaded");
78				$document.trigger('action.trx_addons_image_effects_inited', [planeElements]);
79			}
80		} );
81	}
82
83	$document.on( 'action.got_ajax_response', function() {
84		if ( firstLoad ) {
85			create_planes();
86		}
87	} );
88
89	$document.on( 'action.init_hidden_elements', function() {
90		if ( firstLoad ) {
91			create_planes();
92		}
93	} );
94
95	$document.on( 'action.slider_inited', function() {
96		if ( ! sliderInited ) {
97			sliderInited = true;
98			create_planes();
99		}
100	} );
101
102	$document.on( 'action.init_lazy_load_elements', function( e, element ) {
103		var parent = element.parents('[class*="trx_addons_image_effects_on_"]:not(.trx_addons_image_effects_inited)');
104		if ( parent.length > 0 ) {
105			parent.addClass('lazyload_inited');
106			// Hide and load image
107			parent.css({'opacity': 0, 'transition': 'opacity 0s ease'});
108			// Re-create canvas elements
109			create_planes();
110			// Fade in image
111			setTimeout(function(){
112				// Show loaded image	
113				parent.css({'opacity': 1, 'transition': 'opacity 0.3s ease'});
114				// Remove styles
115				setTimeout(function(){
116					parent.css({'opacity': '', 'transition': ''});
117				}, 300);
118			}, 100);
119		}	
120	} );
121
122	$document.on('action.before_remove_content action.deactivate_tab', function(e, 
122cont) {
123		cont.find( '.trx_addons_image_effects_inited' ).each( function() {
124			var $self = jQuery( this ),
125				$canvas = $self.find('.trx_addons_image_effects_canvas'),
126				curtains = $self.data('curtains');
127			if ( ! globalCanvas ) {
128				if ( curtains ) {
129					curtains.dispose();
130					curtains = null;
131					$self.removeData( 'curtains' );
132				}
133			} else {
134				var plane = $self.data('curtains-plane');
135				if ( plane ) {
136					curtains.removePlane( plane );
137					plane = null;
138					$self.removeData('curtains-plane');
139				}
140			}
141			$canvas.remove();
142		});
143	});
144
145	$document.on('action.after_add_content action.activate_tab', function(e, cont) {
146		cont.find( '.trx_addons_image_effects_inited' ).each( function() {
147			jQuery( this )
148				.removeClass('trx_addons_image_effects_inited')
149				.find( '[id^="trx_addons_image_effects_canvas_"]' ).remove().end()
150				.find( '.trx_addons_image_effects_holder' ).removeClass('trx_addons_image_effects_holder').end();
151		});
152	});
153
154
155	// Utilities
156	//-------------------------------------------
157
158	// Return name of image effect for element
159	function get_effect_name( elm ) {
160		var name = '';
161		for ( var i=0; i < elm.classList.length; i++ ) {
162			if ( elm.classList[i].indexOf(planeClassPrefix) === 0 ) {
163				name = elm.classList[i].substring( planeClassPrefix.length );
164				break;
165			}
166		}
167		return name;
168	}
169
170	// Return mouse coordinates relative to the current image
171	function get_mouse_position_from_event( e ) {
172		var mouse = {};
173		// touch event
174		if (e.targetTouches) {
175			mouse.x = globalCanvas ? e.targetTouches[0].clientX : e.targetTouches[0].layerX;
176			mouse.y = globalCanvas ? e.targetTouches[0].clientY : e.targetTouches[0].layerY;
177
178		// mouse event
179		} else {
180												// In Chrome with custom scroll mouse coordinates in layerXY are incorrect
181												// but in Mozilla mouse properties offsetXY always empty (equal to 0)
182			mouse.x = globalCanvas ? e.clientX : ( e.offsetX !== 0 || e.offsetY !== 0 ? e.offsetX : e.layerX );
183			mouse.y = globalCanvas ? e.clientY : ( e.offsetX !== 0 || e.offsetY !== 0 ? e.offsetY : e.layerY );
184		}
185		return mouse;
186	}
187
188	// Convert our mouse/touch position to coordinates relative to the vertices of the plane
189	function mouse_to_plane_coords( plane, mpos ) {
190		var w = plane.htmlElement.clientWidth, 	cw = w / 2,
191			h = plane.htmlElement.clientHeight,	ch = h / 2;
192		return {
193				x:  ( mpos.x - cw ) / cw,
194				y: -( mpos.y - ch ) / ch
195			};
196	}
197
198	// Add canvas holder
199	var total = 0;
200	function create_canvas( item ) {
201
202		// Append canvas holder to the item
203		var id = 'trx_addons_image_effects_canvas_'+total++,
204			div = document.createElement("div");
205		div.setAttribute('id', id);
206		div.setAttribute('class', 'trx_addons_image_effects_canvas');
207		item.appendChild(div);
208
209		// Set up our WebGL context and append the canvas to our wrapper
210		var webGLCurtain = new Curtains({
211			watchScroll: globalCanvas,
212			premultipliedAlpha: true,	// to avoid gray edge on images in effects 'waves', 'smudge', etc. (who changes the image border geometry)
213			container: id	// if not specified - library create own canvas container
214		});
215
216		// Handling errors
217		webGLCurtain
218			.onError(function() {
219				// we will add a class to the document body to display original images
220				document.body.classList.add("no-curtains", "trx_addons_image_effects_planes_loaded");
221			})
222			.onContextLost(function() {
223				// on context lost, try to restore the context
224				webGLCurtain.restoreContext();
225			});
226
227		return webGLCurtain;
228	}
229
230
231
232	// Effect 'Waves'
233	//-------------------------------------------
234	
235	// Create plane.
236	// Attention! All callbacks must be in a global scope!
237	window.trx_addons_image_effects_callback_waves = function( curtains_global, elm, index, total ) {
238
239		var curtains = curtains_global ? curtains_global : null;
240
241		var waveForceMin = 0,
242			waveForceMax = 7;
243
244		var waveFactor = elm.getAttribute('data-image-effect-waves-factor') || 4;
245
246		var mouseIn = false;
247
248		var scaleOnHover = elm.getAttribute('data-image-effect-scale') > 0,
249			scaleFactor = 0;
250
251		var paddingOnHover = typeof trx_addons_apply_filters == 'function'
252								? trx_addons_apply_filters( 'trx_addons_filter_image_effects_padding', 0.04, 'waves' )
253								: 0.04;
254
255		var effectStrength = Math.max( 5.0, Math.min( 50.0, elm.getAttribute('data-image-effect-strength') || 30.0 ) );
256
257		var plane = null,
258			parent = null,
259			img_all = elm.querySelectorAll( 'img:not([class*="trx_addons_image_effects_"])' ),
260			img = img_all.length > 1
261					? elm.querySelector( 'img:not([class*="avatar"]):not([class*="trx_addons_extended_taxonomy_img"])' )
262					: elm.querySelector( 'img' );
263
264		if ( img ) {
265			if ( img_all.length > 1 ) {
266				jQuery( img ).wrap( '<div class="trx_addons_image_effects_holder"></div>' );
267			}
268			parent = img.parentNode;
269			if ( img_all.length == 1 ) {
270				parent.classList.add('trx_addons_image_effects_holder');
271			}
272//			img.setAttribute('crossorigin', 'anonymous');
273			img.setAttribute('data-sampler', 'wavesTexture');
274			if ( img.getAttribute( 'decoding' ) == 'async' ) {
275				img.setAttribute( 'decoding', 'sync' );
276			}
277			if ( ! globalCanvas ) {
278				curtains = create_canvas(parent);
279				if ( curtains ) jQuery(elm).data('curtains', curtains);
280			}
281			if ( curtains ) {
282				if ( ! permanentDrawing ) curtains.disableDrawing();
283				plane = curtains.addPlane( parent, get_params( elm, img, parent ) );
284				if ( plane ) {
285					jQuery(elm).data('curtains-plane', plane);
286					handle_plane( plane );
287				}
288			}
289		}
290
291		// Return plane params
292		function get_params() {
293			var vs = `
294				#ifdef GL_ES
295					precision mediump float;
296				#endif
297
298				// those are the mandatory attributes that the lib sets
299				attribute vec3 aVertexPosition;
300				attribute vec2 aTextureCoord;
301
302				// those are mandatory uniforms that the lib sets and that contain our model view and projection matrix
303				uniform mat4 uMVMatrix;
304				uniform mat4 uPMatrix;
305
306				// our texture matrix uniform
307				uniform mat4 wavesTextureMatrix;
308
309				// if you want to pass your vertex and texture coords to the fragment shader
310				varying vec3 vVertexPosition;
311				varying vec2 vTextureCoord;
312
313				// effect control vars declared inside our javascript
314				uniform float uTime;
315				uniform float uMouseMoveStrength;
316				uniform float uEffectStrength;
317				uniform float uWaveFactor;
318				uniform vec2 uMousePosition;
319				uniform float hoveringWaveForce;
320				uniform float uPadding;
321
322				void main() {
323					vec3 vertexPosition = aVertexPosition;
324					float distanceFromMouse = distance(uMousePosition, vec2(vertexPosition.x, vertexPosition.y));
325					float waveSinusoid = cos(uWaveFactor * (distanceFromMouse - (uTime / 75.0)));
326					float distanceStrength = 0.4 / (distanceFromMouse + 0.4);
327					float distortionEffect = distanceStrength * waveSinusoid * uMouseMoveStrength / uEffectStrength;
328					vertexPosition.z +=  distortionEffect;
329					vertexPosition.x +=  distortionEffect * (uMousePosition.x - vertexPosition.x) * hoveringWaveForce * 3.0;
330					vertexPosition.y +=  distortionEffect * (uMousePosition.y - vertexPosition.y) * hoveringWaveForce;
331					gl_Position = uPMatrix * uMVMatrix * vec4(vertexPosition, (1.0 + uPadding));
332					vTextureCoord = (wavesTextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy;
333					vVertexPosition = vertexPosition;
334				}
335			`;
336			
337			var fs = `
338				#ifdef GL_ES
339					precision mediump float;
340				#endif
341				// get our varying variables
342				varying vec3 vVertexPosition;
343				varying vec2 vTextureCoord;
344
345				// our texture sampler
346				uniform sampler2D wavesTexture;
347
348				// effect control vars
349				uniform float displacement;
350
351				void main() {
352					if ( false ) {
353						float intensity = 1.0;
354						vec2 textureCoord = vTextureCoord;
355						vec4 image1 = texture2D(wavesTexture, textureCoord);
356						vec4 image2 = texture2D(wavesTexture, textureCoord);
357						vec4 texture1 = texture2D(wavesTexture, vec2(textureCoord.x, textureCoord.y + displacement * (image2 * intensity)));
358						vec4 texture2 = texture2D(wavesTexture, vec2(textureCoord.x, textureCoord.y + (1.0 - displacement) * (image1 * intensity)));
359						vec4 result = mix(texture1, texture2, displacement);
360						gl_FragColor = result;
361					} else {
362						vec2 textureCoord = vTextureCoord;
363						gl_FragColor = texture2D(wavesTexture, textureCoord);
364					}
365				}
366			`;
367
368			return {
369				vertexShader: vs,
370				fragmentShader: fs,
371				widthSegments: 10,
372				heightSegments: 10,
373				uniforms: {
374					time: {
375						name: "uTime",
376						type: "1f",
377						value: 0
378					},
379					mousePosition: {
380						name: "uMousePosition",
381						type: "2f",
382						value: [-.5, .5]
383					},
384					mouseMoveStrength: {
385						name: "uMouseMoveStrength",
386						type: "1f",
387						value: .2
388					},
389					effectStrength: {
390						name: "uEffectStrength",
391						type: "1f",
392						value: effectStrength
393					},
394					displacement: {
395						name: "displacement",
396						type: "1f",
397						value: 0
398					},
399					waveFactor: {
400						name: "uWaveFactor",
401						type: "1f",
402						value: waveFactor
403					},
404					hoveringWaveForce: {
405						name: "hoveringWaveForce",
406						type: "1f",
407						value: waveForceMin
408					},
409					padding: {
410						name: "uPadding",
411						type: "1f",
412						value: 0
413					}
414				}
415			};
416		}
417
418		// handle plane
419		function handle_plane( plane ) {
420			plane
421				.onLoading(function() {
422//					console.log('Waves onLoading');
423				})
424				.onReady(function() {
425//					console.log('Waves onReady: Plane '+index+' of '+total+' is ready');
426					// first render plane
427					if ( ! permanentDrawing ) curtains.needRender();
428					// init tweens
429					plane.tweenForce = null;
430					plane.tweenScale = null;
431					plane.tweenPadding = null;
432					// now that our plane is ready we can listen to mouse move event
433					function mouse_move() {
434						if ( ! mouseIn ) {
435							mouse_enter();
436						}
437					}
438					function mouse_enter() {
439						if ( ! mouseIn ) {
440							mouseIn = true;
441							change_wave_force( waveForceMax );
442							change_scale( 1 );
443							if ( paddingOnHover ) {
444								change_padding( paddingOnHover );
445							}
446						}
447					}
448					function mouse_leave() {
449						if ( mouseIn ) {
450							mouseIn = false;
451							change_wave_force( waveForceMin );
452							change_scale( 0 );
453							if ( paddingOnHover ) {
454								change_padding( 0 );
455							}
456						}
457					}
458					elm.addEventListener("mousemove",  mouse_move );
459					elm.addEventListener("touchmove",  mouse_move );
460					elm.addEventListener("mouseenter", mouse_enter );
461					elm.addEventListener("touchstart", mouse_enter );
462					elm.addEventListener("mouseleave", mouse_leave );
463					elm.addEventListener("touchend",   mouse_leave );
464				})
465				.onRender(function() {
466//					if ( globalCanvas ) plane.updatePosition();
467					plane.uniforms.time.value++;
468
469				})
470				.onAfterResize(function() {
471//					console.log('Waves afterResize: Plane '+index+' of '+total+' is resized');
472				});
473
474			// Change wave force value
475			function change_wave_force( to ) {
476				if ( plane.tweenForce ) {
477					trx_addons_tween_stop( plane.tweenForce );
478				}
479				plane.tweenForce = trx_addons_tween_value( {
480					start: plane.uniforms.hoveringWaveForce.value,
481					end: to,
482					time: 1.25,
483					callbacks: {
484						onUpdate: function(value) {
485							plane.uniforms.hoveringWaveForce.value = value;
486						},
487						onComplete: function() {
488							trx_addons_tween_stop( plane.tweenForce );
489							plane.tweenForce = null;
490						}
491					}
492				} );
493			}
494
495			// Change padding value
496			function change_padding( to ) {
497				if ( plane.tweenPadding ) {
498					trx_addons_tween_stop( plane.tweenPadding );
499				}
500				plane.tweenPadding = trx_addons_tween_value( {
501					start: plane.uniforms.padding.value,
502					end: to,
503					time: 1.25,
504					callbacks: {
505						onUpdate: function(value) {
506							plane.uniforms.padding.value = value;
507						},
508						onComplete: function() {
509							trx_addons_tween_stop( plane.tweenPadding );
510							plane.tweenPadding = null;
511						}
512					}
513				} );
514			}
515
516			// Change scale
517			function change_scale( to ) {
518				if ( plane.tweenScale ) {
519					trx_addons_tween_stop( plane.tweenScale );
520				}
521				if ( ! permanentDrawing ) {
522					curtains.enableDrawing();
523				}
524				plane.tweenScale = trx_addons_tween_value( {
525					start: scaleFactor,
526					end: to,
527					time: 1.25,
528					callbacks: {
529						onUpdate: function(value) {
530							scaleFactor = value;
531							if ( scaleOnHover ) {
532								plane.textures && plane.textures[0].setScale(1 + value / 12, 1 + value / 12);
533							}
534						},
535						onComplete: function() {
536							trx_addons_tween_stop( plane.tweenScale );
537							plane.tweenScale = null;
538							if ( ! permanentDrawing && to === 0 ) {
539								curtains.disableDrawing();
540							}
541						}
542					}
543				} );
544			}
545		}
546	};
547
548
549
550	// Effect 'Waves2'
551	//-------------------------------------------
552	
553	// Create plane.
554	// Attention! All callbacks must be in a global scope!
555	window.trx_addons_image_effects_callback_waves2 = function( curtains_global, elm, index, total ) {
556
557		var curtains = curtains_global ? curtains_global : null;
558
559		var waveForceMin = 0,
560			waveForceMax = 2;
561
562		var waveFactor = elm.getAttribute('data-image-effect-waves-factor') || 4;
563
564		var mouseIn = false;
565
566		var scaleOnHover = elm.getAttribute('data-image-effect-scale') > 0,
567			scaleFactor = 0;
568
569		var paddingOnHover = typeof trx_addons_apply_filters == 'function'
570								? trx_addons_apply_filters( 'trx_addons_filter_image_effects_padding', 0.04, 'waves2' )
571								: 0.04;
572
573		var effectStrength = Math.max( 5.0, Math.min( 50.0, elm.getAttribute('data-image-effect-strength') || 30.0 ) );
574
575		var plane = null,
576			parent = null,
577			img_all = elm.querySelectorAll( 'img:not([class*="trx_addons_image_effects_"])' ),
578			img = img_all.length > 1
579					? elm.querySelector( 'img:not([class*="avatar"]):not([class*="trx_addons_extended_taxonomy_img"])' )
580					: elm.querySelector( 'img' );
581
582		if ( img ) {
583			if ( img_all.length > 1 ) {
584				jQuery( img ).wrap( '<div class="trx_addons_image_effects_holder"></div>' );
585			}
586			parent = img.parentNode;
587			if ( img_all.length == 1 ) {
588				parent.classList.add('trx_addons_image_effects_holder');
589			}
590//			img.setAttribute('crossorigin', 'anonymous');
591			img.setAttribute('data-sampler', 'waves2Texture');
592			if ( img.getAttribute( 'decoding' ) == 'async' ) {
593				img.setAttribute( 'decoding', 'sync' );
594			}
595			if ( ! globalCanvas ) {
596				curtains = create_canvas(parent);
597				if ( curtains ) jQuery(elm).data('curtains', curtains);
598			}
599			if ( curtains ) {
600				if ( ! permanentDrawing ) curtains.disableDrawing();
601				plane = curtains.addPlane( parent, get_params( elm, img, parent ) );
602				if ( plane ) {
603					jQuery(elm).data('curtains-plane', plane);
604					handle_plane( plane );
605				}
606			}
607		}
608
609		// Return plane params
610		function get_params( elm, img, parent ) {
611			var vs = `
612				#ifdef GL_ES
613					precision mediump float;
614				#endif
615
616				// default mandatory variables
617				attribute vec3 aVertexPosition;
618				attribute vec2 aTextureCoord;
619
620				uniform mat4 uMVMatrix;
621				uniform mat4 uPMatrix;
622
623				// our texture matrix
624				uniform mat4 waves2TextureMatrix;
625
626				// custom variables
627				varying vec3 vVertexPosition;
628				varying vec2 vTextureCoord;
629				uniform float uTime;
630				uniform vec2 uResolution;
631				uniform vec2 uMousePosition;
632				uniform float uMouseMoveStrength;
633				uniform float uEffectStrength;
634				uniform float uWaveFactor;
635				uniform float uPadding;
636
637				void main() {
638					vec3 vertexPosition = aVertexPosition;
639					// get the distance between our vertex and the mouse position
640					float distanceFromMouse = distance(uMousePosition, vec2(vertexPosition.x, vertexPosition.y));
641					// calculate our wave effect
642					float waveSinusoid = cos(uWaveFactor * (distanceFromMouse - (uTime / 75.0)));
643					// attenuate the effect based on mouse distance
644					float distanceStrength = 0.4 / (distanceFromMouse + 0.4);
645					// calculate our distortion effect
646					float distortionEffect = distanceStrength * waveSinusoid * uMouseMoveStrength / uEffectStrength;
647					// apply it to our vertex position
648					vertexPosition.z += distortionEffect;
649					vertexPosition.x += distortionEffect * (uMousePosition.x - vertexPosition.x) * (uResolution.x / uResolution.y);
650					vertexPosition.y += distortionEffect * (uMousePosition.y - vertexPosition.y);
651					gl_Position = uPMatrix * uMVMatrix * vec4(vertexPosition, (1.0 + uPadding));
652					// varyings
653					vTextureCoord = (waves2TextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy;
654					vVertexPosition = vertexPosition;
655				}
656			`;
657			var fs = `
658				#ifdef GL_ES
659					precision mediump float;
660				#endif
661
662				varying vec3 vVertexPosition;
663				varying vec2 vTextureCoord;
664				uniform sampler2D waves2Texture;
665
666				void main() {
667					// apply our texture
668					vec4 finalColor = texture2D(waves2Texture, vTextureCoord);
669					// fake shadows based on vertex position along Z axis
670					finalColor.rgb -= clamp(-vVertexPosition.z, 0.0, 1.0);
671					// fake lights based on vertex position along Z axis
672					finalColor.rgb += clamp(vVertexPosition.z, 0.0, 1.0);
673					// handling premultiplied alpha (useful if we were using a png with transparency)
674					finalColor = vec4(finalColor.rgb * finalColor.a, finalColor.a);
675					gl_FragColor = finalColor;
676				}
677			`;
678
679			return {
680				vertexShader: vs,
681				fragmentShader: fs,
682				widthSegments: 20,
683				heightSegments: 20,
684				uniforms: {
685					resolution: { // resolution of our plane
686						name: "uResolution",
687						type: "2f", // notice this is an length 2 array of floats
688						value: [ parent.clientWidth, parent.clientHeight ]
689					},
690					time: { // time uniform that will be updated at each draw call
691						name: "uTime",
692						type: "1f",
693						value: 0
694					},
695					mousePosition: { // our mouse position
696						name: "uMousePosition",
697						type: "2f", // again an array of floats
698						value: [-.5, .5]
699					},
700					mouseMoveStrength: { // the mouse move strength
701						name: "uMouseMoveStrength",
702						type: "1f",
703						value: 0
704					},
705					effectStrength: { // the effect strength
706						name: "uEffectStrength",
707						type: "1f",
708						value: effectStrength
709					},
710					waveFactor: {    // waves frequency
711						name: "uWaveFactor",
712						type: "1f",
713						value: waveFactor
714					},
715					padding: { // padding around image on hover
716						name: "uPadding",
717						type: "1f",
718						value: 0
719					}
720				}
721			};
722		}
723
724		// handle plane
725		function handle_plane( plane ) {
726			plane
727				.onLoading(function() {
728					//console.log(plane.loadingManager.sourcesLoaded);
729				})
730				.onReady(function() {
731//					console.log('Plane '+index+' of '+total+' is ready');
732					// set a fov of 35 to reduce perspective
733					plane.setPerspective(35);
734					// first render plane
735					if ( ! permanentDrawing ) curtains.needRender();
736					// init tweens
737					plane.tween = null;
738					plane.tweenScale = null;
739					// now that our plane is ready we can listen to mouse move event
740					function mouse_move() {
741						if ( ! mouseIn ) {
742							mouse_enter();
743						}
744					}
745					function mouse_enter() {
746						if ( ! mouseIn ) {
747							mouseIn = true;
748							change_wave_force( waveForceMax );
749							change_scale( 1 );
750							if ( paddingOnHover ) {
751								change_padding( paddingOnHover );
752							}
753						}
754					}
755					function mouse_leave() {
756						if ( mouseIn ) {
757							mouseIn = false;
758							change_wave_force( waveForceMin );
759							change_scale( 0 );
760							if ( paddingOnHover ) {
761								change_padding( 0 );
762							}
763						}
764					}
765					elm.addEventListener("mousemove",  mouse_move );
766					elm.addEventListener("touchmove",  mouse_move );
767					elm.addEventListener("mouseenter", mouse_enter );
768					elm.addEventListener("touchstart", mouse_enter );
769					elm.addEventListener("mouseleave", mouse_leave );
770					elm.addEventListener("touchend",   mouse_leave );
771				})
772				.onRender(function() {
773					// increment our time uniform
774					plane.uniforms.time.value++;
775				})
776				.onAfterResize(function() {
777//					console.log('Plane '+index+' of '+total+' is resized');
778					/* Commented, because set width and height to 0 inside slider
779					var planeBoundingRect = plane.getBoundingRect();
780					plane.uniforms.resolution.value = [ planeBoundingRect.width, planeBoundingRect.height ];
781					*/
782				});
783		}
784
785		// Change wave force value
786		function change_wave_force( to ) {
787			if ( plane.tween ) {
788				trx_addons_tween_stop( plane.tween );
789			}
790			plane.tween = trx_addons_tween_value( {
791				start: plane.uniforms.mouseMoveStrength.value,
792				end: to,
793				time: 1.25,
794				callbacks: {
795					onUpdate: function(value) {
796						plane.uniforms.mouseMoveStrength.value = value;
797					},
798					onComplete: function() {
799						trx_addons_tween_stop( plane.tween );
800						plane.tween = null
801					}
802				}
803			} );
804		}
805
806		// Change padding value
807		function change_padding( to ) {
808			if ( plane.tweenPadding ) {
809				trx_addons_tween_stop( plane.tweenPadding );
810			}
811			plane.tweenPadding = trx_addons_tween_value( {
812				start: plane.uniforms.padding.value,
813				end: to,
814				time: 1.25,
815				callbacks: {
816					onUpdate: function(value) {
817						plane.uniforms.padding.value = value;
818					},
819					onComplete: function() {
820						trx_addons_tween_stop( plane.tweenPadding );
821						plane.tweenPadding = null;
822					}
823				}
824			} );
825		}
826
827		// Change scale
828		function change_scale( to ) {
829			if ( plane.tweenScale ) {
830				trx_addons_tween_stop( plane.tweenScale );
831			}
832			if ( ! permanentDrawing ) {
833				curtains.enableDrawing();
834			}
835			plane.tweenScale = trx_addons_tween_value( {
836				start: scaleFactor,
837				end: to,
838				time: 1.25,
839				callbacks: {
840					onUpdate: function(value) {
841						scaleFactor = value;
842						if ( scaleOnHover ) {
843							plane.textures && plane.textures[0].setScale(1 + value / 12, 1 + value / 12);
844						}
845					},
846					onComplete: function() {
847						trx_addons_tween_stop( plane.tweenScale );
848						plane.tweenScale = null;
849						if ( ! permanentDrawing && to === 0 ) {
850							curtains.disableDrawing();
851						}
852					}
853				}
854			} );
855		}
856	};
857
858
859
860	// Effect 'Ripple'
861	//-------------------------------------------
862	
863	// Create plane.
864	// Attention! All callbacks must be in a global scope!
865	window.trx_addons_image_effects_callback_ripple = function( curtains_global, elm, index, total ) {
866
867		var curtains = curtains_global ? curtains_global : null;
868
869		// Common vars
870		var mouseIn = false;
871
872		var scaleOnHover = elm.getAttribute('data-image-effect-scale') > 0,
873			scaleFactor = 0;
874
875		var effectStrength = Math.max( 5.0, Math.min( 50.0, elm.getAttribute('data-image-effect-strength') || 30.0 ) );
876
877		// Effect-specific vars
878		var wavesDirection = Math.max(0.0, Math.min(1.0, elm.getAttribute('data-image-effect-waves-direction') ) );
879
880		// Curtains init
881		var plane = null,
882			parent = null,
883			img_all = elm.querySelectorAll( 'img:not([class*="trx_addons_image_effects_"])' ),
884			img = img_all.length > 1
885					? elm.querySelector( 'img:not([class*="avatar"]):not([class*="trx_addons_extended_taxonomy_img"])' )
886					: elm.querySelector( 'img' );
887
888		if ( img ) {
889			if ( img_all.length > 1 ) {
890				jQuery( img ).wrap( '<div class="trx_addons_image_effects_holder"></div>' );
891			}
892			parent = img.parentNode;
893			if ( img_all.length == 1 ) {
894				parent.classList.add('trx_addons_image_effects_holder');
895			}
896//			img.setAttribute('crossorigin', 'anonymous');
897			img.setAttribute('data-sampler', 'rippleTexture');
898			if ( img.getAttribute( 'decoding' ) == 'async' ) {
899				img.setAttribute( 'decoding', 'sync' );
900			}
901			if ( ! globalCanvas ) {
902				curtains = create_canvas(parent);
903				if ( curtains ) jQuery(elm).data('curtains', curtains);
904			}
905			if ( curtains ) {
906				if ( ! permanentDrawing ) curtains.disableDrawing();
907				plane = curtains.addPlane( parent, get_params( elm, img, parent ) );
908				if ( plane ) {
909					jQuery(elm).data('curtains-plane', plane);
910					handle_plane( plane );
911				}
912			}
913		}
914
915		// Return plane params
916		function get_params() {
917			var vs = `
918
919				#ifdef GL_ES
920					precision mediump float;
921				#endif
922
923				// those are the mandatory attributes that the lib sets
924				attribute vec3 aVertexPosition;
925				attribute vec2 aTextureCoord;
926
927				// those are mandatory uniforms that the lib sets and that contain our model view and projection matrix
928				uniform mat4 uMVMatrix;
929				uniform mat4 uPMatrix;
930
931				// our texture matrix uniform
932				uniform mat4 rippleTextureMatrix;
933
934				// if you want to pass your vertex and texture coords to the fragment shader
935				varying vec3 vVertexPosition;
936				varying vec2 vTextureCoord;
937
938				void main() {
939					// get the vertex position from its attribute
940					vec3 vertexPosition = aVertexPosition;
941					
942					// set its position based on projection and model view matrix
943					gl_Position = uPMatrix * uMVMatrix * vec4(vertexPosition, 1.0
943);
944
945					// set the varying variables
946					// thanks to the texture matrix we will be able to calculate accurate texture coords
947					// so that our texture will always fit our plane without being distorted
948					vTextureCoord = (rippleTextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy;
949					vVertexPosition = vertexPosition;
950				}
951			`;
952			
953			var fs = `
954				#ifdef GL_ES
955					precision mediump float;
956				#endif
957
958				// get our varying variables
959				varying vec3 vVertexPosition;
960				varying vec2 vTextureCoord;
961
962				// our texture sampler
963				uniform sampler2D rippleTexture;
964
965				// effect control vars declared inside our javascript
966				uniform float uTime;			// time iterator to change waves position
967				uniform float uEffectStrength;	// 5.0 - 50.0 - waves amplitude
968				uniform float uWavesForce;		// 0.0 - 1.0 - fadeIn/fadeOut on mouse hover
969				uniform float uWavesDirection;	// 0 - horizontal, 1 - vertical
970
971				void main() {
972					// get our texture coords
973					vec2 textureCoord = vTextureCoord;
974
975					// displace our pixels along both axis based on our time uniform and texture UVs
976					// this will create a kind of water surface effect
977					// try to comment a line or change the constants to see how it changes the effect
978					// reminder : textures coords are ranging from 0.0 to 1.0 on both axis
979					const float PI = 3.141592;
980
981					textureCoord.x += (
982										sin(textureCoord.x * 10.0 * ( 2.0 - uWavesDirection ) + ((uTime * (PI / 3.0)) * 0.031))
983										+ sin(textureCoord.y * 10.0 * ( 2.0 - uWavesDirection ) + ((uTime * (PI / 2.489)) * 0.017))
984										) / uEffectStrength / ( 2.5 + 1.5 * uWavesDirection ) * uWavesForce;	// * 0.0075;
985
986					textureCoord.y += (
987										sin(textureCoord.y * 20.0 / ( 2.0 - uWavesDirection ) + ((uTime * (PI / 2.023)) * 0.023))
988										+ sin(textureCoord.x * 20.0 / ( 2.0 - uWavesDirection ) + ((uTime * (PI / 3.1254)) * 0.037))
989										) / uEffectStrength / ( 2.5 + 1.5 * ( 1.0 - uWavesDirection ) ) * uWavesForce;	// * 0.0125;
990
991					gl_FragColor = texture2D(rippleTexture, textureCoord);
992				}
993			`;
994
995			return {
996				vertexShader: vs,		// our vertex shader ID
997				fragmentShader: fs,		// our fragment shader ID
998//				widthSegments: 10,
999//				heightSegments: 10,
1000				uniforms: {				// variables passed to shaders
1001					time: {
1002						name: "uTime",
1003						type: "1f",
1004						value: 0
1005					},
1006					effectStrength: {
1007						name: "uEffectStrength",
1008						type: "1f",
1009						value: effectStrength
1010					},
1011					wavesForce: {
1012						name: "uWavesForce",
1013						type: "1f",
1014						value: 0
1015					},
1016					wavesDirection: {
1017						name: "uWavesDirection",
1018						type: "1f",
1019						value: wavesDirection
1020					}
1021				}
1022			};
1023		}
1024
1025		// handle plane
1026		function handle_plane( plane ) {
1027			plane
1028				.onLoading(function() {
1029//					console.log(plane.loadingManager.sourcesLoaded);
1030				})
1031				.onReady(function() {
1032//					console.log('Plane '+index+' of '+total+' is ready');
1033					// first render plane
1034					if ( ! permanentDrawing ) curtains.needRender();
1035					// init tweens
1036					plane.tweenForce = null;
1037					plane.tweenScale = null;
1038					// now that our plane is ready we can listen to mouse move event
1039					function mouse_move(e) {
1040						if ( ! mouseIn ) {
1041							mouse_enter();
1042						}
1043					}
1044					function mouse_enter() {
1045						if ( ! mouseIn ) {
1046							mouseIn = true;
1047							change_waves_force( 1 );
1048							change_scale( 1 );
1049						}
1050					}
1051					function mouse_leave() {
1052						if ( mouseIn ) {
1053							mouseIn = false;
1054							change_waves_force( 0 );
1055							change_scale( 0 );
1056						}
1057					}
1058					elm.addEventListener("mousemove",  mouse_move );
1059					elm.addEventListener("touchmove",  mouse_move );
1060					elm.addEventListener("mouseenter", mouse_enter );
1061					elm.addEventListener("touchstart", mouse_enter );
1062					elm.addEventListener("mouseleave", mouse_leave );
1063					elm.addEventListener("touchend",   mouse_leave );
1064				})
1065				.onRender(function() {
1066					plane.uniforms.time.value++;
1067
1068				})
1069				.onAfterResize(function() {
1070//					console.log('Plane '+index+' of '+total+' is resized');
1071				});
1072
1073			// Change wave force value
1074			function change_waves_force( to ) {
1075				if ( plane.tweenForce ) {
1076					trx_addons_tween_stop( plane.tweenForce );
1077				}
1078				plane.tweenForce = trx_addons_tween_value( {
1079					start: plane.uniforms.wavesForce.value,
1080					end: to,
1081					time: 1.25,
1082					callbacks: {
1083						onUpdate: function(value) {
1084							plane.uniforms.wavesForce.value = value;
1085						},
1086						onComplete: function() {
1087							trx_addons_tween_stop( plane.tweenForce );
1088							plane.tweenForce = null;
1089						}
1090					}
1091				} );
1092			}
1093
1094			// Change scale
1095			function change_scale( to ) {
1096				if ( plane.tweenScale ) {
1097					trx_addons_tween_stop( plane.tweenScale );
1098				}
1099				if ( ! permanentDrawing ) {
1100					curtains.enableDrawing();
1101				}
1102				plane.tweenScale = trx_addons_tween_value( {
1103					start: scaleFactor,
1104					end: to,
1105					time: 1.25,
1106					callbacks: {
1107						onUpdate: function(value) {
1108							scaleFactor = value;
1109							if ( scaleOnHover ) {
1110								plane.textures && plane.textures[0].setScale(1 + value / 12, 1 + value / 12);
1111							}
1112						},
1113						onComplete: function() {
1114							trx_addons_tween_stop( plane.tweenScale );
1115							plane.tweenScale = null;
1116							if ( ! permanentDrawing && to === 0 ) {
1117								curtains.disableDrawing();
1118							}
1119						}
1120					}
1121				} );
1122			}
1123		}
1124	};
1125
1126
1127
1128	// Effect 'Ripple 2'
1129	//-------------------------------------------
1130	
1131	// Create plane.
1132	// Attention! All callbacks must be in a global scope!
1133	window.trx_addons_image_effects_callback_ripple2 = function( curtains_global, elm, index, total ) {
1134
1135		var curtains = curtains_global ? curtains_global : null;
1136
1137		// Common vars
1138		var mouseIn = false;
1139
1140		var scaleOnHover = elm.getAttribute('data-image-effect-scale') > 0,
1141			scaleFactor = 0;
1142
1143		var effectStrength = Math.max( 5.0, Math.min( 50.0, elm.getAttribute('data-image-effect-strength') || 30.0 ) );
1144
1145		// Effect-specific vars
1146		var wavesDirection = Math.max(0.0, Math.min(1.0, elm.getAttribute('data-image-effect-waves-direction') ) );
1147
1148		// Curtains init
1149		var plane = null,
1150			parent = null,
1151			img_all = elm.querySelectorAll( 'img:not([class*="trx_addons_image_effects_"])' ),
1152			img = img_all.length > 1
1153					? elm.querySelector( 'img:not([class*="avatar"]):not([class*="trx_addons_extended_taxonomy_img"])' )
1154					: elm.querySelector( 'img' );
1155
1156		if ( img ) {
1157			if ( img_all.length > 1 ) {
1158				jQuery( img ).wrap( '<div class="trx_addons_image_effects_holder"></div>' );
1159			}
1160			parent = img.parentNode;
1161			if ( img_all.length == 1 ) {
1162				parent.classList.add('trx_addons_image_effects_holder');
1163			}
1164//			img.setAttribute('crossorigin', 'anonymous');
1165			img.setAttribute('data-sampler', 'ripple2Texture');
1166			if ( img.getAttribute( 'decoding' ) == 'async' ) {
1167				img.setAttribute( 'decoding', 'sync' );
1168			}
1169			var displacement_url = elm.getAttribute('data-image-effect-displacement');
1170			if ( displacement_url ) {
1171				var displacement_img = document.createElement("img");
1172//				displacement_img.setAttribute('crossorigin', 'anonymous');
1173				displacement_img.setAttribute('src', displacement_url);
1174				displacement_img.setAttribute('data-sampler', 'ripple2Displacement');
1175				if ( displacement_img.getAttribute( 'decoding' ) == 'async' ) {
1176					displacement_img.setAttribute( 'decoding', 'sync' );
1177				}
1178				displacement_img.classList.add('trx_addons_image_effects_ripple_displacement');
1179				parent.appendChild(displacement_img);
1180				$document.on('action.after_add_content', function(e, cont) {
1181					cont.find( '.trx_addons_image_effects_ripple_displacement' ).remove();
1182				});
1183			}
1184			if ( ! globalCanvas ) {
1185				curtains = create_canvas(parent);
1186				if ( curtains ) jQuery(elm).data('curtains', curtains);
1187			}
1188			if ( curtains ) {
1189				if ( ! permanentDrawing ) curtains.disableDrawing();
1190				plane = curtains.addPlane( parent, get_params( elm, img, parent ) );
1191				if ( plane ) {
1192					jQuery(elm).data('curtains-plane', plane);
1193					handle_plane( plane );
1194				}
1195			}
1196		}
1197
1198		// Return plane params
1199		function get_params() {
1200			var vs = `
1201				#ifdef GL_ES
1202					precision mediump float;
1203				#endif
1204
1205				// default mandatory variables
1206				attribute vec3 aVertexPosition;
1207				attribute vec2 aTextureCoord;
1208
1209				uniform mat4 uMVMatrix;
1210				uniform mat4 uPMatrix;
1211
1212				// our texture matrices
1213				// notice how it matches our data-sampler attributes + "Matrix"
1214				uniform mat4 ripple2TextureMatrix;
1215
1216				// varying variables
1217				varying vec3 vVertexPosition;
1218
1219				// our displacement texture will use original texture coords attributes
1220				varying vec2 vDisplacementCoord;
1221
1222				// our image will use texture coords based on their texture matrices
1223				varying vec2 vTextureCoord;
1224
1225				// custom uniforms
1226				uniform float uTime;
1227
1228				void main() {
1229					vec3 vertexPosition = aVertexPosition;
1230
1231					gl_Position = uPMatrix * uMVMatrix * vec4(vertexPosition, 1.0
1231);
1232
1233					// varying variables
1234					// texture coords attributes because we want our displacement texture to be contained
1235					vDisplacementCoord = aTextureCoord;
1236					// our image texture coords based on their texture matrices
1237					vTextureCoord = (ripple2TextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy;
1238					// vertex position as usual
1239					vVertexPosition = vertexPosition;
1240				}
1241			`;
1242			
1243			var fs = `
1244				#ifdef GL_ES
1245					precision mediump float;
1246				#endif
1247
1248				// all our varying variables
1249				varying vec3 vVertexPosition;
1250				varying vec2 vDisplacementCoord;
1251				varying vec2 vTextureCoord;
1252
1253				// our textures samplers
1254				// notice how it matches our data-sampler attributes
1255				uniform sampler2D ripple2Texture;
1256				uniform sampler2D ripple2Displacement;
1257
1258				// effect control vars declared inside our javascript
1259				uniform float uTime;			// time iterator to change waves position
1260				uniform float uEffectStrength;	// 5.0 - 50.0 - waves amplitude
1261				uniform float uWavesForce;		// 0.0 - 1.0 - fadeIn/fadeOut on mouse hover
1262				uniform float uWavesDirection;	// 0 - horizontal, 1 - vertical
1263
1264				void main( void ) {
1265
1266					// our displacement texture
1267					vec2 displacementCoords = vDisplacementCoord;
1268
1269					displacementCoords = vec2( mod(displacementCoords.x - (1.0 - uWavesDirection) * uTime / ( 400.0 + uEffectStrength * 5.0 ), 1.0),
1270											   mod(displacementCoords.y - uWavesDirection * uTime / ( 600.0 + uEffectStrength * 5.0 ), 1.0)
1271											);
1272					vec4 displacementTexture = texture2D(ripple2Displacement, displacementCoords);
1273
1274					// image texture
1275					vec2 textureCoords = vTextureCoord;
1276
1277					// displace our pixels along both axis based on our time uniform and texture UVs
1278					// this will create a kind of water surface effect
1279					// try to comment a line or change the constants to see how it changes the effect
1280					// reminder : textures coords are ranging from 0.0 to 1.0 on both axis
1281					const float PI = 3.141592;
1282
1283					textureCoords.x += 1.0 / uEffectStrength / ( 2.5 + 1.5 * uWavesDirection ) * uWavesForce * displacementTexture.r;
1284
1285					textureCoords.y -= 1.0 / uEffectStrength / ( 2.0 + 1.5 * ( 1.0 - uWavesDirection ) ) * uWavesForce * displacementTexture.r;
1286
1287					vec4 finalColor = texture2D(ripple2Texture, textureCoords);
1288
1289					// handling premultiplied alpha and apply displacement
1290					finalColor = vec4(finalColor.rgb * finalColor.a, finalColor.a);
1291
1292					// apply our shader
1293					gl_FragColor = finalColor;
1294				}
1295			`;
1296
1297			return {
1298				vertexShader: vs,		// our vertex shader ID
1299				fragmentShader: fs,		// our fragment shader ID
1300//				widthSegments: 10,
1301//				heightSegments: 10,
1302				imageCover: false,		// our displacement texture has to fit the plane
1303				uniforms: {				// variables passed to shaders
1304					time: {
1305						name: "uTime",
1306						type: "1f",
1307						value: 0
1308					},
1309					effectStrength: {
1310						name: "uEffectStrength",
1311						type: "1f",
1312						value: effectStrength
1313					},
1314					wavesForce: {
1315						name: "uWavesForce",
1316						type: "1f",
1317						value: 0
1318					},
1319					wavesDirection: {
1320						name: "uWavesDirection",
1321						type: "1f",
1322						value: wavesDirection
1323					}
1324				}
1325			};
1326		}
1327
1328		// handle plane
1329		function handle_plane( plane ) {
1330			plane
1331				.onLoading(function() {
1332//					console.log(plane.loadingManager.sourcesLoaded);
1333				})
1334				.onReady(function() {
1335//					console.log('Plane '+index+' of '+total+' is ready');
1336					// first render plane
1337					if ( ! permanentDrawing ) curtains.needRender();
1338					// init tweens
1339					plane.tweenForce = null;
1340					plane.tweenScale = null;
1341					// now that our plane is ready we can listen to mouse move event
1342					function mouse_move(e) {
1343						if ( ! mouseIn ) {
1344							mouse_enter();
1345						}
1346					}
1347					function mouse_enter() {
1348						if ( ! mouseIn ) {
1349							mouseIn = true;
1350							change_waves_force( 1 );
1351							change_scale( 1 );
1352						}
1353					}
1354					function mouse_leave() {
1355						if ( mouseIn ) {
1356							mouseIn = false;
1357							change_waves_force( 0 );
1358							change_scale( 0 );
1359						}
1360					}
1361					elm.addEventListener("mousemove",  mouse_move );
1362					elm.addEventListener("touchmove",  mouse_move );
1363					elm.addEventListener("mouseenter", mouse_enter );
1364					elm.addEventListener("touchstart", mouse_enter );
1365					elm.addEventListener("mouseleave", mouse_leave );
1366					elm.addEventListener("touchend",   mouse_leave );
1367				})
1368				.onRender(function() {
1369					plane.uniforms.time.value++;
1370
1371				})
1372				.onAfterResize(function() {
1373//					console.log('Plane '+index+' of '+total+' is resized');
1374				});
1375
1376			// Change wave force value
1377			function change_waves_force( to ) {
1378				if ( plane.tweenForce ) {
1379					trx_addons_tween_stop( plane.tweenForce );
1380				}
1381				plane.tweenForce = trx_addons_tween_value( {
1382					start: plane.uniforms.wavesForce.value,
1383					end: to,
1384					time: 1.25,
1385					callbacks: {
1386						onUpdate: function(value) {
1387							plane.uniforms.wavesForce.value = value;
1388						},
1389						onComplete: function() {
1390							trx_addons_tween_stop( plane.tweenForce );
1391							plane.tweenForce = null;
1392						}
1393					}
1394				} );
1395			}
1396
1397			// Change scale
1398			function change_scale( to ) {
1399				if ( plane.tweenScale ) {
1400					trx_addons_tween_stop( plane.tweenScale );
1401				}
1402				if ( ! permanentDrawing ) {
1403					curtains.enableDrawing();
1404				}
1405				plane.tweenScale = trx_addons_tween_value( {
1406					start: scaleFactor,
1407					end: to,
1408					time: 1.25,
1409					callbacks: {
1410						onUpdate: function(value) {
1411							scaleFactor = value;
1412							if ( scaleOnHover ) {
1413								plane.textures && plane.textures[0].setScale(1 + value / 12, 1 + value / 12);
1414							}
1415						},
1416						onComplete: function() {
1417							trx_addons_tween_stop( plane.tweenScale );
1418							plane.tweenScale = null;
1419							if ( ! permanentDrawing && to === 0 ) {
1420								curtains.disableDrawing();
1421							}
1422						}
1423					}
1424				} );
1425			}
1426		}
1427	};
1428
1429
1430
1431	// Effect 'Smudge'
1432	//-------------------------------------------
1433	
1434	// Create plane.
1435	// Attention! All callbacks must be in a global scope!
1436	window.trx_addons_image_effects_callback_smudge = function( curtains_global, elm, index, total ) {
1437
1438		var curtains = curtains_global ? curtains_global : null;
1439
1440		var enableDrawing = false;
1441
1442		// track the mouse positions to send it to the shaders
1443		var mousePosition = {
1444			x: 0,
1445			y: 0
1446		};
1447
1448		// we will keep track of the last position in order to calculate the movement strength/delta
1449		var mouseLastPosition = {
1450			x: 0,
1451			y: 0
1452		};
1453
1454		var mouseStrength = false;
1455
1456		var mouseIn = false;
1457
1458		var scaleOnHover = elm.getAttribute('data-image-effect-scale') > 0,
1459			scaleFactor = 0;
1460
1461		var paddingOnHover = typeof trx_addons_apply_filters == 'function'
1462								? trx_addons_apply_filters( 'trx_addons_filter_image_effects_padding', 0.04, 'smudge' )
1463								: 0.04;
1464
1465		var effectStrength = Math.max( 5.0, Math.min( 50.0, elm.getAttribute('data-image-effect-strength') || 30.0 ) );
1466
1467		var deltas = {
1468			max: 0,
1469			applied: 0
1470		};
1471
1472		var plane = null,
1473			parent = null,
1474			img_all = elm.querySelectorAll( 'img:not([class*="trx_addons_image_effects_"])' ),
1475			img = img_all.length > 1
1476					? elm.querySelector( 'img:not([class*="avatar"]):not([class*="trx_addons_extended_taxonomy_img"])' )
1477					: elm.querySelector( 'img' );
1478
1479		if ( img ) {
1480			if ( img_all.length > 1 ) {
1481				jQuery( img ).wrap( '<div class="trx_addons_image_effects_holder"></div>' );
1482			}
1483			parent = img.parentNode;
1484			if ( img_all.length == 1 ) {
1485				parent.classList.add('trx_addons_image_effects_holder');
1486			}
1487//			img.setAttribute('crossorigin', 'anonymous');
1488			img.setAttribute('data-sampler', 'smudgeTexture');
1489			if ( img.getAttribute( 'decoding' ) == 'async' ) {
1490				img.setAttribute( 'decoding', 'sync' );
1491			}
1492			if ( ! globalCanvas ) {
1493				curtains = create_canvas(parent);
1494				if ( curtains ) jQuery(elm).data('curtains', curtains);
1495			}
1496			if ( curtains ) {
1497				if ( ! permanentDrawing ) curtains.disableDrawing();
1498				plane = curtains.addPlane( parent, get_params( elm, img, parent ) );
1499				if ( plane ) {
1500					jQuery(elm).data('curtains-plane', plane);
1501					handle_plane( plane );
1502				}
1503			}
1504		}
1505
1506		// Return plane params
1507		function get_params( elm, img, parent ) {
1508			var vs = `
1509				#ifdef GL_ES
1510					precision mediump float;
1511				#endif
1512
1513				// default mandatory variables
1514				attribute vec3 aVertexPosition;
1515				attribute vec2 aTextureCoord;
1516
1517				uniform mat4 uMVMatrix;
1518				uniform mat4 uPMatrix;
1519
1520				// our texture matrix
1521				uniform mat4 smudgeTextureMatrix;
1522
1523				// custom variables
1524				varying vec3 vVertexPosition;
1525				varying vec2 vTextureCoord;
1526				uniform float uTime;
1527				//uniform vec2 uResolution;
1528				uniform vec2 uMousePosition;
1529				uniform float uMouseMoveStrength;
1530				uniform float uEffectStrength;
1531				uniform float uPadding;
1532
1533				void main() {
1534					vec3 vertexPosition = aVertexPosition;
1535					// get the distance between our vertex and the mouse position
1536					float distanceFromMouse = distance(uMousePosition, vec2(vertexPosition.x, vertexPosition.y));
1537					// attenuate the effect based on mouse distance
1538					float distanceStrength = 0.4 / (distanceFromMouse + 0.4);
1539					// calculate our distortion effect
1540					float distortionEffect = distanceStrength * uMouseMoveStrength / uEffectStrength;
1541					// apply it to our vertex position
1542					vertexPosition.z += distortionEffect;
1543					//vertexPosition.x += distortionEffect * (uMousePosition.x - vertexPosition.x) * (uResolution.x / uResolution.y);
1544					vertexPosition.x += distortionEffect * (uMousePosition.x - vertexPosition.x);
1545					vertexPosition.y += distortionEffect * (uMousePosition.y - vertexPosition.y);
1546					gl_Position = uPMatrix * uMVMatrix * vec4(vertexPosition, (1.0 + uPadding));
1547					// varyings
1548					vTextureCoord = (smudgeTextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy;
1549					vVertexPosition = vertexPosition;
1550				}
1551			`;
1552			var fs = `
1553				#ifdef GL_ES
1554					precision mediump float;
1555				#endif
1556
1557				varying vec3 vVertexPosition;
1558				varying vec2 vTextureCoord;
1559				uniform sampler2D smudgeTexture;
1560
1561				void main() {
1562					// apply our texture
1563					vec4 finalColor = texture2D(smudgeTexture, vTextureCoord);
1564					// fake shadows based on vertex position along Z axis
1565					finalColor.rgb -= clamp(-vVertexPosition.z, 0.0, 1.0);
1566					// fake lights based on vertex position along Z axis
1567					finalColor.rgb += clamp(vVertexPosition.z, 0.0, 1.0);
1568					// handling premultiplied alpha (useful if we were using a png with transparency)
1569					finalColor = vec4(finalColor.rgb * finalColor.a, finalColor.a);
1570					gl_FragColor = finalColor;
1571				}
1572			`;
1573
1574			return {
1575				vertexShader: vs,
1576				fragmentShader: fs,
1577				widthSegments: 20,
1578				heightSegments: 20,
1579				uniforms: {
1580					/*
1581					resolution: { // resolution of our plane
1582						name: "uResolution",
1583						type: "2f", // notice this is an length 2 array of floats
1584						value: [ parent.clientWidth, parent.clientHeight ]
1585					},
1586					*/
1587					time: { // time uniform that will be updated at each draw call
1588						name: "uTime",
1589						type: "1f",
1590						value: 0
1591					},
1592					mousePosition: { // our mouse position
1593						name: "uMousePosition",
1594						type: "2f", // again an array of floats
1595						value: [ mousePosition.x, mousePosition.y ]
1596					},
1597					mouseMoveStrength: { // the mouse move strength
1598						name: "uMouseMoveStrength",
1599						type: "1f",
1600						value: 0
1601					},
1602					effectStrength: { // the smudge strength
1603						name: "uEffectStrength",
1604						type: "1f",
1605						value: effectStrength
1606					},
1607					padding: { 		// paddings around image on hover
1608						name: "uPadding",
1609						type: "1f",
1610						value: 0
1611					}
1612				}
1613			};
1614		}
1615
1616		// handle plane
1617		function handle_plane( plane ) {
1618			plane
1619				.onLoading(function() {
1620					//console.log(plane.loadingManager.sourcesLoaded);
1621				})
1622				.onReady(function() {
1623//					console.log('Plane '+index+' of '+total+' is ready');
1624					// set a fov of 35 to reduce perspective
1625					plane.setPerspective(35);
1626					// apply a little effect once everything is ready
1627					deltas.max = 2;
1628					// first render plane
1629					if ( ! permanentDrawing ) curtains.needRender();
1630					// init tweens
1631					plane.tweenScale = null;
1632					// now that our plane is ready we can listen to mouse move event
1633					function mouse_move(e) {
1634						if ( ! mouseIn ) {
1635							mouse_enter();
1636						}
1637						handle_movement(e, plane);
1638					}
1639					function mouse_enter() {
1640						if ( ! mouseIn ) {
1641							mouseIn = true;
1642							change_scale( 1 );
1643							if ( paddingOnHover ) {
1644								change_padding(paddingOnHover);
1645							}
1646						}
1647					}
1648					function mouse_leave() {
1649						if ( mouseIn ) {
1650							mouseIn = false;
1651							change_scale( 0 );
1652							if ( paddingOnHover ) {
1653								change_padding(0);
1654							}
1655						}
1656					}
1657					elm.addEventListener("mousemove",  mouse_move );
1658					elm.addEventListener("touchmove",  mouse_move );
1659					elm.addEventListener("mouseenter", mouse_enter );
1660					elm.addEventListener("touchstart", mouse_enter );
1661					elm.addEventListener("mouseleave", mouse_leave );
1662					elm.addEventListener("touchend",   mouse_leave );
1663				})
1664				.onRender(function() {
1665					// increment our time uniform
1666					plane.uniforms.time.value++;
1667					// decrease both deltas by damping : if the user doesn't move the mouse, effect will fade away
1668					deltas.applied += (deltas.max - deltas.applied) * 0.02;
1669					deltas.max += (0 - deltas.max) * 0.01;
1670					// send the new mouse move strength value
1671					plane.uniforms.mouseMoveStrength.value = deltas.applied;
1672					if ( ! permanentDrawing && ! enableDrawing && Math.abs(deltas.applied - deltas.max) < 0.001 ) {
1673						curtains.disableDrawing();
1674					}
1675				})
1676				.onAfterResize(function() {
1677//					console.log('Plane '+index+' of '+total+' is resized');
1678					/*
1679					var planeBoundingRect = plane.getBoundingRect();
1680					plane.uniforms.resolution.value = [planeBoundingRect.width, planeBoundingRect.height];
1681					*/
1682				});
1683		}
1684
1685		// handle the mouse move event
1686		function handle_movement(e, plane) {
1687
1688			// update mouse last pos
1689			mouseLastPosition.x = mousePosition.x;
1690			mouseLastPosition.y = mousePosition.y;
1691
1692			var mouse = {};
1693
1694			// touch event
1695			if (e.targetTouches) {
1696				mouse.x = globalCanvas ? e.targetTouches[0].clientX : e.targetTouches[0].layerX;
1697				mouse.y = globalCanvas ? e.targetTouches[0].clientY : e.targetTouches[0].layerY;
1698
1699			// mouse event
1700			} else {
1701													// In Chrome with custom scroll mouse coordinates in layerXY are incorrect
1702													// but in Mozilla mouse properties offsetXY always empty (equal to 0)
1703				mouse.x = globalCanvas ? e.clientX : ( e.offsetX !== 0 || e.offsetY !== 0 ? e.offsetX : e.layerX );
1704				mouse.y = globalCanvas ? e.clientY : ( e.offsetX !== 0 || e.offsetY !== 0 ? e.offsetY : e.layerY );
1705			}
1706
1707			// lerp the mouse position a bit to smoothen the overall effect
1708			mousePosition.x = trx_addons_lerp(mousePosition.x, mouse.x, 0.3);
1709			mousePosition.y = trx_addons_lerp(mousePosition.y, mouse.y, 0.3);
1710
1711			// convert our mouse/touch position to coordinates relative to the vertices of the plane
1712			var mouseCoords = globalCanvas
1713								? plane.mouseToPlaneCoords(mousePosition.x, mousePosition.y)
1714								: mouse_to_plane_coords( plane, mousePosition );
1715			
1716
1717			// update our mouse position uniform
1718			plane.uniforms.mousePosition.value = [mouseCoords.x, mouseCoords.y];
1719
1720			// calculate the mouse move strength
1721			if ( mouseStrength && mouseLastPosition.x && mouseLastPosition.y ) {
1722				var delta = Math.sqrt( Math.pow( mousePosition.x - mouseLastPosition.x, 2 ) + Math.pow( mousePosition.y - mouseLastPosition.y, 2 ) ) / 20;
1723				delta = Math.min(4, delta);
1724				// update max delta only if it increased
1725				if ( delta >= deltas.max ) {
1726					deltas.max = delta;
1727				}
1728			} else {
1729				deltas.max = 2;
1730			}
1731		}
1732
1733		// Change padding value
1734		function change_padding( to ) {
1735			if ( plane.tweenPadding ) {
1736				trx_addons_tween_stop( plane.tweenPadding );
1737			}
1738			plane.tweenPadding = trx_addons_tween_value( {
1739				start: plane.uniforms.padding.value,
1740				end: to,
1741				time: 1.25,
1742				callbacks: {
1743					onUpdate: function(value) {
1744						plane.uniforms.padding.value = value;
1745					},
1746					onComplete: function() {
1747						trx_addons_tween_stop( plane.tweenPadding );
1748						plane.tweenPadding = null;
1749					}
1750				}
1751			} );
1752		}
1753
1754		// Change scale
1755		function change_scale( to ) {
1756			if ( plane.tweenScale ) {
1757				trx_addons_tween_stop( plane.tweenScale );
1758			}
1759			if ( ! permanentDrawing ) {
1760				enableDrawing = true;
1761				curtains.enableDrawing();
1762			}
1763			plane.tweenScale = trx_addons_tween_value( {
1764				start: scaleFactor,
1765				end: to,
1766				time: 1.25,
1767				callbacks: {
1768					onUpdate: function(value) {
1769						scaleFactor = value;
1770						if ( scaleOnHover ) {
1771							plane.textures && plane.textures[0].setScale(1 + value / 12, 1 + value / 12);
1772						}
1773					},
1774					onComplete: function() {
1775						trx_addons_tween_stop( plane.tweenScale );
1776						plane.tweenScale = null;
1777						if ( ! permanentDrawing && to === 0 ) {
1778							enableDrawing = false;
1779							//curtains.disableDrawing();
1780						}
1781					}
1782				}
1783			} );
1784		}
1785
1786	};
1787
1788
1789
1790	// Effect 'Tint'
1791	//-------------------------------------------
1792	
1793	// Create plane.
1794	// Attention! All callbacks must be in a global scope!
1795	window.trx_addons_image_effects_callback_tint = function( curtains_global, elm, index, total ) {
1796
1797		var curtains = curtains_global ? curtains_global : null;
1798
1799		var mouseIn = false;
1800
1801		var scaleOnHover = elm.getAttribute('data-image-effect-scale') > 0,
1802			scaleFactor = 0,
1803			tintColor = elm.getAttribute('data-image-effect-tint-color') || TRX_ADDONS_STORAGE['theme_accent_color'];
1804
1805		var plane = null,
1806			parent = null,
1807			img_all = elm.querySelectorAll( 'img:not([class*="trx_addons_image_effects_"])' ),
1808			img = img_all.length > 1
1809					? elm.querySelector( 'img:not([class*="avatar"]):not([class*="trx_addons_extended_taxonomy_img"])' )
1810					: elm.querySelector( 'img' );
1811
1812		if ( img ) {
1813			if ( img_all.length > 1 ) {
1814				jQuery( img ).wrap( '<div class="trx_addons_image_effects_holder"></div>' );
1815			}
1816			parent = img.parentNode;
1817			if ( img_all.length == 1 ) {
1818				parent.classList.add('trx_addons_image_effects_holder');
1819			}
1820//			img.setAttribute('crossorigin', 'anonymous');
1821			img.setAttribute('data-sampler', 'tintTexture');
1822			if ( img.getAttribute( 'decoding' ) == 'async' ) {
1823				img.setAttribute( 'decoding', 'sync' );
1824			}
1825			if ( ! globalCanvas ) {
1826				curtains = create_canvas(parent);
1827				if ( curtains ) jQuery(elm).data('curtains', curtains);
1828			}
1829			if ( curtains ) {
1830				if ( ! permanentDrawing ) curtains.disableDrawing();
1831				plane = curtains.addPlane( parent, get_params( elm, img, parent ) );
1832				if ( plane ) {
1833					jQuery(elm).data('curtains-plane', plane);
1834					handle_plane( plane );
1835				}
1836			}
1837		}
1838
1839		// Return plane params
1840		function get_params( elm, img, parent ) {
1841
1842			var vs = `
1843				#ifdef GL_ES
1844					precision mediump float;
1845				#endif
1846
1847				vec3 permute(vec3 x) {
1848					return mod((x*34.0+1.0)*x, 289.0);
1849				}
1850				
1851				float snoise(vec2 v) {
1852					const vec4 C = vec4(0.211325, 0.366025, -0.57735, 0.02439);
1853					vec2 i = floor(v + dot(v, C.yy)),
1854						 x0 = v - i + dot(i, C.xx),
1855						 i1;
1856					i1 = x0.x > x0.y ? vec2(1.0, 0.0) : vec2(0.0,1.0);
1857					vec4 x12 = x0.xyxy + C.xxzz;
1858					x12.xy -= i1,
1859					i = mod(i, 289.0);
1860					vec3 p = permute(permute(i.y + vec3(0.0, i1.y, 1.0)) + i.x + vec3(0.0,i1.x, 1.0)),
1861						 m = max(0.5 - vec3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), 0.0);
1862					m = m * m, m = m * m;
1863					vec3 x = 2.0 * fract(p * C.www) - 1.0,
1864						 h = abs(x) - 0.5,
1865						 ox = floor(x + 0.5),
1866						 a0 = x - ox;
1867					m *= 1.792843 - 0.853735 * (a0 * a0 + h * h);
1868					vec3 g;
1869					g.x = a0.x * x0.x + h.x * x0.y,
1870					g.yz = a0.yz * x12.xz + h.yz * x12.yw;
1871					return 130.0 * dot(m, g);
1872				}
1873				
1874				attribute vec3 aVertexPosition;
1875				attribute vec2 aTextureCoord;
1876
1877				uniform mat4 uMVMatrix, uPMatrix, tintTextureMatrix;
1878				uniform float uTime, uMouseOver;
1879
1880				varying vec2 vTintTextureCoord;
1881				varying vec3 vVertexPosition, vNoise;
1882
1883				void main(){
1884					vec3 vP = aVertexPosition;
1885					vec2 sUV = vec2(vP.x * 0.75, vP.y * 0.75);
1886					vec3 sN = vec3(snoise(sUV * 2.0 - uTime / 360.0));
1887					vP.z = 0.0,
1888					gl_Position = uPMatrix * uMVMatrix * vec4(vP, 1.0),
1889					vVertexPosition = vP,
1890					vTintTextureCoord = (tintTextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy,
1891					vNoise = sN;
1892				}
1893			`;
1894			var fs = `
1895				#ifdef GL_ES
1896					precision mediump float;
1897				#endif
1898				varying vec2 vTintTextureCoord;
1899				varying vec3 vVertexPosition;
1900				varying vec3 vNoise;
1901
1902				uniform sampler2D tintTexture;
1903				uniform float uMouseOver;
1904				uniform vec3 uColor;
1905
1906				void main(){
1907					vec4 f = texture2D(tintTexture, vTintTextureCoord);
1908					f.rgb -= clamp(-vVertexPosition.z / 10.0, 0.0, 1.0);
1909					f.rgb += clamp( vVertexPosition.z / 12.5, 0.0, 1.0);
1910					vec4 lC = vec4(0.299, 0.587, 0.114, 0.0),
1911						 l = vec4(1.0),
1912						 tint = vec4(uColor.r / 255.0, uColor.g / 255.0, uColor.b / 255.0, 1.0);
1913					float lM = dot(f, lC),
1914						  mN = clamp(uMouseOver * (1.0 - vNoise.r) - 1.0 + uMouseOver * 2.0, 0.0, 1.0);
1915					vec4 mC = (l + tint) / vec4(2.0),
1916						 dT = lM >= 0.45 ? mix(mC, l, smoothstep(0.45, 0.93125, lM)) : mix(tint, mC, smoothstep(-0.03125, 0.45, lM));
1917					f = mix(dT, f, step(0.9, mN));
1918					f = vec4(f.rgb * f.a, f.a);
1919					gl_FragColor = f;
1920				}
1921			`;
1922
1923			return {
1924				vertexShader: vs,
1925				fragmentShader: fs,
1926				widthSegments: 5,
1927				heightSegments: 40,
1928				fov: 45,
1929				drawCheckMargins: {
1930					top: 15,
1931					right: 0,
1932					bottom: 15,
1933					left: 0
1934				},
1935				uniforms: {
1936					time: {
1937						name: "uTime",
1938						type: "1f",
1939						value: 0	// 180 * index
1940					},
1941					mouseOver: {
1942						name: "uMouseOver",
1943						type: "1f",
1944						value: 0
1945					},
1946					color: {
1947						name: "uColor",
1948						type: "3f",
1949						value: trx_addons_rgb2components(tintColor)
1950					}
1951				}
1952			};
1953		}
1954
1955		// handle plane
1956		function handle_plane( plane ) {
1957			plane
1958				.onReady(function() {
1959//					console.log('Plane '+index+' of '+total+' is ready');
1960					// first render plane
1961					if ( ! permanentDrawing ) curtains.needRender();
1962					// init tweens
1963					plane.tween = null;
1964					plane.tweenScale = null;
1965					// now that our plane is ready we can listen to mouse move event
1966					function mouse_move(e) {
1967						if ( ! mouseIn ) {
1968							mouse_enter();
1969						}
1970					}
1971					function mouse_enter() {
1972						if ( ! mouseIn ) {
1973							mouseIn = true;
1974							change_mouse_over( 1 );
1975							change_scale( 1 );
1976						}
1977					}
1978					function mouse_leave() {
1979						if ( mouseIn ) {
1980							mouseIn = false;
1981							change_mouse_over( 0 );
1982							change_scale( 0 );
1983						}
1984					}
1985					elm.addEventListener("mousemove",  mouse_move );
1986					elm.addEventListener("touchmove",  mouse_move );
1987					elm.addEventListener("mouseenter", mouse_enter );
1988					elm.addEventListener("touchstart", mouse_enter );
1989					elm.addEventListener("mouseleave", mouse_leave );
1990					elm.addEventListener("touchend",   mouse_leave );
1991				})
1992				.onRender(function() {
1993					plane.uniforms.time.value++;
1994				});
1995		}
1996
1997		// Change wave force value
1998		function change_mouse_over( to ) {
1999			if ( plane.tween ) {
2000				trx_addons_tween_stop( plane.tween );
2001			}
2002			plane.tween = trx_addons_tween_value( {
2003				start: plane.uniforms.mouseOver.value,
2004				end: to,
2005				time: 1.25,
2006				callbacks: {
2007					onUpdate: function(value) {
2008						plane.uniforms.mouseOver.value = value;
2009					},
2010					onComplete: function() {
2011						trx_addons_tween_stop( plane.tween );
2012						plane.tween = null
2013					}
2014				}
2015			} );
2016		}
2017
2018		// Change scale
2019		function change_scale( to ) {
2020			if ( plane.tweenScale ) {
2021				trx_addons_tween_stop( plane.tweenScale );
2022			}
2023			if ( ! permanentDrawing ) {
2024				curtains.enableDrawing();
2025			}
2026			plane.tweenScale = trx_addons_tween_value( {
2027				start: scaleFactor,
2028				end: to,
2029				time: 1.25,
2030				callbacks: {
2031					onUpdate: function(value) {
2032						scaleFactor = value;
2033						if ( scaleOnHover ) {
2034							plane.textures && plane.textures[0].setScale(1 + value / 12, 1 + value / 12);
2035						}
2036					},
2037					onComplete: function() {
2038						trx_addons_tween_stop( plane.tweenScale );
2039						plane.tweenScale = null;
2040						if ( ! permanentDrawing && to === 0 ) {
2041							curtains.disableDrawing();
2042						}
2043					}
2044				}
2045			} );
2046		}
2047	};
2048
2049
2050
2051	// Effect 'Swap'
2052	//-------------------------------------------
2053	
2054	// Create plane.
2055	// Attention! All callbacks must be in a global scope!
2056	window.trx_addons_image_effects_callback_swap = function( curtains_global, elm, index, total ) {
2057
2058		var curtains = curtains_global ? curtains_global : null;
2059
2060		// Common vars
2061		var mouseIn = false;
2062
2063		var scaleOnHover = elm.getAttribute('data-image-effect-scale') > 0,
2064			scaleFactor = 0;
2065
2066		var activeImage = 0;
2067
2068		// Curtains init
2069		var plane = null,
2070			parent = null,
2071			img_all = elm.querySelectorAll( 'img:not([class*="trx_addons_image_effects_"])' ),
2072			img = img_all.length > 1
2073					? elm.querySelector( 'img:not([class*="avatar"]):not([class*="trx_addons_extended_taxonomy_img"])' )
2074					: elm.querySelector( 'img' );
2075
2076		if ( img ) {
2077			if ( img_all.length > 1 ) {
2078				jQuery( img ).wrap( '<div class="trx_addons_image_effects_holder"></div>' );
2079			}
2080			parent = img.parentNode;
2081			if ( img_all.length == 1 ) {
2082				parent.classList.add('trx_addons_image_effects_holder');
2083			}
2084//			img.setAttribute('crossorigin', 'anonymous');
2085			img.setAttribute('data-sampler', 'swapTexture');
2086			if ( img.getAttribute( 'decoding' ) == 'async' ) {
2087				img.setAttribute( 'decoding', 'sync' );
2088			}
2089			var swap_url = elm.getAttribute('data-image-effect-swap-image');
2090			if ( swap_url ) {
2091				var swap_img = document.createElement("img");
2092//				swap_img.setAttribute('crossorigin', 'anonymous');
2093				swap_img.setAttribute('src', swap_url);
2094				swap_img.setAttribute('data-sampler', 'swap2Texture');
2095				if ( swap_img.getAttribute( 'decoding' ) == 'async' ) {
2096					swap_img.setAttribute( 'decoding', 'sync' );
2097				}
2098					swap_img.classList.add('trx_addons_image_effects_swap_image');
2099				parent.appendChild(swap_img);
2100				$document.on('action.after_add_content', function(e, cont) {
2101					cont.find( '.trx_addons_image_effects_swap_image' ).remove();
2102				});
2103			}
2104			var displacement_url = elm.getAttribute('data-image-effect-displacement');
2105			if ( displacement_url ) {
2106				var displacement_img = document.createElement("img");
2107//				displacement_img.setAttribute('crossorigin', 'anonymous');
2108				displacement_img.setAttribute('src', displacement_url);
2109				displacement_img.setAttribute('data-sampler', 'swapDisplacement');
2110				if ( displacement_img.getAttribute( 'decoding' ) == 'async' ) {
2111					displacement_img.setAttribute( 'decoding', 'sync' );
2112				}
2113					displacement_img.classList.add('trx_addons_image_effects_swap_displacement');
2114				parent.appendChild(displacement_img);
2115				$document.on('action.after_add_content', function(e, cont) {
2116					cont.find( '.trx_addons_image_effects_swap_displacement' ).remove();
2117				});
2118			}
2119			if ( ! globalCanvas ) {
2120				curtains = create_canvas(parent);
2121				if ( curtains ) jQuery(elm).data('curtains', curtains);
2122			}
2123			if ( curtains ) {
2124				if ( ! permanentDrawing ) curtains.disableDrawing();
2125				plane = curtains.addPlane( parent, get_params( elm, img, parent ) );
2126				if ( plane ) {
2127					jQuery(elm).data('curtains-plane', plane);
2128					handle_plane( plane );
2129				}
2130			}
2131		}
2132
2133		// Return plane params
2134		function get_params() {
2135			var vs = `
2136				#ifdef GL_ES
2137					precision mediump float;
2138				#endif
2139
2140				// default mandatory variables
2141				attribute vec3 aVertexPosition;
2142				attribute vec2 aTextureCoord;
2143
2144				uniform mat4 uMVMatrix;
2145				uniform mat4 uPMatrix;
2146
2147				// our texture matrices
2148				// notice how it matches our data-sampler attributes + "Matrix"
2149				uniform mat4 swapTextureMatrix;
2150				uniform mat4 swap2TextureMatrix;
2151
2152				// varying variables
2153				varying vec3 vVertexPosition;
2154
2155				// our displacement texture will use original texture coords attributes
2156				varying vec2 vTextureCoord;
2157
2158				// our image will use texture coords based on their texture matrices
2159				varying vec2 vSwapTextureCoord;
2160				varying vec2 vSwap2TextureCoord;
2161
2162				// custom uniforms
2163				uniform float uTime;
2164
2165				void main() {
2166					vec3 vertexPosition = aVertexPosition;
2167
2168					gl_Position = uPMatrix * uMVMatrix * vec4(vertexPosition, 1.0
2168);
2169
2170					// varying variables
2171					// texture coords attributes because we want our displacement texture to be contained
2172					vTextureCoord = aTextureCoord;
2173					// our image texture coords based on their texture matrices
2174					vSwapTextureCoord = (swapTextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy;
2175					vSwap2TextureCoord = (swap2TextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy;
2176					// vertex position as usual
2177					vVertexPosition = vertexPosition;
2178				}
2179			`;
2180			
2181			var fs = `
2182				#ifdef GL_ES
2183					precision mediump float;
2184				#endif
2185
2186				// all our varying variables
2187				varying vec3 vVertexPosition;
2188				varying vec2 vTextureCoord;
2189				varying vec2 vSwapTextureCoord;
2190				varying vec2 vSwap2TextureCoord;
2191
2192				// custom uniforms
2193				uniform float uTime;
2194
2195				// our textures samplers
2196				// notice how it matches our data-sampler attributes
2197				uniform sampler2D swapTexture;
2198				uniform sampler2D swap2Texture;
2199				uniform sampler2D swapDisplacement;
2200
2201				void main( void ) {
2202					// our texture coords
2203					vec2 textureCoords = vTextureCoord;
2204
2205					// our displacement texture
2206					vec4 displacementTexture = texture2D(swapDisplacement, textureCoords);
2207
2208					// our displacement factor is a float varying from 1 to 0 based on the timer
2209					float displacementFactor = 1.0 - (cos(uTime / (60.0 / 3.141592)) + 1.0) / 2.0;
2210
2211					// the effect factor will tell which way we want to displace our pixels
2212					// the farther from the center of the videos, the stronger it will be
2213					vec2 effectFactor = vec2((textureCoords.x - 0.5) * 0.75, (textureCoords.y - 0.5) * 0.75
2213);
2214
2215					// calculate our displaced coordinates of the first video
2216					vec2 firstDisplacementCoords = vec2(vSwapTextureCoord.x - displacementFactor * (displacementTexture.r * effectFactor.x), vSwapTextureCoord.y- displacementFactor * (displacementTexture.r * effectFactor.y));
2217					// opposite displacement effect on the second video
2218					vec2 secondDisplacementCoords = vec2(vSwap2TextureCoord.x - (1.0 - displacementFactor) * (displacementTexture.r * effectFactor.x), vSwap2TextureCoord.y - (1.0 - displacementFactor) * (displacementTexture.r * effectFactor.y));
2219
2220					// apply the textures
2221					vec4 firstDistortedColor = texture2D(swapTexture, firstDisplacementCoords);
2222					vec4 secondDistortedColor = texture2D(swap2Texture, secondDisplacementCoords);
2223
2224					// blend both textures based on our displacement factor
2225					vec4 finalColor = mix(firstDistortedColor, secondDistortedColor, displacementFactor);
2226
2227					// handling premultiplied alpha
2228					finalColor = vec4(finalColor.rgb * finalColor.a, finalColor.a);
2229
2230					// apply our shader
2231					gl_FragColor = finalColor;
2232				}
2233			`;
2234
2235			return {
2236				vertexShader: vs,		// our vertex shader ID
2237				fragmentShader: fs,		// our fragment shader ID
2238//				widthSegments: 10,
2239//				heightSegments: 10,
2240				imageCover: false,		// our displacement texture has to fit the plane
2241				uniforms: {				// variables passed to shaders
2242					time: {
2243						name: "uTime",
2244						type: "1f",
2245						value: 0
2246					}
2247				}
2248			};
2249		}
2250
2251		// handle plane
2252		function handle_plane( plane ) {
2253			plane
2254				.onLoading(function() {
2255//					console.log(plane.loadingManager.sourcesLoaded);
2256				})
2257				.onReady(function() {
2258//					console.log('Plane '+index+' of '+total+' is ready');
2259					// first render plane
2260					if ( ! permanentDrawing ) curtains.needRender();
2261					// init tweens
2262					plane.tweenScale = null;
2263					// now that our plane is ready we can listen to mouse move event
2264					function mouse_move(e) {
2265						if ( ! mouseIn ) {
2266							mouse_enter();
2267						}
2268					}
2269					function mouse_enter() {
2270						if ( ! mouseIn ) {
2271							mouseIn = true;
2272							activeImage = 1;
2273							change_scale( 1 );
2274						}
2275					}
2276					function mouse_leave() {
2277						if ( mouseIn ) {
2278							mouseIn = false;
2279							activeImage = 0;
2280							change_scale( 0 );
2281						}
2282					}
2283					elm.addEventListener("mousemove",  mouse_move );
2284					elm.addEventListener("touchmove",  mouse_move );
2285					elm.addEventListener("mouseenter", mouse_enter );
2286					elm.addEventListener("touchstart", mouse_enter );
2287					elm.addEventListener("mouseleave", mouse_leave );
2288					elm.addEventListener("touchend",   mouse_leave );
2289				})
2290				.onRender(function() {
2291					plane.uniforms.time.value = activeImage == 1 ? Math.min(60, plane.uniforms.time.value + 1) : Math.max(0, plane.uniforms.time.value - 1);
2292
2293				})
2294				.onAfterResize(function() {
2295//					console.log('Plane '+index+' of '+total+' is resized');
2296				});
2297
2298			// Change scale
2299			function change_scale( to ) {
2300				if ( plane.tweenScale ) {
2301					trx_addons_tween_stop( plane.tweenScale );
2302				}
2303				if ( ! permanentDrawing ) {
2304					curtains.enableDrawing();
2305				}
2306				plane.tweenScale = trx_addons_tween_value( {
2307					start: scaleFactor,
2308					end: to,
2309					time: 1.25,
2310					callbacks: {
2311						onUpdate: function(value) {
2312							scaleFactor = value;
2313							if ( scaleOnHover ) {
2314								plane.textures && plane.textures[0].setScale(1 + value / 12, 1 + value / 12);
2315							}
2316						},
2317						onComplete: function() {
2318							trx_addons_tween_stop( plane.tweenScale );
2319							plane.tweenScale = null;
2320							if ( ! permanentDrawing ) {
2321								curtains.disableDrawing();
2322							}
2323						}
2324					}
2325				} );
2326			}
2327		}
2328	};
2329
2330
2331
2332	// Effect 'Fish'
2333	//-------------------------------------------
2334	
2335	// Create plane.
2336	// Attention! All callbacks must be in a global scope!
2337	window.trx_addons_image_effects_callback_fish = function( curtains_global, elm, index, total ) {
2338
2339		var curtains = curtains_global ? curtains_global : null;
2340
2341		// Common vars
2342		var mouseIn = false;
2343
2344		// track the mouse positions to send it to the shaders
2345		var mousePosition = {
2346			x: 0,
2347			y: 0
2348		};
2349
2350		var scaleOnHover = elm.getAttribute('data-image-effect-scale') > 0,
2351			scaleFactor = 0;
2352
2353		var activeImage = 0;
2354
2355		// Curtains init
2356		var plane = null,
2357			parent = null,
2358			img_all = elm.querySelectorAll( 'img:not([class*="trx_addons_image_effects_"])' ),
2359			img = img_all.length > 1
2360					? elm.querySelector( 'img:not([class*="avatar"]):not([class*="trx_addons_extended_taxonomy_img"])' )
2361					: elm.querySelector( 'img' );
2362
2363		if ( img ) {
2364			if ( img_all.length > 1 ) {
2365				jQuery( img ).wrap( '<div class="trx_addons_image_effects_holder"></div>' );
2366			}
2367			parent = img.parentNode;
2368			if ( img_all.length == 1 ) {
2369				parent.classList.add('trx_addons_image_effects_holder');
2370			}
2371//			img.setAttribute('crossorigin', 'anonymous');
2372			img.setAttribute('data-sampler', 'swapTexture');
2373			if ( img.getAttribute( 'decoding' ) == 'async' ) {
2374				img.setAttribute( 'decoding', 'sync' );
2375			}
2376			var swap_url = elm.getAttribute('data-image-effect-swap-image');
2377			if ( swap_url ) {
2378				var swap_img = document.createElement("img");
2379//				swap_img.setAttribute('crossorigin', 'anonymous');
2380				swap_img.setAttribute('src', swap_url);
2381				swap_img.setAttribute('data-sampler', 'swap2Texture');
2382				if ( swap_img.getAttribute( 'decoding' ) == 'async' ) {
2383					swap_img.setAttribute( 'decoding', 'sync' );
2384				}
2385					swap_img.classList.add('trx_addons_image_effects_swap_image');
2386				parent.appendChild(swap_img);
2387				$document.on('action.after_add_content', function(e, cont) {
2388					cont.find( '.trx_addons_image_effects_swap_image' ).remove();
2389				});
2390			}
2391			var displacement_url = elm.getAttribute('data-image-effect-displacement');
2392			if ( displacement_url ) {
2393				var displacement_img = document.createElement("img");
2394//				displacement_img.setAttribute('crossorigin', 'anonymous');
2395				displacement_img.setAttribute('src', displacement_url);
2396				displacement_img.setAttribute('data-sampler', 'swapDisplacement');
2397				if ( displacement_img.getAttribute( 'decoding' ) == 'async' ) {
2398					displacement_img.setAttribute( 'decoding', 'sync' );
2399				}
2400					displacement_img.classList.add('trx_addons_image_effects_swap_displacement');
2401				parent.appendChild(displacement_img);
2402				$document.on('action.after_add_content', function(e, cont) {
2403					cont.find( '.trx_addons_image_effects_swap_displacement' ).remove();
2404				});
2405			}
2406			if ( ! globalCanvas ) {
2407				curtains = create_canvas(parent);
2408				if ( curtains ) jQuery(elm).data('curtains', curtains);
2409			}
2410			if ( curtains ) {
2411				if ( ! permanentDrawing ) curtains.disableDrawing();
2412				plane = curtains.addPlane( parent, get_params( elm, img, parent ) );
2413				if ( plane ) {
2414					jQuery(elm).data('curtains-plane', plane);
2415					handle_plane( plane );
2416				}
2417			}
2418		}
2419
2420		// Return plane params
2421		function get_params() {
2422			var vs = `
2423				#ifdef GL_ES
2424					precision mediump float;
2425				#endif
2426
2427				// default mandatory variables
2428				attribute vec3 aVertexPosition;
2429				attribute vec2 aTextureCoord;
2430
2431				uniform mat4 uMVMatrix;
2432				uniform mat4 uPMatrix;
2433
2434				// our texture matrices
2435				// notice how it matches our data-sampler attributes + "Matrix"
2436				uniform mat4 swapTextureMatrix;
2437				uniform mat4 swap2TextureMatrix;
2438
2439				// varying variables
2440				varying vec3 vVertexPosition;
2441
2442				// our displacement texture will use original texture coords attributes
2443				varying vec2 vTextureCoord;
2444
2445				// our image will use texture coords based on their texture matrices
2446				varying vec2 vSwapTextureCoord;
2447				varying vec2 vSwap2TextureCoord;
2448
2449				// custom uniforms
2450				uniform float uTime;
2451
2452				void main() {
2453					vec3 vertexPosition = aVertexPosition;
2454
2455					gl_Position = uPMatrix * uMVMatrix * vec4(vertexPosition, 1.0
2455);
2456
2457					// varying variables
2458					// texture coords attributes because we want our displacement texture to be contained
2459					vTextureCoord = aTextureCoord;
2460					// our image texture coords based on their texture matrices
2461					vSwapTextureCoord = (swapTextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy;
2462					vSwap2TextureCoord = (swap2TextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy;
2463					// vertex position as usual
2464					vVertexPosition = vertexPosition;
2465				}
2466			`;
2467			
2468			var fs = `
2469				#ifdef GL_ES
2470					precision mediump float;
2471				#endif
2472
2473				// all our varying variables
2474				varying vec3 vVertexPosition;
2475				varying vec2 vTextureCoord;
2476				varying vec2 vSwapTextureCoord;
2477				varying vec2 vSwap2TextureCoord;
2478
2479				// custom uniforms
2480				uniform float uPR;
2481				uniform float uScale;
2482				uniform float uEffect;
2483				uniform float uTime;
2484
2485				uniform vec2 uRes;
2486				uniform vec2 uMousePosition;
2487
2488				// our textures samplers
2489				// notice how it matches our data-sampler attributes
2490				uniform sampler2D swapTexture;
2491				uniform sampler2D swap2Texture;
2492				uniform sampler2D swapDisplacement;
2493
2494				float circle( in vec2 _st, in float _radius, in float blurriness ) {
2495					vec2 dist = _st;
2496					return 1. - smoothstep( _radius - ( _radius * blurriness ), _radius + ( _radius * blurriness ), dot( dist, dist ) * 4.0 );
2497				}
2498
2499				void main( void ) {
2500					// our texture coords
2501					vec2 textureCoords = vTextureCoord;
2502
2503					//vec2 st = textureCoords - vec2(0.5);
2504					// Manage the device ratio
2505					vec2 res = uRes * uPR;
2506					vec2 st = gl_FragCoord.xy / res.xy - vec2(0.5);
2507					// tip: use the following formula to keep the good ratio of your coordinates
2508					st.y *= uRes.y / uRes.x;
2509					
2510					vec2 mouse = uMousePosition * -0.5;
2511					// tip2: do the same for your mouse
2512					mouse.y *= uRes.y / uRes.x;
2513					//mouse *= -1.;
2514
2515					vec2 circlePos = st + mouse;
2516					float c = circle( circlePos, 0.3 * uScale, 2. );
2517
2518					// our displacement texture
2519					vec4 displacementTexture = texture2D(swapDisplacement, textureCoords);
2520
2521					// our displacement factor is a float varying from 1 to 0 based on the timer
2522					float displacementFactor = ( 1.0 - (cos(uTime / (60.0 / 3.141592)) + 1.0) / 2.0 * uEffect ) * c;
2523
2524					// the effect factor will tell which way we want to displace our pixels
2525					// the farther from the center of the videos, the stronger it will be
2526					vec2 effectFactor = vec2( (textureCoords.x - 0.5) * 0.75 * uEffect, (textureCoords.y - 0.5) * 0.75 * uEffect );
2527
2528					// calculate our displaced coordinates of the first video
2529					vec2 firstDisplacementCoords = vec2(vSwapTextureCoord.x - displacementFactor * (displacementTexture.r * effectFactor.x), vSwapTextureCoord.y - displacementFactor * (displacementTexture.r * effectFactor.y));
2530					// opposite displacement effect on the second video
2531					vec2 secondDisplacementCoords = vec2(vSwap2TextureCoord.x - (1.0 - displacementFactor) * (displacementTexture.r * effectFactor.x), vSwap2TextureCoord.y - (1.0 - displacementFactor) * (displacementTexture.r * effectFactor.y));
2532
2533					// apply the textures
2534					vec4 firstDistortedColor = texture2D(swapTexture, firstDisplacementCoords);
2535					vec4 secondDistortedColor = texture2D(swap2Texture, secondDisplacementCoords);
2536
2537					// blend both textures based on our displacement factor
2538					vec4 finalColor = mix(firstDistortedColor, secondDistortedColor, displacementFactor);
2539
2540					// handling premultiplied alpha
2541					finalColor = vec4(finalColor.rgb * finalColor.a, finalColor.a);
2542
2543					// apply our shader
2544					gl_FragColor = finalColor;
2545				}
2546			`;
2547			return {
2548				vertexShader: vs,		// our vertex shader ID
2549				fragmentShader: fs,		// our fragment shader ID
2550//				widthSegments: 10,
2551//				heightSegments: 10,
2552				imageCover: false,		// our displacement texture has to fit the plane
2553				uniforms: {				// variables passed to shaders
2554					mousePosition: { // our mouse position
2555						name: "uMousePosition",
2556						type: "2f",
2557						value: [ mousePosition.x, mousePosition.y ]
2558					},
2559					imageResolution: {
2560						name: "uRes",
2561						type: "2f",
2562						value: [ img.clientWidth, img.clientHeight ]
2563					},
2564					pixelRatio: {
2565						name: "uPR",
2566						type: "1f",
2567						value: window.devicePixelRatio.toFixed(1)
2568					},
2569					scaleFactor: {
2570						name: "uScale",
2571						type: "1f",
2572						value: 0
2573					},
2574					useEffect: {
2575						name: "uEffect",
2576						type: "1f",
2577						value: displacement_url != '' ? 1 : 0
2578					},
2579					time: {
2580						name: "uTime",
2581						type: "1f",
2582						value: 0
2583					}
2584				}
2585			};
2586		}
2587
2588		// handle plane
2589		function handle_plane( plane ) {
2590			plane
2591				.onLoading(function() {
2592//					console.log(plane.loadingManager.sourcesLoaded);
2593				})
2594				.onReady(function() {
2595//					console.log('Plane '+index+' of '+total+' is ready');
2596					// first render plane
2597					if ( ! permanentDrawing ) curtains.needRender();
2598					// init tweens
2599					plane.tweenScale = null;
2600					// now that our plane is ready we can listen to mouse move event
2601					function mouse_move(e) {
2602						if ( ! mouseIn ) {
2603							mouse_enter();
2604						}
2605						handle_movement(e, plane);
2606					}
2607					function mouse_enter() {
2608						if ( ! mouseIn ) {
2609							mouseIn = true;
2610							activeImage = 1;
2611							change_scale( 1 );
2612						}
2613					}
2614					function mouse_leave() {
2615						if ( mouseIn ) {
2616							mouseIn = false;
2617							activeImage = 0;
2618							change_scale( 0 );
2619						}
2620					}
2621					elm.addEventListener("mousemove",  mouse_move );
2622					elm.addEventListener("touchmove",  mouse_move );
2623					elm.addEventListener("mouseenter", mouse_enter );
2624					elm.addEventListener("touchstart", mouse_enter );
2625					elm.addEventListener("mouseleave", mouse_leave );
2626					elm.addEventListener("touchend",   mouse_leave );
2627				})
2628				.onRender(function() {
2629					plane.uniforms.time.value = activeImage == 1 ? Math.min(60, plane.uniforms.time.value + 1) : Math.max(0, plane.uniforms.time.value - 1);
2630				})
2631				.onAfterResize(function() {
2632//					console.log('Plane '+index+' of '+total+' is resized');
2633				});
2634
2635			// Change scale
2636			function change_scale( to ) {
2637				if ( plane.tweenScale ) {
2638					trx_addons_tween_stop( plane.tweenScale );
2639				}
2640				if ( ! permanentDrawing && to == 1 ) {
2641					curtains.enableDrawing();
2642				}
2643				plane.tweenScale = trx_addons_tween_value( {
2644					start: scaleFactor,
2645					end: to,
2646					time: 1.25,
2647					callbacks: {
2648						onUpdate: function(value) {
2649							scaleFactor = value;
2650							plane.uniforms.scaleFactor.value = value;
2651							if ( scaleOnHover ) {
2652								plane.textures && plane.textures[0].setScale(1 + value / 12, 1 + value / 12);
2653							}
2654						},
2655						onComplete: function() {
2656							trx_addons_tween_stop( plane.tweenScale );
2657							plane.tweenScale = null;
2658							if ( ! permanentDrawing && to == 0 ) {
2659								curtains.disableDrawing();
2660							}
2661						}
2662					}
2663				} );
2664			}
2665		}
2666
2667		// handle the mouse move event
2668		function handle_movement(e, plane) {
2669
2670			var mouse = get_mouse_position_from_event( e );
2671
2672			// lerp the mouse position a bit to smoothen the overall effect
2673			mousePosition.x = trx_addons_lerp(mousePosition.x, mouse.x, 0.3);
2674			mousePosition.y = trx_addons_lerp(mousePosition.y, mouse.y, 0.3);
2675
2676			// convert our mouse/touch position to coordinates relative to the vertices of the plane
2677			var mouseCoords = globalCanvas
2678								? plane.mouseToPlaneCoords(mousePosition.x, mousePosition.y)
2679								: mouse_to_plane_coords( plane, mousePosition );
2680			
2681
2682			// update our mouse position uniform
2683			plane.uniforms.mousePosition.value = [mouseCoords.x, mouseCoords.y];
2684		}
2685	};
2686
2687
2688
2689	// Effect 'Blot'
2690	//-------------------------------------------
2691	
2692	// Create plane.
2693	// Attention! All callbacks must be in a global scope!
2694	window.trx_addons_image_effects_callback_blot = function( curtains_global, elm, index, total ) {
2695
2696		var curtains = curtains_global ? curtains_global : null;
2697
2698		// Common vars
2699		var mouseIn = false;
2700
2701		// track the mouse positions to send it to the shaders
2702		var mousePosition = {
2703			x: 0,
2704			y: 0
2705		};
2706
2707		var scaleOnHover = elm.getAttribute('data-image-effect-scale') > 0,
2708			scaleFactor = 0,
2709			tintColor = elm.getAttribute('data-image-effect-tint-color') || '';
2710
2711
2712		var effectStrength = Math.max( 5.0, Math.min( 50.0, elm.getAttribute('data-image-effect-strength') || 30.0 ) );
2713		var wavesDirection = Math.max(0.0, Math.min(1.0, elm.getAttribute('data-image-effect-waves-direction') ) );
2714
2715		var activeImage = 0;
2716
2717		// Curtains init
2718		var plane = null,
2719			parent = null,
2720			img_all = elm.querySelectorAll( 'img:not([class*="trx_addons_image_effects_"])' ),
2721			img = img_all.length > 1
2722					? elm.querySelector( 'img:not([class*="avatar"]):not([class*="trx_addons_extended_taxonomy_img"])' )
2723					: elm.querySelector( 'img' );
2724
2725		if ( img ) {
2726			if ( img_all.length > 1 ) {
2727				jQuery( img ).wrap( '<div class="trx_addons_image_effects_holder"></div>' );
2728			}
2729			parent = img.parentNode;
2730			if ( img_all.length == 1 ) {
2731				parent.classList.add('trx_addons_image_effects_holder');
2732			}
2733//			img.setAttribute('crossorigin', 'anonymous');
2734			img.setAttribute('data-sampler', 'swapTexture');
2735			if ( img.getAttribute( 'decoding' ) == 'async' ) {
2736				img.setAttribute( 'decoding', 'sync' );
2737			}
2738			var swap_url = elm.getAttribute('data-image-effect-swap-image');
2739			if ( swap_url ) {
2740				var swap_img = document.createElement("img");
2741//				swap_img.setAttribute('crossorigin', 'anonymous');
2742				swap_img.setAttribute('src', swap_url);
2743				swap_img.setAttribute('data-sampler', 'swap2Texture');
2744				if ( swap_img.getAttribute( 'decoding' ) == 'async' ) {
2745					swap_img.setAttribute( 'decoding', 'sync' );
2746				}
2747					swap_img.classList.add('trx_addons_image_effects_swap_image');
2748				parent.appendChild(swap_img);
2749				$document.on('action.after_add_content', function(e, cont) {
2750					cont.find( '.trx_addons_image_effects_swap_image' ).remove();
2751				});
2752			}
2753			if ( ! globalCanvas ) {
2754				curtains = create_canvas(parent);
2755				if ( curtains ) jQuery(elm).data('curtains', curtains);
2756			}
2757			if ( curtains ) {
2758				if ( ! permanentDrawing ) curtains.disableDrawing();
2759				plane = curtains.addPlane( parent, get_params( elm, img, parent ) );
2760				if ( plane ) {
2761					jQuery(elm).data('curtains-plane', plane);
2762					handle_plane( plane );
2763				}
2764			}
2765		}
2766
2767		// Return plane params
2768		function get_params() {
2769			var vs = `
2770				#ifdef GL_ES
2771					precision mediump float;
2772				#endif
2773
2774				// default mandatory variables
2775				attribute vec3 aVertexPosition;
2776				attribute vec2 aTextureCoord;
2777
2778				uniform mat4 uPMatrix;
2779				uniform mat4 uMVMatrix;
2780
2781				// our texture matrices
2782				// notice how it matches our data-sampler attributes + "Matrix"
2783				uniform mat4 swapTextureMatrix;
2784				uniform mat4 swap2TextureMatrix;
2785
2786				// our displacement texture will use original texture coords attributes
2787				varying vec2 vTextureCoord;
2788
2789				// our image will use texture coords based on their texture matrices
2790				varying vec2 vSwapTextureCoord;
2791				varying vec2 vSwap2TextureCoord;
2792
2793				void main() {
2794					// texture coords attributes because we want our displacement texture to be contained
2795					// our image texture coords based on their texture matrices
2796					vTextureCoord = aTextureCoord;
2797					vSwapTextureCoord = (swapTextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy;
2798					vSwap2TextureCoord = (swap2TextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy;
2799
2800					gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0
2800);
2801				}
2802			`;
2803
2804			var fs = `
2805				#ifdef GL_ES
2806					precision mediump float;
2807				#endif
2808
2809				// all our varying variables
2810				//varying vec3 vVertexPosition;
2811				varying vec2 vTextureCoord;
2812				varying vec2 vSwapTextureCoord;
2813				varying vec2 vSwap2TextureCoord;
2814
2815				// custom uniforms
2816				uniform float uPR;
2817				uniform float uScale;
2818				uniform float uTint;
2819				uniform float uWavesTime;
2820				uniform float uTime;
2821
2822				uniform vec2 uRes;
2823				uniform vec2 uMousePosition;
2824				uniform vec3 uColor;
2825
2826				// our textures samplers
2827				// notice how it matches our data-sampler attributes
2828				uniform sampler2D swapTexture;
2829				uniform sampler2D swap2Texture;
2830
2831				uniform float uEffectStrength;	// 5.0 - 50.0 - waves amplitude
2832				uniform float uWavesDirection;	// 0 - horizontal, 1 - vertical
2833
2834				// noise 2d generator
2835				//---------------------------------------------------
2836				vec3 permute3(vec3 x) {
2837					return mod((x*34.0+1.0)*x, 289.0);
2838				}
2839				float snoise2(vec2 v) {
2840					const vec4 C = vec4(0.211325, 0.366025, -0.57735, 0.02439);
2841					vec2 i = floor(v + dot(v, C.yy)),
2842						 x0 = v - i + dot(i, C.xx),
2843						 i1;
2844					i1 = x0.x > x0.y ? vec2(1.0, 0.0) : vec2(0.0,1.0);
2845					vec4 x12 = x0.xyxy + C.xxzz;
2846					x12.xy -= i1,
2847					i = mod(i, 289.0);
2848					vec3 p = permute3(permute3(i.y + vec3(0.0, i1.y, 1.0)) + i.x + vec3(0.0,i1.x, 1.0)),
2849						 m = max(0.5 - vec3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), 0.0);
2850					m = m * m, m = m * m;
2851					vec3 x = 2.0 * fract(p * C.www) - 1.0,
2852						 h = abs(x) - 0.5,
2853						 ox = floor(x + 0.5),
2854						 a0 = x - ox;
2855					m *= 1.792843 - 0.853735 * (a0 * a0 + h * h);
2856					vec3 g;
2857					g.x = a0.x * x0.x + h.x * x0.y,
2858					g.yz = a0.yz * x12.xz + h.yz * x12.yw;
2859					return 130.0 * dot(m, g);
2860				}
2861
2862				// noise 3d generator
2863				//-------------------------------------------
2864				vec3 mod289(vec3 x) {
2865					return x - floor(x * (1.0 / 289.0)) * 289.0;
2866				}
2867				vec4 mod289(vec4 x) {
2868					return x - floor(x * (1.0 / 289.0)) * 289.0;
2869				}
2870				vec4 permute4(vec4 x) {
2871					return mod289(((x*34.0)+1.0)*x);
2872				}
2873				vec4 taylorInvSqrt(vec4 r) {
2874					return 1.79284291400159 - 0.85373472095314 * r;
2875				}
2876				float snoise3(vec3 v) {
2877					const vec2  C = vec2(1.0/6.0, 1.0/3.0) ;
2878					const vec4  D = vec4(0.0, 0.5, 1.0, 2.0);
2879					// First corner
2880					vec3 i  = floor(v + dot(v, C.yyy) );
2881					vec3 x0 =   v - i + dot(i, C.xxx) ;
2882					// Other corners
2883					vec3 g = step(x0.yzx, x0.xyz);
2884					vec3 l = 1.0 - g;
2885					vec3 i1 = min( g.xyz, l.zxy );
2886					vec3 i2 = max( g.xyz, l.zxy );
2887
2888					//   x0 = x0 - 0.0 + 0.0 * C.xxx;
2889					//   x1 = x0 - i1  + 1.0 * C.xxx;
2890					//   x2 = x0 - i2  + 2.0 * C.xxx;
2891					//   x3 = x0 - 1.0 + 3.0 * C.xxx;
2892					vec3 x1 = x0 - i1 + C.xxx;
2893					vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y
2894					vec3 x3 = x0 - D.yyy;      // -1.0+3.0*C.x = -0.5 = -D.y
2895
2896					// Permutations
2897					i = mod289(i); 
2898					vec4 p = permute4( permute4( permute4( 
2899					         i.z + vec4(0.0, i1.z, i2.z, 1.0 ))
2900					       + i.y + vec4(0.0, i1.y, i2.y, 1.0 )) 
2901					       + i.x + vec4(0.0, i1.x, i2.x, 1.0 ));
2902
2903					// Gradients: 7x7 points over a square, mapped onto an octahedron.
2904					// The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294)
2905					float n_ = 0.142857142857; // 1.0/7.0
2906					vec3  ns = n_ * D.wyz - D.xzx;
2907
2908					vec4 j = p - 49.0 * floor(p * ns.z * ns.z);  //  mod(p,7*7)
2909
2910					vec4 x_ = floor(j * ns.z);
2911					vec4 y_ = floor(j - 7.0 * x_ );    // mod(j,N)
2912
2913					vec4 x = x_ *ns.x + ns.yyyy;
2914					vec4 y = y_ *ns.x + ns.yyyy;
2915					vec4 h = 1.0 - abs(x) - abs(y);
2916
2917					vec4 b0 = vec4( x.xy, y.xy );
2918					vec4 b1 = vec4( x.zw, y.zw );
2919
2920					//vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0;
2921					//vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0;
2922					vec4 s0 = floor(b0)*2.0 + 1.0;
2923					vec4 s1 = floor(b1)*2.0 + 1.0;
2924					vec4 sh = -step(h, vec4(0.0));
2925
2926					vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ;
2927					vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ;
2928
2929					vec3 p0 = vec3(a0.xy,h.x);
2930					vec3 p1 = vec3(a0.zw,h.y);
2931					vec3 p2 = vec3(a1.xy,h.z);
2932					vec3 p3 = vec3(a1.zw,h.w);
2933
2934					//Normalise gradients
2935					vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));
2936					p0 *= norm.x;
2937					p1 *= norm.y;
2938					p2 *= norm.z;
2939					p3 *= norm.w;
2940
2941					// Mix final noise value
2942					vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
2943					m = m * m;
2944					return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1), 
2945					                            dot(p2,x2), dot(p3,x3) ) );
2946				}
2947
2948				// circle
2949				//-------------------------------------------
2950				float circle( in vec2 _st, in float _radius, in float blurriness ) {
2951					vec2 dist = _st;
2952					return 1. - smoothstep( _radius - ( _radius * blurriness ), _radius + ( _radius * blurriness ), dot( dist, dist ) * 4.0 );
2953				}
2954
2955				void main( void ) {
2956					vec2 v_uv = vTextureCoord;
2957					vec2 v_sv = vSwapTextureCoord;
2958
2959					// Manage the device ratio
2960					vec2 res = uRes * uPR;
2961					vec2 st = gl_FragCoord.xy / res.xy - vec2(0.5);
2962					// tip: use the following formula to keep the good ratio of your coordinates
2963					st.y *= uRes.y / uRes.x;
2964					
2965					vec2 mouse = uMousePosition * -0.5;
2966					// tip2: do the same for your mouse
2967					mouse.y *= uRes.y / uRes.x;
2968					//mouse *= -1.;
2969
2970					vec2 circlePos = st + mouse;
2971					// float c = circle( circlePos, 0.3 * uScale, 2.0 ) * 2.5;
2972					float c = circle( circlePos, 0.3 * uScale, 2.5 ) * 2.25;
2973
2974					float offx = v_uv.x + sin( v_uv.y + uTime * 0.1 );
2975					float offy = v_uv.y - uTime * 0.1 - cos( uTime * 0.001 ) * 0.01;
2976					float n = snoise3( vec3( offx, offy, uTime * 0.1 ) * 8.0 ) - 1.0;
2977
2978					float finalMask = smoothstep( 0.4, 0.5, n + pow( c, 2.0 ) );
2979
2980					// Add waves to the first image
2981					const float PI = 3.141592;
2982
2983					v_sv.x += (
2984								sin(v_sv.x * 10.0 * ( 2.0 - uWavesDirection ) + ((uWavesTime * (PI / 3.0)) * 0.031))
2985								+ sin(v_sv.y * 10.0 * ( 2.0 - uWavesDirection ) + ((uWavesTime * (PI / 2.489)) * 0.017))
2986								) / uEffectStrength / ( 2.5 + 1.5 * uWavesDirection ) * uScale;	// * 0.0075;
2987
2988					v_sv.y += (
2989								sin(v_sv.y * 20.0 / ( 2.0 - uWavesDirection ) + ((uWavesTime * (PI / 2.023)) * 0.023))
2990								+ sin(v_sv.x * 20.0 / ( 2.0 - uWavesDirection ) + ((uWavesTime * (PI / 3.1254)) * 0.037))
2991								) / uEffectStrength / ( 2.5 + 1.5 * ( 1.0 - uWavesDirection ) ) * uScale;	// * 0.0125;
2992
2993					vec4 imageColor = texture2D( swapTexture, v_sv );
2994
2995					// Add tint the second image
2996					vec4 hoverColor = texture2D( swap2Texture, vSwap2TextureCoord );
2997					vec4 tint = vec4( uColor.r / 255.0, uColor.g / 255.0, uColor.b / 255.0, 1.0 );
2998					hoverColor = uTint > 0.0 ? mix( hoverColor, tint, 0.5) : hoverColor;
2999
3000					gl_FragColor = mix( imageColor, hoverColor, finalMask );
3001				}
3002			`;
3003
3004			return {
3005				vertexShader: vs,		// our vertex shader ID
3006				fragmentShader: fs,		// our fragment shader ID
3007//				widthSegments: 10,
3008//				heightSegments: 10,
3009				imageCover: false,		// our displacement texture has to fit the plane
3010				uniforms: {				// variables passed to shaders
3011					mousePosition: { // our mouse position
3012						name: "uMousePosition",
3013						type: "2f",
3014						value: [ mousePosition.x, mousePosition.y ]
3015					},
3016					imageResolution: {
3017						name: "uRes",
3018						type: "2f",
3019						value: [ img.clientWidth, img.clientHeight ]
3020					},
3021					pixelRatio: {
3022						name: "uPR",
3023						type: "1f",
3024						value: window.devicePixelRatio.toFixed(1)
3025					},
3026					scaleFactor: {
3027						name: "uScale",
3028						type: "1f",
3029						value: 0
3030					},
3031					color: {
3032						name: "uColor",
3033						type: "3f",
3034						value: tintColor ? trx_addons_rgb2components( tintColor ) : [0, 0, 0]
3035					},
3036					useColor: {
3037						name: "uTint",
3038						type: "1f",
3039						value: tintColor ? 1 : 0
3040					},
3041					effectStrength: {
3042						name: "uEffectStrength",
3043						type: "1f",
3044						value: effectStrength
3045					},
3046					wavesDirection: {
3047						name: "uWavesDirection",
3048						type: "1f",
3049						value: wavesDirection
3050					},
3051					wavesTime: {
3052						name: "uWavesTime",
3053						type: "1f",
3054						value: 0
3055					},
3056					time: {
3057						name: "uTime",
3058						type: "1f",
3059						value: 0
3060					}
3061				}
3062			};
3063		}
3064
3065		// handle plane
3066		function handle_plane( plane ) {
3067			plane
3068				.onLoading(function() {
3069//					console.log(plane.loadingManager.sourcesLoaded);
3070				})
3071				.onReady(function() {
3072//					console.log('Plane '+index+' of '+total+' is ready');
3073					// first render plane
3074					if ( ! permanentDrawing ) curtains.needRender();
3075					// init tweens
3076					plane.tweenScale = null;
3077					// now that our plane is ready we can listen to mouse move event
3078					function mouse_move(e) {
3079						if ( ! mouseIn ) {
3080							mouse_enter();
3081						}
3082						handle_movement(e, plane);
3083					}
3084					function mouse_enter() {
3085						if ( ! mouseIn ) {
3086							mouseIn = true;
3087							activeImage = 1;
3088							change_scale( 1 );
3089						}
3090					}
3091					function mouse_leave() {
3092						if ( mouseIn ) {
3093							mouseIn = false;
3094							activeImage = 0;
3095							change_scale( 0 );
3096						}
3097					}
3098					elm.addEventListener("mousemove",  mouse_move );
3099					elm.addEventListener("touchmove",  mouse_move );
3100					elm.addEventListener("mouseenter", mouse_enter );
3101					elm.addEventListener("touchstart", mouse_enter );
3102					elm.addEventListener("mouseleave", mouse_leave );
3103					elm.addEventListener("touchend",   mouse_leave );
3104				})
3105				.onRender(function() {
3106					plane.uniforms.time.value += 0.01;
3107					plane.uniforms.wavesTime.value++;
3108				})
3109				.onAfterResize(function() {
3110//					console.log('Plane '+index+' of '+total+' is resized');
3111				});
3112
3113			// Change scale
3114			function change_scale( to ) {
3115				if ( plane.tweenScale ) {
3116					trx_addons_tween_stop( plane.tweenScale );
3117				}
3118				if ( ! permanentDrawing && to == 1 ) {
3119					curtains.enableDrawing();
3120				}
3121				plane.tweenScale = trx_addons_tween_value( {
3122					start: scaleFactor,
3123					end: to,
3124					time: 0.6,
3125					callbacks: {
3126						onUpdate: function(value) {
3127							scaleFactor = value;
3128							plane.uniforms.scaleFactor.value = value;
3129							if ( scaleOnHover ) {
3130								plane.textures && plane.textures[0].setScale(1 + value / 12, 1 + value / 12);
3131							}
3132						},
3133						onComplete: function() {
3134							trx_addons_tween_stop( plane.tweenScale );
3135							plane.tweenScale = null;
3136							if ( ! permanentDrawing && to == 0 ) {
3137								curtains.disableDrawing();
3138							}
3139						}
3140					}
3141				} );
3142			}
3143		}
3144
3145		// handle the mouse move event
3146		function handle_movement(e, plane) {
3147
3148			var mouse = get_mouse_position_from_event( e );
3149
3150			// lerp the mouse position a bit to smoothen the overall effect
3151			mousePosition.x = trx_addons_lerp(mousePosition.x, mouse.x, 0.3);
3152			mousePosition.y = trx_addons_lerp(mousePosition.y, mouse.y, 0.3);
3153
3154			// convert our mouse/touch position to coordinates relative to the vertices of the plane
3155			var mouseCoords = globalCanvas
3156								? plane.mouseToPlaneCoords(mousePosition.x, mousePosition.y)
3157								: mouse_to_plane_coords( plane, mousePosition );
3158
3159			// update our mouse position uniform
3160			plane.uniforms.mousePosition.value = [mouseCoords.x, mouseCoords.y];
3161		}
3162	};
3163
3164
3165
3166	// Effect 'Scroller'
3167	//-------------------------------------------
3168	
3169	// Create plane.
3170	// Attention! All callbacks must be in a global scope!
3171	window.trx_addons_image_effects_callback_scroller = function( curtains_global, elm, index, total ) {
3172
3173		var curtains = curtains_global ? curtains_global : null;
3174
3175		var mouseIn = false;
3176
3177		var scaleOnHover = elm.getAttribute('data-image-effect-scale') > 0,
3178			scaleFactor = 0;
3179
3180		var scrollDirection = 0,
3181			scrollY = -1;
3182
3183		var effectStrength = 60 - Math.max( 5.0, Math.min( 50.0, elm.getAttribute('data-image-effect-strength') || 30 ) ),
3184			effectDirection = elm.getAttribute('data-image-effect-waves-direction') || 0;
3185
3186		var plane = null,
3187			parent = null,
3188			img_all = elm.querySelectorAll( 'img:not([class*="trx_addons_image_effects_"])' ),
3189			img = img_all.length > 1
3190					? elm.querySelector( 'img:not([class*="avatar"]):not([class*="trx_addons_extended_taxonomy_img"])' )
3191					: elm.querySelector( 'img' );
3192
3193		if ( img ) {
3194			if ( img_all.length > 1 ) {
3195				jQuery( img ).wrap( '<div class="trx_addons_image_effects_holder"></div>' );
3196			}
3197			parent = img.parentNode;
3198			if ( img_all.length == 1 ) {
3199				parent.classList.add('trx_addons_image_effects_holder');
3200			}
3201//			img.setAttribute('crossorigin', 'anonymous');
3202			img.setAttribute('data-sampler', 'scrollerTexture');
3203			if ( img.getAttribute( 'decoding' ) == 'async' ) {
3204				img.setAttribute( 'decoding', 'sync' );
3205			}
3206			if ( ! globalCanvas ) {
3207				curtains = create_canvas(parent);
3208				if ( curtains ) jQuery(elm).data('curtains', curtains);
3209			}
3210			if ( curtains ) {
3211				plane = curtains.addPlane( parent, get_params( elm, img, parent ) );
3212				if ( plane ) {
3213					jQuery(elm).data('curtains-plane', plane);
3214					handle_plane( plane );
3215				}
3216			}
3217		}
3218
3219		// Return plane params
3220		function get_params( elm, img, parent ) {
3221
3222			var vs = `
3223				#ifdef GL_ES
3224					precision mediump float;
3225				#endif
3226
3227				attribute vec3 aVertexPosition;
3228				attribute vec2 aTextureCoord;
3229
3230				uniform mat4 uMVMatrix, uPMatrix;
3231
3232				uniform float uScrollSpeed;
3233				uniform float uEffectStrength;
3234				uniform float uEffectDirection;
3235				uniform float uPadding;
3236
3237				// our texture matrices
3238				// notice how it matches our data-sampler attributes + "Matrix"
3239				uniform mat4 scrollerTextureMatrix;
3240
3241				// if you want to pass your vertex and texture coords to the fragment shader
3242				varying vec3 vVertexPosition;
3243				varying vec2 vScrollerTextureCoord;
3244
3245				void main(){
3246					vec3 vertexPosition = aVertexPosition;
3247					if ( uEffectDirection > 0.0 ) {
3248						// Vertical wave
3249						vertexPosition.x += sin( ( vertexPosition.y + 1.0 ) * 3.141592 ) * sin( uScrollSpeed * 3.141592 ) * 0.05 * ( uEffectStrength / 30.0 );
3250					} else {
3251						// Horizontal wave
3252						vertexPosition.y += sin( ( vertexPosition.x + 1.0 ) / 2.0 * 3.141592 ) * sin( uScrollSpeed * 3.141592 ) * 0.05 * ( uEffectStrength / 30.0 );
3253					}
3254					vVertexPosition = vertexPosition;
3255					gl_Position = uPMatrix * uMVMatrix * vec4( vertexPosition, 1.0 + uPadding * ( uEffectStrength / 30.0 ) );
3256
3257					vScrollerTextureCoord = ( scrollerTextureMatrix * vec4( aTextureCoord, 0.0, 1.0 ) ).xy;
3258				}
3259			`;
3260			var fs = `
3261				#ifdef GL_ES
3262					precision mediump float;
3263				#endif
3264
3265				// all our varying variables
3266				varying vec2 vScrollerTextureCoord;
3267
3268				uniform sampler2D scrollerTexture;
3269
3270				void main(){
3271					vec2 textureCoord = vScrollerTextureCoord;
3272					gl_FragColor = texture2D(scrollerTexture, textureCoord);
3273				}
3274			`;
3275
3276			return {
3277				vertexShader: vs,
3278				fragmentShader: fs,
3279				widthSegments: 10,
3280				heightSegments: 10,
3281				uniforms: {
3282					padding: {
3283						name: "uPadding",
3284						type: "1f",
3285						value: trx_addons_apply_filters( 'trx_addons_filter_image_effects_padding', 0.05, 'scroller' )
3286					},
3287					effectStrength: {
3288						name: "uEffectStrength",
3289						type: "1f",
3290						value: effectStrength
3291					},
3292					effectDirection: {
3293						name: "uEffectDirection",
3294						type: "1f",
3295						value: effectDirection
3296					},
3297					scrollSpeed: {
3298						name: "uScrollSpeed",
3299						type: "1f",
3300						value: 0
3301					}
3302				}
3303			};
3304		}
3305
3306		// handle plane
3307		function handle_plane( plane ) {
3308			plane
3309				.onReady(function() {
3310//					console.log('Plane '+index+' of '+total+' is ready');
3311					// init tweens
3312					plane.tween = null;
3313					plane.tweenScale = null;
3314					var scrollBusy = false,
3315						clear_scroll_busy = trx_addons_throttle( function() {
3316							scrollBusy = false;
3317						}, 300, true );
3318
3319					function scroll_get() {
3320						return ( typeof window.scrollY != 'undefined' ? window.scrollY : window.pageYOffset ) || 0;
3321					}
3322					// now that our plane is ready we can listen to mouse move event
3323					function scroll_start(e) {
3324						clear_scroll_busy();
3325						if ( scrollY < 0 ) {
3326							scrollY = scroll_get();
3327						} else if ( ! scrollDirection ) {
3328							var scrollNew = scroll_get();
3329							scrollDirection = scrollNew > scrollY ? 1 : -1;
3330							scrollBusy = true;
3331							change_scroll( 1 );
3332						}
3333					}
3334					window.addEventListener("scroll",  scroll_start );
3335					// now that our plane is ready we can listen to mouse move event
3336					function mouse_move(e) {
3337						if ( ! mouseIn ) {
3338							mouse_enter();
3339						}
3340					}
3341					function mouse_enter() {
3342						if ( ! mouseIn ) {
3343							mouseIn = true;
3344							change_scale( 1 );
3345						}
3346					}
3347					function mouse_leave() {
3348						if ( mouseIn ) {
3349							mouseIn = false;
3350							change_scale( 0 );
3351						}
3352					}
3353					elm.addEventListener("mousemove",  mouse_move );
3354					elm.addEventListener("touchmove",  mouse_move );
3355					elm.addEventListener("mouseenter", mouse_enter );
3356					elm.addEventListener("touchstart", mouse_enter );
3357					elm.addEventListener("mouseleave", mouse_leave );
3358					elm.addEventListener("touchend",   mouse_leave );
3359				})
3360				.onRender(function() {
3361					//plane.uniforms.time.value++;
3362				});
3363		}
3364
3365		// Change wave force value
3366		function change_scroll( to ) {
3367			if ( plane.tween ) {
3368				trx_addons_tween_stop( plane.tween );
3369			}
3370			plane.tween = trx_addons_tween_value( {
3371				start: plane.uniforms.scrollSpeed.value,
3372				end: to,
3373				time: 1.5,
3374				callbacks: {
3375					onUpdate: function(value) {
3376						plane.uniforms.scrollSpeed.value = value * scrollDirection;
3377					},
3378					onComplete: function() {
3379						scrollDirection = 0;
3380						scrollY = -1;
3381						plane.uniforms.scrollSpeed.value = 0;
3382						trx_addons_tween_stop( plane.tween );
3383						plane.tween = null;
3384					}
3385				}
3386			} );
3387		}
3388
3389		// Change scale
3390		function change_scale( to ) {
3391			if ( plane.tweenScale ) {
3392				trx_addons_tween_stop( plane.tweenScale );
3393			}
3394			if ( ! permanentDrawing && to == 1 ) {
3395				curtains.enableDrawing();
3396			}
3397			plane.tweenScale = trx_addons_tween_value( {
3398				start: scaleFactor,
3399				end: to,
3400				time: 1.0,
3401				callbacks: {
3402					onUpdate: function(value) {
3403						scaleFactor = value;
3404						if ( scaleOnHover ) {
3405							plane.textures && plane.textures[0].setScale(1 + value / 12, 1 + value / 12);
3406						}
3407					},
3408					onComplete: function() {
3409						trx_addons_tween_stop( plane.tweenScale );
3410						plane.tweenScale = null;
3411					}
3412				}
3413			} );
3414		}
3415	};
3416
3417})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.