PageSourceSearch

https://anjaguedes.de/wp-content/plugins/trx_addons/addons/image-effects/image-effects.js

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

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.