diff --git a/src/jquery.nanogallery2.core.js b/src/jquery.nanogallery2.core.js
index 97c534b1..9c53b3ef 100644
--- a/src/jquery.nanogallery2.core.js
+++ b/src/jquery.nanogallery2.core.js
@@ -1,1512 +1,1531 @@
-/*!
- * @preserve nanogallery2 v3.0.0beta - javascript photo / video gallery and lightbox
- * Homepage: http://nanogallery2.nanostudio.org
- * Sources:  https://github.com/nanostudio-org/nanogallery2
- *
- * License:  GPLv3 and commercial licence
- * 
- * Requirements:
- *  - jQuery (http://www.jquery.com) - version >= 1.7.1
- *
- * Embeded components:
- *  - shifty (https://github.com/jeremyckahn/shifty)
- *  - imagesloaded (https://github.com/desandro/imagesloaded)
- *  - hammer.js (http://hammerjs.github.io/)
- *  - screenfull.js (https://github.com/sindresorhus/screenfull.js)
- * Tools:
- *  - webfont generated with http://fontello.com - mainly based on Font Awesome Copyright (C) 2012 by Dave Gandy (http://fontawesome.io/)
- *  - ICO online converter: https://iconverticons.com/online/
- */
-
- 
-// nanogallery v3.0.0beta
-/*
-- fixed nano_photos_provider2: on gallery initialization, if an album is defined, gallery will not display sub-albums
-- [replaced] label position -> valign - top, bottom, middle / default: bottom
-- tableau de remplacement de nom d'albums (text found -> text to use)
-- new: mosaic layout is now fully responsive
-- new: filtering, option galleryFilterTagsMode/galleryL1FilterTagsMode - possible value 'single', 'multiple'
-- new: filtering, if no tag is selected then no filter is applied
-- new: loading spinner over thumbnail during album content download
-- new: first album level, new options thumbnailL1BorderHorizontal and thumbnailL1BorderVertical
-- new: left / right pagination buttons on top of gallery, new option galleryPaginationTopButtons
-    icons: navigationPaginationPrevious / navigationPaginationNext
-    theme: navigationPagination
-    option galleryPaginationTopButtons : true,
-- new: swipe lightbox up to close it (additional to the existing pan down gesture)
-- new: thumbnails on lightbox (options: viewerGallery ('none', 'bottomOverMedia', 'bottom'), viewerGalleryTHeight, viewerGalleryTWidth)
-- new: callback fnPopupMediaInfo(item, title, content) -> {title: my_title, content: my_content}
-- changed: icon for tags and for tag's filter reset
-- changed: lightbox tool icons layout and background
-- fixed: gallery may not be displayed depending on the display animation
-- changed: touchAnimation default value to false - hover animation on thumbnails are now disabled by default
-- fixed: lightbox, a touch will display toolbars and lable when they are hidden
-- improved: swipe and touch gesture, velocity
-- new: rounded border on thumbnails -> galleryTheme : { thumbnail: { borderRadius
-- new:   thumbnailIcon :         { shadow:'' },
-- CDNJS
-- enhancement: page scrollbar better removed on lightbox display, to avoid page reflow
-- fixed: modal popup not sharp (media info and share), and wrong size on mobile devices
-- removed: viewerDisplayLogo option
-- fixed modal size on mobile
-- fixed: some artefacts around thumbnails in some use cases
-- fixed: #219 dragging in Firefox - many thanks to Largo (https://github.com/Largo)
-- changed: thumbnailOpenImage renamed in thumbnailOpenInLightox
-- new option thumbnailDisplayOrder/L1: randomized thumbnail display order ('', 'random');
-- new imageslideup
-- new option thumbnailDisplayTransitionEasing/L1, default: easeOutQuart
-- new: thumbnailGutterWidth and thumbnailGutterHeight responsive
-- rewrite - shopping cart
-   -> compteur
-   -> toolbar name cart -> shoppingcart
-   -> lightbox -> shoppingcart
-- depreciated: options 'topOverImage', 'bottomOverImage' for lighbox vertical toolbar position
-- depreciated: theme border for lightbox
-- compatibility issue: Jquery 3.0 and 3.1 - issue on width calculation - https://github.com/jquery/jquery/issues/3193
-- changed: option blackList renamed to blockList
-- changed: option whiteList renamed to allowList
-- minor fixes
-- new: lightbox standalone, without gallery
-
-TODO: 
-- tags :
-  - centre tags
-  - grouper tags (sous-tags)
-- hash : only once -> read but not set
-- enlever bottom bar par defaut
-- am�liorer fond d�grad� par d�faut des imagettes 
-- animation s�par�es pour les tools imagette
-- si hash n'existe pas -> eviter erreur et effacer hash
-- centrage vertical titre sans description
-- screenful update
-- delete button -> thumbnail/lightbox + callback(with cancel)
-- re-display one thumbnail -> new image, title, etc...
-- double touch in zoom mode -> should not go to next/previous media
-- rajouter custom meta-data dans nanophotosprovider (par ex. prix d'un article)
-- doc: enlever "button" au nom des outils du viewer
-- lightbox pan event on IFRAME
-- scrolltop on ResizeWindowEvent -> ne faire que suite � action utilisateur (ouverture album, pagination, navigation, filtre?)
-
-*/
- 
- 
-// ###########################################
-// ##### nanogallery2 as a JQUERY PLUGIN #####
-// ###########################################
-
-
-// Expose plugin as an AMD module if AMD loader is present:
-(function (factory) {
-    "use strict";
-    if (typeof define === 'function' && define.amd) {
-        // AMD. Register as an anonymous module.
-        // define('nanogallery2', ['jquery'], factory);
-        define(['jquery'], factory);
-    } else if (typeof exports === 'object' && typeof require === 'function') {
-        // Browserify
-        factory(require('jquery'));
-    } else {
-        // Browser globals
-        factory(jQuery);
-    }
-}(function ($) {
-// ;(function ($) {
-  "use strict";
-
-  //##### TOOLS/HELPERS ####
-
-  // Convert color to RGB/RGBA
-  function ColorHelperToRGB( color ) {
-    var obj = document.getElementById('ngyColorHelperToRGB');
-    if (obj === null) {
-      obj = document.createElement('div');
-      obj.id = "ngyColorHelperToRGB";
-      obj.style.cssText = 'display: none; color:' + color + ';';
-      document.body.appendChild(obj);
-    }
-    
-    var rgb = getComputedStyle(obj).color;
-
-    // to get HEX value:
-    // var rgb = getComputedStyle(obj).color.match(/\d+/g);
-    // var r = parseInt(rgb[0]).toString(16);
-    // var g = parseInt(rgb[1]).toString(16);
-    // var b = parseInt(rgb[2]).toString(16);
-    // var hex = '#' + r + g + b;
-
-    return rgb;
-  }
-
-  
-  // ##### helper for color handling
-  // - normalise RGB/RGBA/HEX format
-  // - lighten/darken color
-  // Inspired by:          
-  // https://stackoverflow.com/questions/5560248/programmatically-lighten-or-darken-a-hex-color-or-rgb-and-blend-colors
-  // http://www.pimptrizkit.com/?t=20%20Shades
-  function ShadeBlendConvert (p, from, to) {
-    var rgba='';
-    if( from.toUpperCase().substring(0,5) == 'RGBA(' ) {
-      rgba='a';
-      from='rgb('+from.substring(5);
-    }
-
-    if(typeof(p)!="number"||p<-1||p>1||typeof(from)!="string"||(from[0]!='r'&&from[0]!='#')||(typeof(to)!="string"&&typeof(to)!="undefined"))return null;
-    //if(!this.sbcRip)this.sbcRip=function(d){
-    function sbcRip(d){
-      var l=d.length,RGB=new Object();
-      if(l>9){
-        d=d.split(",");
-        if(d.length<3||d.length>4)return null;
-        RGB[0]=i(d[0].slice(4)),RGB[1]=i(d[1]),RGB[2]=i(d[2]),RGB[3]=d[3]?parseFloat(d[3]):-1;
-      }else{
-        if(l==8||l==6||l<4)return null;
-        if(l<6)d="#"+d[1]+d[1]+d[2]+d[2]+d[3]+d[3]+(l>4?d[4]+""+d[4]:"");
-        d=i(d.slice(1),16),RGB[0]=d>>16&255,RGB[1]=d>>8&255,RGB[2]=d&255,RGB[3]=l==9||l==5?r(((d>>24&255)/255)*10000)/10000:-1;
-      }
-      return RGB;
-    }
-    var i=parseInt,r=Math.round,h=from.length>9,h=typeof(to)=="string"?to.length>9?true:to=="c"?!h:false:h,b=p<0,p=b?p*-1:p,to=to&&to!="c"?to:b?"#000000":"#FFFFFF",f=sbcRip(from),t=sbcRip(to);
-    if(!f||!t)return null;
-    if(h)return "rgb"+rgba+"("+r((t[0]-f[0])*p+f[0])+","+r((t[1]-f[1])*p+f[1])+","+r((t[2]-f[2])*p+f[2])+(f[3]<0&&t[3]<0?")":","+(f[3]>-1&&t[3]>-1?r(((t[3]-f[3])*p+f[3])*10000)/10000:t[3]<0?f[3]:t[3])+")");
-    else return "#"+(0x100000000+(f[3]>-1&&t[3]>-1?r(((t[3]-f[3])*p+f[3])*255):t[3]>-1?r(t[3]*255):f[3]>-1?r(f[3]*255):255)*0x1000000+r((t[0]-f[0])*p+f[0])*0x10000+r((t[1]-f[1])*p+f[1])*0x100+r((t[2]-f[2])*p+f[2])).toString(16).slice(f[3]>-1||t[3]>-1?1:3);
-  }
-  
-  
-  // ##### clone a javascript object
-  function cloneJSObject( obj ) {
-    if (obj === null || typeof obj !== 'object') {
-      return obj;
-    }
-
-    var temp = obj.constructor(); // give temp the original obj's constructor
-    for (var key in obj) {
-        temp[key] = cloneJSObject(obj[key]);
-    }
-    return temp;
-  }
-  
-  // get viewport coordinates and size
-  function getViewport() {
-    var $win = jQuery(window);
-    return {
-      l: $win.scrollLeft(),
-      t: $win.scrollTop(),
-      w: $win.width(),
-      h: $win.height()
-    }
-  }
-
-
-  // Check if element is in viewport
-  // avoid if possible (performance issue)
-  function inViewport( $elt, threshold ) {
-    var wp = getViewport(),
-    eltOS = $elt.offset(),
-    th = $elt.outerHeight(true),
-    tw = $elt.outerWidth(true);
-    if( eltOS.top >= (wp.t - threshold) 
-      && (eltOS.top + th) <= (wp.t + wp.h + threshold)
-      && eltOS.left >= (wp.l - threshold) 
-      && (eltOS.left + tw) <= (wp.l + wp.w + threshold) ) {
-      return true;
-    }
-    else {
-      return false;
-    }
-  }
-
-
-  // Check if whole element is in ViewPort
-  // avoid if possible (performance issue)
-  function inViewportVert( $elt, threshold ) {
-    var wp = getViewport(),
-    eltOS = $elt.offset(),
-    th = $elt.outerHeight(true);
-    //var tw=$elt.outerWidth(true);
-
-    if( wp.t == 0 && (eltOS.top) <= (wp.t + wp.h ) ) { return true; }
-
-    if( eltOS.top >= wp.t && (eltOS.top + th) <= (wp.t + wp.h - threshold) ) {
-        return true;
-    }
-    else {
-      return false;
-    }
-  }
-  // Check if top of the element is in ViewPort
-  function topInViewportVert( $elt, threshold ) {
-    var wp = getViewport(),
-    eltOS = $elt.offset(),
-    th = $elt.outerHeight(true);
-
-    // if( wp.t == 0 && (eltOS.top) <= (wp.t + wp.h ) ) { return true; }
-
-    if( eltOS.top >= wp.t && eltOS.top <= (wp.t + wp.h - threshold) ) {
-        return true;
-    }
-    else {
-      return false;
-    }
-  }
-
-
-  // set z-index to display 2 elements on top of all others
-  function set2ElementsOnTop( start, elt1, elt2 ) {
-    var highest_index = 0;
-    if( start=='' ) { start= '*'; }
-    jQuery(start).each(function() {
-      var cur = parseInt(jQuery(this).css('z-index'));
-      highest_index = cur > highest_index ? cur : highest_index;
-    });
-    highest_index++;
-    jQuery(elt2).css('z-index',highest_index+1);
-    jQuery(elt1).css('z-index',highest_index);
-  }
-
-  // set z-index to display element on top of all others
-  function setElementOnTop( start, elt ) {
-    var highest_index = 0;
-    if( start == '' ) { start = '*'; }
-    jQuery(start).each(function() {
-      var cur = parseInt(jQuery(this).css('z-index'));
-      highest_index = cur > highest_index ? cur : highest_index;
-    });
-    highest_index++;
-    jQuery(elt).css('z-index',highest_index);
-  }
-  
-  // return the real type of the object
-  var toType = function( obj ) {
-    // by Angus Croll - http://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/
-    return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase()
-  };    
-    
-
-  $.nanogallery2 = function (elt, options) {
-    // To avoid scope issues, use '_this' instead of 'this'
-    // to reference this class from internal events and functions.
-    var _this = this;
-
-    // Access to jQuery and DOM versions of element
-    _this.$e  = jQuery(elt);
-    _this.e   = elt;
-
-    // Add a reverse reference to the DOM object
-    _this.$e.data('nanogallery2data', _this);
-
-    _this.init = function () {
-  
-      // define these global objects only once per HTML page
-      if (typeof window.NGY2Item === 'undefined') {
-  
-        window.NGY2Tools = (function () {
-
-          function NGY2Tools() {
-            var nextId = 1;                   // private static --> all instances
-          }
-
-          // check album name - albumList/blockList/allowList
-          NGY2Tools.FilterAlbumName = function( title, ID ) {
-            var s = title.toUpperCase();
-            if( this.albumList.length > 0 ) {
-              for( var j=0; j < this.albumList.length; j++) {
-                if( s === this.albumList[j].toUpperCase() || ID === this.albumList[j] ) {
-                  return true;
-                }
-              }
-            }
-            else {
-              var found = false;
-              if( this.allowList !== null ) {
-                //allowList : authorize only album cointaining one of the specified keyword in the title
-                for( var j = 0; j < this.allowList.length; j++) {
-                  if( s.indexOf(this.allowList[j]) !== -1 ) {
-                    found = true;
-                  }
-                }
-                if( !found ) { return false; }
-              }
-
-
-              if( this.blockList !== null ) {
-                //blockList : ignore album cointaining one of the specified keyword in the title
-                for( var j = 0; j < this.blockList.length; j++) {
-                  if( s.indexOf(this.blockList[j]) !== -1 ) { 
-                    return false;
-                  }
-                }
-              }
-              return true;
-            }
-          };
-
-
-          /** @function nanoAlert */
-          /* Display an alert message in a specific element */
-          NGY2Tools.NanoAlert = function(context, msg, verbose) {
-            NGY2Tools.NanoConsoleLog.call(context, msg);
-            if( context.$E.conConsole != null ) {
-              context.$E.conConsole.css({visibility: 'visible', minHeight: '100px'});
-              if( verbose == false ) {
-                context.$E.conConsole.append('<p>' + msg + '</p>');
-              }
-              else {
-                context.$E.conConsole.append('<p>nanogallery2: '+ msg + ' [' + context.baseEltID + ']</p>');
-              }
-            }
-          };
-          
-  
-          /** @function NanoConsoleLog */
-          /* write message to the browser console */
-          NGY2Tools.NanoConsoleLog = function(context, msg) {
-            if (window.console) { console.log('nanogallery2: ' + msg + ' [' + context.baseEltID + ']'); }
-            // debugger;
-          };
-          
-
-          /** @function PreloaderDisplay() */
-          /* Display/hide preloader */
-          NGY2Tools.PreloaderDisplay = function(display) {
-            if( display === true ) {
-              // loading bar at the top of the gallery
-              this.$E.conLoadingB.removeClass('nanoGalleryLBarOff').addClass('nanoGalleryLBar');
-              // spinner over album thumbnail
-              if( this.GOM.albumIdxLoading != undefined && this.GOM.albumIdxLoading != -1 ) {
-                var item = this.I[this.GOM.albumIdxLoading];
-                item.$Elts['.nGY2TnImg'].addClass('nGY2GThumbnailLoaderDisplayed');
-              }
-            }
-            else {
-              // loading bar at the top of the gallery
-              this.$E.conLoadingB.removeClass('nanoGalleryLBar').addClass('nanoGalleryLBarOff');
-              // spinner over album thumbnail
-              if( this.GOM.albumIdxLoading != undefined && this.GOM.albumIdxLoading != -1 ) {
-                var item = this.I[this.GOM.albumIdxLoading];
-                item.$Elts['.nGY2TnImg'].removeClass('nGY2GThumbnailLoaderDisplayed');
-              }
-            }
-          };
-
-          // Scrambles the elements of an array
-					//+ Jonas Raoni Soares Silva
-          //@ http://jsfromhell.com/array/shuffle [v1.0]
-          NGY2Tools.AreaShuffle = function (o) {
-            for (var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
-            return o;
-          };
-          
-          /** @function GetImageTitleFromURL() */
-          /* retrieve filemane */
-          NGY2Tools.GetImageTitleFromURL = function( imageURL ) {
-            if( this.O.thumbnailLabel.get('title') == '%filename' ) {
-              return (imageURL.split('/').pop()).replace('_',' ');
-            }
-            
-            if( this.O.thumbnailLabel.get('title') == '%filenameNoExt' ) {
-              var s=imageURL.split('/').pop();
-              return (s.split('.').shift()).replace('_',' ');
-            }
-            // return imageURL;
-            return '';
-          };
-          
-
-          /** @function AlbumPostProcess() */
-          /* post process one album based on plugin general parameters  --> sorting/maxItems*/
-          NGY2Tools.AlbumPostProcess = function(albumID) {
-
-            // this function can probably be optimized....
-          
-            var sortOrder = this.gallerySorting[this.GOM.curNavLevel];
-            var maxItems = this.galleryMaxItems[this.GOM.curNavLevel];
-
-            if( sortOrder != '' || maxItems > 0 ) {
-            
-              // copy album's items to a new array
-              var currentAlbum = this.I.filter( function( obj ) {
-                return( obj.albumID == albumID && obj.kind != 'albumUp' );
-              });
-        
-              // sorting options
-              switch( sortOrder ) {
-                case 'RANDOM':
-                  currentAlbum = NGY2Tools.AreaShuffle(currentAlbum);
-                  break;
-                case 'REVERSED':
-                  currentAlbum = currentAlbum.reverse();
-                  break;
-                case 'TITLEASC':
-                  currentAlbum.sort(function (a, b) {
-                    return( (a.title.toUpperCase() < b.title.toUpperCase()) ? -1 : ((a.title.toUpperCase() > b.title.toUpperCase()) ? 1 : 0) );
-                  });
-                  break;
-                case 'TITLEDESC':
-                  currentAlbum.sort(function (a, b) {
-                    return( (a.title.toUpperCase() > b.title.toUpperCase()) ? -1 : ((a.title.toUpperCase() < b.title.toUpperCase()) ? 1 : 0) );
-                  });
-                  break;
-              }     
-
-              // max Items
-              if( maxItems > 0 && currentAlbum.length > maxItems ) {
-                currentAlbum.splice(maxItems - 1, currentAlbum.length-maxItems );
-              }
-              
-              // remove the albums's items from the global items array
-              this.I.removeIf( function( obj ) {
-                return( obj.albumID == albumID && obj.kind != 'albumUp' );
-              });
-              
-              // add the sorted items back to the album
-              this.I.push.apply(this.I, currentAlbum);
-
-            }
-          };
-          
-          return NGY2Tools;
-        })(); 
-
-        // ====================
-        // ===== NGY2Item =====
-        // ====================
-        window.NGY2Item = (function() {
-          var nextId = 1;                   // private static --> all instances
-
-          // constructor
-          function NGY2Item( itemID ) {
-          //window.NGY2Item = function( itemID ) {
-            var ID = 0;                     // private
-
-            // public (this instance only)
-            if( itemID === undefined || itemID === null ) {
-              ID = nextId++;
-            }
-            else {
-              ID = itemID;
-            }
-            this.GetID = function () { return ID; };
-            
-            // public
-            this.kind =                 '';       // 'image', 'album' or 'albumUp'
-            this.mediaKind =            'img';    // 'img', 'iframe'
-            this.mediaMarkup =          '';
-            this.G =                    null;     // pointer to global instance
-            this.title =                '';       // image title
-            this.description =          '';       // image description
-            this.albumID =              0;        // ID of the parent album
-            this.src =                  '';       // full sized image URL
-            this.width =                0;        // image width
-            this.height =               0;        // image height
-            this.destinationURL =       '';       // thumbnail destination URL --> open URL instead of displaying image
-            this.downloadURL =          '';       // thumbnail download URL --> specify the image for download button
-            this.author =               '';       // image/album author
-            this.left =                 0;        // store position to animate from old to new
-            this.top =                  0;
-            this.width =                0;        // store size to avoid setting width/height if not required
-            this.height =               0;
-            this.resizedContentWidth=   0;        // store size of content (image) to avoid setting width/height if not required
-            this.resizedContentHeight=  0;
-            this.thumbs = {                       // URLs and sizes for user defined
-              url:    { l1: { xs: '', sm:'', me: '', la: '', xl: '' }, lN: { xs: '', sm: '', me: '', la:'', xl: '' } },
-              width:  { l1: { xs: 0,  sm: 0, me: 0,  la: 0 , xl: 0  }, lN: { xs: 0 , sm: 0,  me: 0,  la: 0, xl: 0  } },
-              height: { l1: { xs: 0,  sm: 0, me: 0,  la: 0 , xl: 0  }, lN: { xs: 0,  sm: 0,  me: 0,  la: 0, xl: 0  } }
-            };
-            this.thumbnailImgRevealed   = false;  // thumbnail image already revealed
-            this.imageDominantColors    = null;   // base64 GIF
-            this.imageDominantColor     = null;   // HEX RGB
-            this.featured =             false;    // featured element
-            this.flickrThumbSizes =     {};       // store URLs for all available thumbnail sizes (flickr)
-            this.picasaThumbs =         null;     // store URLs and sizes
-            this.hovered =              false;    // is the thumbnail currently hovered?
-            this.hoverInitDone =        false;
-            this.contentIsLoaded =      false;    // album: are items already loaded?
-            this.contentLength =        0;        // album: number of items (real number of items in memory)
-            this.numberItems =          0;        // album: number of items (value returned by data source)
-            this.mediaNumber =          0;        // media number in the album
-            this.mediaCounter =         0;        // number of medias in an album
-            this.eltTransform =         [];       // store the CSS transformations
-            this.eltFilter =            [];       // store the CSS filters
-            this.eltEffect =            [];       // store data about hover effects animations
-            this.paginationLastPage =   0;        // for albums
-            this.paginationLastWidth =  0;        // for albums
-            this.customData =           {};
-            this.selected =             false;
-            this.imageWidth =           0;        // image natural (real) width
-            this.imageHeight =          0;        // image natural (real) height
-            this.$elt =                 null;     // pointer to the corresponding DOM element
-            this.$Elts =                [];       // cached pointers to the thumbnail content -> to avoid jQuery().find()
-            this.tags =                 [];       // list of tags of the current item
-            this.albumTagList =         [];       // list of all the tags of the items contained in the current album
-            this.albumTagListSel =      [];       // list of currently selected tags (only for albums)
-            this.exif = { exposure: '', flash: '', focallength: '', fstop: '', iso: '', model: '', time: '', location: ''};
+/*!
+ * @preserve nanogallery2 v3.0.0beta - javascript photo / video gallery and lightbox
+ * Homepage: http://nanogallery2.nanostudio.org
+ * Sources:  https://github.com/nanostudio-org/nanogallery2
+ *
+ * License:  GPLv3 and commercial licence
+ * 
+ * Requirements:
+ *  - jQuery (http://www.jquery.com) - version >= 1.7.1
+ *
+ * Embeded components:
+ *  - shifty (https://github.com/jeremyckahn/shifty)
+ *  - imagesloaded (https://github.com/desandro/imagesloaded)
+ *  - hammer.js (http://hammerjs.github.io/)
+ *  - screenfull.js (https://github.com/sindresorhus/screenfull.js)
+ * Tools:
+ *  - webfont generated with http://fontello.com - mainly based on Font Awesome Copyright (C) 2012 by Dave Gandy (http://fontawesome.io/)
+ *  - ICO online converter: https://iconverticons.com/online/
+ */
+
+ 
+// nanogallery v3.0.0beta
+/*
+- fixed nano_photos_provider2: on gallery initialization, if an album is defined, gallery will not display sub-albums
+- [replaced] label position -> valign - top, bottom, middle / default: bottom
+- tableau de remplacement de nom d'albums (text found -> text to use)
+- new: mosaic layout is now fully responsive
+- new: filtering, option galleryFilterTagsMode/galleryL1FilterTagsMode - possible value 'single', 'multiple'
+- new: filtering, if no tag is selected then no filter is applied
+- new: loading spinner over thumbnail during album content download
+- new: first album level, new options thumbnailL1BorderHorizontal and thumbnailL1BorderVertical
+- new: left / right pagination buttons on top of gallery, new option galleryPaginationTopButtons
+    icons: navigationPaginationPrevious / navigationPaginationNext
+    theme: navigationPagination
+    option galleryPaginationTopButtons : true,
+- new: swipe lightbox up to close it (additional to the existing pan down gesture)
+- new: thumbnails on lightbox (options: viewerGallery ('none', 'bottomOverMedia', 'bottom'), viewerGalleryTHeight, viewerGalleryTWidth)
+- new: callback fnPopupMediaInfo(item, title, content) -> {title: my_title, content: my_content}
+- changed: icon for tags and for tag's filter reset
+- changed: lightbox tool icons layout and background
+- fixed: gallery may not be displayed depending on the display animation
+- changed: touchAnimation default value to false - hover animation on thumbnails are now disabled by default
+- fixed: lightbox, a touch will display toolbars and lable when they are hidden
+- improved: swipe and touch gesture, velocity
+- new: rounded border on thumbnails -> galleryTheme : { thumbnail: { borderRadius
+- new:   thumbnailIcon :         { shadow:'' },
+- CDNJS
+- enhancement: page scrollbar better removed on lightbox display, to avoid page reflow
+- fixed: modal popup not sharp (media info and share), and wrong size on mobile devices
+- removed: viewerDisplayLogo option
+- fixed modal size on mobile
+- fixed: some artefacts around thumbnails in some use cases
+- fixed: #219 dragging in Firefox - many thanks to Largo (https://github.com/Largo)
+- changed: thumbnailOpenImage renamed in thumbnailOpenInLightox
+- new option thumbnailDisplayOrder/L1: randomized thumbnail display order ('', 'random');
+- new imageslideup
+- new option thumbnailDisplayTransitionEasing/L1, default: easeOutQuart
+- new: thumbnailGutterWidth and thumbnailGutterHeight responsive
+- rewrite - shopping cart
+   -> compteur
+   -> toolbar name cart -> shoppingcart
+   -> lightbox -> shoppingcart
+- depreciated: options 'topOverImage', 'bottomOverImage' for lighbox vertical toolbar position
+- depreciated: theme border for lightbox
+- compatibility issue: Jquery 3.0 and 3.1 - issue on width calculation - https://github.com/jquery/jquery/issues/3193
+- changed: option blackList renamed to blockList
+- changed: option whiteList renamed tost
+- minor fixes
+- new: lightbox standalone, without gallery
+
+TODO: 
+- tags :
+  - centre tags
+  - grouper tags (sous-tags)
+- hash : only once -> read but not set
+- enlever bottom bar par defaut
+- améliorer fond dégradé par défaut des imagettes 
+- animation séparées pour les tools imagette
+- si hash n'existe pas -> eviter erreur et effacer hash
+- centrage vertical titre sans description
+- screenful update
+- delete button -> thumbnail/lightbox + callback(with cancel)
+- re-display one thumbnail -> new image, title, etc...
+- double touch in zoom mode -> should not go to next/previous media
+- rajouter custom meta-data dans nanophotosprovider (par ex. prix d'un article)
+- doc: enlever "button" au nom des outils du viewer
+- lightbox pan event on IFRAME
+- scrolltop on ResizeWindowEvent -> ne faire que suite à action utilisateur (ouverture album, pagination, navigation, filtre?)
+
+*/
+ 
+ 
+// ###########################################
+// ##### nanogallery2 as a JQUERY PLUGIN #####
+// ###########################################
+
+
+// Expose plugin as an AMD module if AMD loader is present:
+(function (factory) {
+    "use strict";
+    if (typeof define === 'function' && define.amd) {
+        // AMD. Register as an anonymous module.
+        // define('nanogallery2', ['jquery'], factory);
+        define(['jquery'], factory);
+    } else if (typeof exports === 'object' && typeof require === 'function') {
+        // Browserify
+        factory(require('jquery'));
+    } else {
+        // Browser globals
+        factory(jQuery);
+    }
+}(function ($) {
+// ;(function ($) {
+  "use strict";
+
+  //##### TOOLS/HELPERS ####
+
+  // Convert color to RGB/RGBA
+  function ColorHelperToRGB( color ) {
+    var obj = document.getElementById('ngyColorHelperToRGB');
+    if (obj === null) {
+      obj = document.createElement('div');
+      obj.id = "ngyColorHelperToRGB";
+      obj.style.cssText = 'display: none; color:' + color + ';';
+      document.body.appendChild(obj);
+    }
+    
+    var rgb = getComputedStyle(obj).color;
+
+    // to get HEX value:
+    // var rgb = getComputedStyle(obj).color.match(/\d+/g);
+    // var r = parseInt(rgb[0]).toString(16);
+    // var g = parseInt(rgb[1]).toString(16);
+    // var b = parseInt(rgb[2]).toString(16);
+    // var hex = '#' + r + g + b;
+
+    return rgb;
+  }
+
+  
+  // ##### helper for color handling
+  // - normalise RGB/RGBA/HEX format
+  // - lighten/darken color
+  // Inspired by:          
+  // https://stackoverflow.com/questions/5560248/programmatically-lighten-or-darken-a-hex-color-or-rgb-and-blend-colors
+  // http://www.pimptrizkit.com/?t=20%20Shades
+  function ShadeBlendConvert (p, from, to) {
+    var rgba='';
+    if( from.toUpperCase().substring(0,5) == 'RGBA(' ) {
+      rgba='a';
+      from='rgb('+from.substring(5);
+    }
+
+    if(typeof(p)!="number"||p<-1||p>1||typeof(from)!="string"||(from[0]!='r'&&from[0]!='#')||(typeof(to)!="string"&&typeof(to)!="undefined"))return null;
+    //if(!this.sbcRip)this.sbcRip=function(d){
+    function sbcRip(d){
+      var l=d.length,RGB=new Object();
+      if(l>9){
+        d=d.split(",");
+        if(d.length<3||d.length>4)return null;
+        RGB[0]=i(d[0].slice(4)),RGB[1]=i(d[1]),RGB[2]=i(d[2]),RGB[3]=d[3]?parseFloat(d[3]):-1;
+      }else{
+        if(l==8||l==6||l<4)return null;
+        if(l<6)d="#"+d[1]+d[1]+d[2]+d[2]+d[3]+d[3]+(l>4?d[4]+""+d[4]:"");
+        d=i(d.slice(1),16),RGB[0]=d>>16&255,RGB[1]=d>>8&255,RGB[2]=d&255,RGB[3]=l==9||l==5?r(((d>>24&255)/255)*10000)/10000:-1;
+      }
+      return RGB;
+    }
+    var i=parseInt,r=Math.round,h=from.length>9,h=typeof(to)=="string"?to.length>9?true:to=="c"?!h:false:h,b=p<0,p=b?p*-1:p,to=to&&to!="c"?to:b?"#000000":"#FFFFFF",f=sbcRip(from),t=sbcRip(to);
+    if(!f||!t)return null;
+    if(h)return "rgb"+rgba+"("+r((t[0]-f[0])*p+f[0])+","+r((t[1]-f[1])*p+f[1])+","+r((t[2]-f[2])*p+f[2])+(f[3]<0&&t[3]<0?")":","+(f[3]>-1&&t[3]>-1?r(((t[3]-f[3])*p+f[3])*10000)/10000:t[3]<0?f[3]:t[3])+")");
+    else return "#"+(0x100000000+(f[3]>-1&&t[3]>-1?r(((t[3]-f[3])*p+f[3])*255):t[3]>-1?r(t[3]*255):f[3]>-1?r(f[3]*255):255)*0x1000000+r((t[0]-f[0])*p+f[0])*0x10000+r((t[1]-f[1])*p+f[1])*0x100+r((t[2]-f[2])*p+f[2])).toString(16).slice(f[3]>-1||t[3]>-1?1:3);
+  }
+  
+  
+  // ##### clone a javascript object
+  function cloneJSObject( obj ) {
+    if (obj === null || typeof obj !== 'object') {
+      return obj;
+    }
+
+    var temp = obj.constructor(); // give temp the original obj's constructor
+    for (var key in obj) {
+        temp[key] = cloneJSObject(obj[key]);
+    }
+    return temp;
+  }
+  
+  // get viewport coordinates and size
+  function getViewport() {
+    var $win = jQuery(window);
+    return {
+      l: $win.scrollLeft(),
+      t: $win.scrollTop(),
+      w: $win.width(),
+      h: $win.height()
+    }
+  }
+
+
+  // Check if element is in viewport
+  // avoid if possible (performance issue)
+  function inViewport( $elt, threshold ) {
+    var wp = getViewport(),
+    eltOS = $elt.offset(),
+    th = $elt.outerHeight(true),
+    tw = $elt.outerWidth(true);
+    if( eltOS.top >= (wp.t - threshold) 
+      && (eltOS.top + th) <= (wp.t + wp.h + threshold)
+      && eltOS.left >= (wp.l - threshold) 
+      && (eltOS.left + tw) <= (wp.l + wp.w + threshold) ) {
+      return true;
+    }
+    else {
+      return false;
+    }
+  }
+
+
+  // Check if whole element is in ViewPort
+  // avoid if possible (performance issue)
+  function inViewportVert( $elt, threshold ) {
+    var wp = getViewport(),
+    eltOS = $elt.offset(),
+    th = $elt.outerHeight(true);
+    //var tw=$elt.outerWidth(true);
+
+    if( wp.t == 0 && (eltOS.top) <= (wp.t + wp.h ) ) { return true; }
+
+    if( eltOS.top >= wp.t && (eltOS.top + th) <= (wp.t + wp.h - threshold) ) {
+        return true;
+    }
+    else {
+      return false;
+    }
+  }
+  // Check if top of the element is in ViewPort
+  function topInViewportVert( $elt, threshold ) {
+    var wp = getViewport(),
+    eltOS = $elt.offset(),
+    th = $elt.outerHeight(true);
+
+    // if( wp.t == 0 && (eltOS.top) <= (wp.t + wp.h ) ) { return true; }
+
+    if( eltOS.top >= wp.t && eltOS.top <= (wp.t + wp.h - threshold) ) {
+        return true;
+    }
+    else {
+      return false;
+    }
+  }
+
+
+  // set z-index to display 2 elements on top of all others
+  function set2ElementsOnTop( start, elt1, elt2 ) {
+    var highest_index = 0;
+    if( start=='' ) { start= '*'; }
+    jQuery(start).each(function() {
+      var cur = parseInt(jQuery(this).css('z-index'));
+      highest_index = cur > highest_index ? cur : highest_index;
+    });
+    highest_index++;
+    jQuery(elt2).css('z-index',highest_index+1);
+    jQuery(elt1).css('z-index',highest_index);
+  }
+
+  // set z-index to display element on top of all others
+  function setElementOnTop( start, elt ) {
+    var highest_index = 0;
+    if( start == '' ) { start = '*'; }
+    jQuery(start).each(function() {
+      var cur = parseInt(jQuery(this).css('z-index'));
+      highest_index = cur > highest_index ? cur : highest_index;
+    });
+    highest_index++;
+    jQuery(elt).css('z-index',highest_index);
+  }
+  
+  // return the real type of the object
+  var toType = function( obj ) {
+    // by Angus Croll - http://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/
+    return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase()
+  };    
+    
+
+  $.nanogallery2 = function (elt, options) {
+    // To avoid scope issues, use '_this' instead of 'this'
+    // to reference this class from internal events and functions.
+    var _this = this;
+
+    // Access to jQuery and DOM versions of element
+    _this.$e  = jQuery(elt);
+    _this.e   = elt;
+
+    // Add a reverse reference to the DOM object
+    _this.$e.data('nanogallery2data', _this);
+
+    _this.init = function () {
+  
+      // define these global objects only once per HTML page
+      if (typeof window.NGY2Item === 'undefined') {
+  
+        window.NGY2Tools = (function () {
+
+          function NGY2Tools() {
+            var nextId = 1;                   // private static --> all instances
+          }
+
+          // check album name - albumList/blockList/allowList
+          NGY2Tools.FilterAlbumName = function( title, ID ) {
+            var s = title.toUpperCase();
+            if( this.albumList.length > 0 ) {
+              for( var j=0; j < this.albumList.length; j++) {
+                if( s === this.albumList[j].toUpperCase() || ID === this.albumList[j] ) {
+                  return true;
+                }
+              }
+            }
+            else {
+              var found = false;
+              if( this.allowList !== null ) {
+                //allowList : authorize only album cointaining one of the specified keyword in the title
+                for( var j = 0; j < this.allowList.length; j++) {
+                  if( s.indexOf(this.allowList[j]) !== -1 ) {
+                    found = true;
+                  }
+                }
+                if( !found ) { return false; }
+              }
+
+
+              if( this.blockList !== null ) {
+                //blockList : ignore album cointaining one of the specified keyword in the title
+                for( var j = 0; j < this.blockList.length; j++) {
+                  if( s.indexOf(this.blockList[j]) !== -1 ) { 
+                    return false;
+                  }
+                }
+              }
+              return true;
+            }
+          };
+
+
+          /** @function nanoAlert */
+          /* Display an alert message in a specific element */
+          NGY2Tools.NanoAlert = function(context, msg, verbose) {
+            NGY2Tools.NanoConsoleLog.call(context, msg);
+            if( context.$E.conConsole != null ) {
+              context.$E.conConsole.css({visibility: 'visible', minHeight: '100px'});
+              if( verbose == false ) {
+                context.$E.conConsole.append('<p>' + msg + '</p>');
+              }
+              else {
+                context.$E.conConsole.append('<p>nanogallery2: '+ msg + ' [' + context.baseEltID + ']</p>');
+              }
+            }
+          };
+          
+  
+          /** @function NanoConsoleLog */
+          /* write message to the browser console */
+          NGY2Tools.NanoConsoleLog = function(context, msg) {
+            if (window.console) { console.log('nanogallery2: ' + msg + ' [' + context.baseEltID + ']'); }
+            // debugger;
+          };
+          
+
+          /** @function PreloaderDisplay() */
+          /* Display/hide preloader */
+          NGY2Tools.PreloaderDisplay = function(display) {
+            if( display === true ) {
+              // loading bar at the top of the gallery
+              this.$E.conLoadingB.removeClass('nanoGalleryLBarOff').addClass('nanoGalleryLBar');
+              // spinner over album thumbnail
+              if( this.GOM.albumIdxLoading != undefined && this.GOM.albumIdxLoading != -1 ) {
+                var item = this.I[this.GOM.albumIdxLoading];
+                item.$Elts['.nGY2TnImg'].addClass('nGY2GThumbnailLoaderDisplayed');
+              }
+            }
+            else {
+              // loading bar at the top of the gallery
+              this.$E.conLoadingB.removeClass('nanoGalleryLBar').addClass('nanoGalleryLBarOff');
+              // spinner over album thumbnail
+              if( this.GOM.albumIdxLoading != undefined && this.GOM.albumIdxLoading != -1 ) {
+                var item = this.I[this.GOM.albumIdxLoading];
+                item.$Elts['.nGY2TnImg'].removeClass('nGY2GThumbnailLoaderDisplayed');
+              }
+            }
+          };
+
+          // Scrambles the elements of an array
+					//+ Jonas Raoni Soares Silva
+          //@ http://jsfromhell.com/array/shuffle [v1.0]
+          NGY2Tools.AreaShuffle = function (o) {
+            for (var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
+            return o;
+          };
+          
+          /** @function GetImageTitleFromURL() */
+          /* retrieve filemane */
+          NGY2Tools.GetImageTitleFromURL = function( imageURL ) {
+            if( this.O.thumbnailLabel.get('title') == '%filename' ) {
+              return (imageURL.split('/').pop()).replace('_',' ');
+            }
+            
+            if( this.O.thumbnailLabel.get('title') == '%filenameNoExt' ) {
+              var s=imageURL.split('/').pop();
+              return (s.split('.').shift()).replace('_',' ');
+            }
+            // return imageURL;
+            return '';
+          };
+          
+
+          /** @function AlbumPostProcess() */
+          /* post process one album based on plugin general parameters  --> sorting/maxItems*/
+          NGY2Tools.AlbumPostProcess = function(albumID) {
+
+            // this function can probably be optimized....
+          
+            var sortOrder = this.gallerySorting[this.GOM.curNavLevel];
+            var maxItems = this.galleryMaxItems[this.GOM.curNavLevel];
+
+            if( sortOrder != '' || maxItems > 0 ) {
+            
+              // copy album's items to a new array
+              var currentAlbum = this.I.filter( function( obj ) {
+                return( obj.albumID == albumID && obj.kind != 'albumUp' );
+              });
+        
+              // sorting options
+              switch( sortOrder ) {
+                case 'RANDOM':
+                  currentAlbum = NGY2Tools.AreaShuffle(currentAlbum);
+                  break;
+                case 'REVERSED':
+                  currentAlbum = currentAlbum.reverse();
+                  break;
+                case 'TITLEASC':
+                  currentAlbum.sort(function (a, b) {
+                    return( (a.title.toUpperCase() < b.title.toUpperCase()) ? -1 : ((a.title.toUpperCase() > b.title.toUpperCase()) ? 1 : 0) );
+                  });
+                  break;
+                case 'TITLEDESC':
+                  currentAlbum.sort(function (a, b) {
+                    return( (a.title.toUpperCase() > b.title.toUpperCase()) ? -1 : ((a.title.toUpperCase() < b.title.toUpperCase()) ? 1 : 0) );
+                  });
+                  break;
+              }     
+
+              // max Items
+              if( maxItems > 0 && currentAlbum.length > maxItems ) {
+                currentAlbum.splice(maxItems - 1, currentAlbum.length-maxItems );
+              }
+              
+              // remove the albums's items from the global items array
+              this.I.removeIf( function( obj ) {
+                return( obj.albumID == albumID && obj.kind != 'albumUp' );
+              });
+              
+              // add the sorted items back to the album
+              this.I.push.apply(this.I, currentAlbum);
+
+            }
+          };
+          
+          /** @function FilterByTags() */
+          /* filter out images that have black listed tags (flickr only) */
+          NGY2Tools.FilterByTags = function(data, tagBlackList) {
+            if (data != undefined) {
+              data = data.filter(function (item) {
+                var regex = new RegExp( tagBlackList, "i");
+                if ( Array.isArray(item.tags) ) {
+                  var tagsToTest = item.tags;
+                } else {
+                  var tagsToTest = [item.tags];
+                }
+                return ! tagsToTest.some( function (x) { return regex.test(x); } );
+              });
+            }
+            return data;
+          };
+
+          return NGY2Tools;
+        })(); 
+
+        // ====================
+        // ===== NGY2Item =====
+        // ====================
+        window.NGY2Item = (function() {
+          var nextId = 1;                   // private static --> all instances
+
+          // constructor
+          function NGY2Item( itemID ) {
+          //window.NGY2Item = function( itemID ) {
+            var ID = 0;                     // private
+
+            // public (this instance only)
+            if( itemID === undefined || itemID === null ) {
+              ID = nextId++;
+            }
+            else {
+              ID = itemID;
+            }
+            this.GetID = function () { return ID; };
+            
+            // public
+            this.kind =                 '';       // 'image', 'album' or 'albumUp'
+            this.mediaKind =            'img';    // 'img', 'iframe'
+            this.mediaMarkup =          '';
+            this.G =                    null;     // pointer to global instance
+            this.title =                '';       // image title
+            this.description =          '';       // image description
+            this.albumID =              0;        // ID of the parent album
+            this.src =                  '';       // full sized image URL
+            this.width =                0;        // image width
+            this.height =               0;        // image height
+            this.destinationURL =       '';       // thumbnail destination URL --> open URL instead of displaying image
+            this.downloadURL =          '';       // thumbnail download URL --> specify the image for download button
+            this.author =               '';       // image/album author
+            this.left =                 0;        // store position to animate from old to new
+            this.top =                  0;
+            this.width =                0;        // store size to avoid setting width/height if not required
+            this.height =               0;
+            this.resizedContentWidth=   0;        // store size of content (image) to avoid setting width/height if not required
+            this.resizedContentHeight=  0;
+            this.thumbs = {                       // URLs and sizes for user defined
+              url:    { l1: { xs: '', sm:'', me: '', la: '', xl: '' }, lN: { xs: '', sm: '', me: '', la:'', xl: '' } },
+              width:  { l1: { xs: 0,  sm: 0, me: 0,  la: 0 , xl: 0  }, lN: { xs: 0 , sm: 0,  me: 0,  la: 0, xl: 0  } },
+              height: { l1: { xs: 0,  sm: 0, me: 0,  la: 0 , xl: 0  }, lN: { xs: 0,  sm: 0,  me: 0,  la: 0, xl: 0  } }
+            };
+            this.thumbnailImgRevealed   = false;  // thumbnail image already revealed
+            this.imageDominantColors    = null;   // base64 GIF
+            this.imageDominantColor     = null;   // HEX RGB
+            this.featured =             false;    // featured element
+            this.flickrThumbSizes =     {};       // store URLs for all available thumbnail sizes (flickr)
+            this.picasaThumbs =         null;     // store URLs and sizes
+            this.hovered =              false;    // is the thumbnail currently hovered?
+            this.hoverInitDone =        false;
+            this.contentIsLoaded =      false;    // album: are items already loaded?
+            this.contentLength =        0;        // album: number of items (real number of items in memory)
+            this.numberItems =          0;        // album: number of items (value returned by data source)
+            this.mediaNumber =          0;        // media number in the album
+            this.mediaCounter =         0;        // number of medias in an album
+            this.eltTransform =         [];       // store the CSS transformations
+            this.eltFilter =            [];       // store the CSS filters
+            this.eltEffect =            [];       // store data about hover effects animations
+            this.paginationLastPage =   0;        // for albums
+            this.paginationLastWidth =  0;        // for albums
+            this.customData =           {};
+            this.selected =             false;
+            this.imageWidth =           0;        // image natural (real) width
+            this.imageHeight =          0;        // image natural (real) height
+            this.$elt =                 null;     // pointer to the corresponding DOM element
+            this.$Elts =                [];       // cached pointers to the thumbnail content -> to avoid jQuery().find()
+            this.tags =                 [];       // list of tags of the current item
+            this.albumTagList =         [];       // list of all the tags of the items contained in the current album
+            this.albumTagListSel =      [];       // list of currently selected tags (only for albums)
+            this.exif = { exposure: '', flash: '', focallength: '', fstop: '', iso: '', model: '', time: '', location: ''};
             this.deleted =              false;    // item is deleted -> do not display anymore
-            this.rotationAngle =        0;        // image display rotation angle
-          }
-
-          // public static
-          
-          NGY2Item.Get = function( instance, ID ) {
-            var l = instance.I.length;
-            for( var i = 0; i < l; i++ ) {
-              if( instance.I[i].GetID() == ID ) {
-                return instance.I[i];
-              }
-            }
-            return null;
-          };
-            
-          NGY2Item.GetIdx = function( instance, ID ) {
-            var l = instance.I.length;
-            for( var i = 0; i < l; i++ ) {
-              if( instance.I[i].GetID() == ID ) {
-                return i;
-              }
-            }
-            return -1;
-          };
-          
-          // create new item (image, album or albumUp)
-          NGY2Item.New = function( instance, title, description, ID, albumID, kind, tags ) {
-            var album = NGY2Item.Get( instance, albumID );
-            
-            // title translation
-            if( instance.O.title_translation_map !== null ) {
-              let obj = instance.O.title_translation_map.find(o => o.title === title);
-              if( obj !== undefined ) {
-                title = obj.replace;
-              }
-            }
-            
-            
-            if( albumID != -1 && albumID != 0 && title !='image gallery by nanogallery2 [build]'  ) {
-              if( instance.O.thumbnailLevelUp && album.getContentLength(false) == 0 && instance.O.album == '' ) {
-                // add navigation thumbnail (album up)
-                var item = new NGY2Item('0');
-                instance.I.push( item );
-                album.contentLength += 1;
-                item.title = 'UP';
-                item.albumID = albumID;
-                item.kind = 'albumUp';
-                item.G = instance;
-
-                jQuery.extend( true, item.thumbs.width, instance.tn.defaultSize.width);
-                jQuery.extend( true, item.thumbs.height, instance.tn.defaultSize.height);
-              }
-            }
-            
-            var item = NGY2Item.Get(instance, ID);
-            if( item === null ){
-              // create a new item (otherwise, just update the existing one)
-              item = new NGY2Item(ID);
-              instance.I.push(item);
-              if( albumID != -1 && title !='image gallery by nanogallery2 [build]' ) {
-                album.contentLength+=1;
-              }
-            }
-            item.G = instance;
-
-            item.albumID = albumID;
-            item.kind = kind;
-            if( kind == 'image' ) {
-              album.mediaCounter += 1;
-              item.mediaNumber = album.mediaCounter;
-            }
-
-            // check keyword to find features images/albums
-            var kw = instance.O.thumbnailFeaturedKeyword;
-            if( kw != '' ) {
-              // check if item featured based on a keyword in the title or in the description
-              kw = kw.toUpperCase();
-              var p = title.toUpperCase().indexOf(kw);
-              if( p > -1) {
-                item.featured = true;
-                // remove keyword case unsensitive
-                title = title.substring(0, p) + title.substring(p+kw.length, title.length);
-              }
-              p = description.toUpperCase().indexOf(kw);
-              if( p > -1) {
-                item.featured=true;
-                // remove keyword case unsensitive
-                description=description.substring(0, p) + description.substring(p + kw.length, description.length);
-              }
-            }
-            
-            // TAGS 
-            // if( instance.galleryFilterTags.Get() != false ) {
-              // if( instance.galleryFilterTags.Get() == true ) {
-                // if( tags != '' && tags != undefined ) {
-                  // use set tags
-                  // item.setTags(tags.split(' '));
-                // }
-              // }
-              // else {
-                // extract tags starting with # (in title)
-              if( typeof instance.galleryFilterTags.Get() == 'string' ) {
-                switch( instance.galleryFilterTags.Get().toUpperCase() ) {
-                  case 'TITLE':
-                    var re = /(?:^|\W)#(\w+)(?!\w)/g, match, matches = [];
-                    var tags = "";
-                    while (match = re.exec(title)) {
-                      matches.push(match[1].replace(/^\s*|\s*$/, ''));   //trim trailing/leading whitespace
-                    }
-                    item.setTags(matches);  //tags;
-                    title = title.split('#').join('');   //replaceall
-                    break;
-                  case 'DESCRIPTION':
-                    var re = /(?:^|\W)#(\w+)(?!\w)/g, match, matches = [];
-                    var tags = "";
-                    while (match = re.exec(description)) {
-                      matches.push(match[1].replace(/^\s*|\s*$/, ''));   //trim trailing/leading whitespace
-                    }
-                    item.setTags(matches);  //tags;
-                    description = description.split('#').join('');   //replaceall
-                    break;
-                }
-              }
-                else {
-                  if( tags != '' && tags != undefined ) {
-                    // use set tags
-                    item.setTags(tags.split(' '));
-                  }
-                }
-              // }
-            // }
-            
-            // set (maybe modified) fields title and description
-            item.title = escapeHtml(instance, title);
-            item.description = escapeHtml(instance, description);
-            return item;
-          };
-          
-          
-          // removes logically current item
-          NGY2Item.prototype.delete = function( ) {
-            this.deleted = true;
-            
-            // update content length of parent album
-            this.G.I[NGY2Item.GetIdx(this.G, this.albumID)].contentLength--;
-            this.G.I[NGY2Item.GetIdx(this.G, this.albumID)].numberItems--;
-            
-            // check if in GOM and removes it
-            var nbTn = this.G.GOM.items.length;
-            var ID = this.GetID();
-            var foundIdx = -1;
-            var foundGOMidx = -1;
-            for( var i = 0; i < nbTn ; i++ ) {
-              var curTn = this.G.GOM.items[i];
-              var item=this.G.I[curTn.thumbnailIdx];
-              if( item.GetID() == ID ) {
-                // FOUND
-                if( !curTn.neverDisplayed ) {
-                  foundIdx = curTn.thumbnailIdx;
-                  foundGOMidx = i;
-                }
-              }
-              else {
-                if( foundIdx != -1 ) {
-                  if( !curTn.neverDisplayed ) {
-                    // update index value
-                    item.$getElt('.nGY2GThumbnail').data('index', i-1);
-                    item.$getElt('.nGY2GThumbnailImg').data('index', i-1);
-                  }
-                }
-              }
-            }
-            if( foundIdx != -1 ) {
-              // delete item in GOM and delete thumbnail
-              var G = this.G;
-              if( this.selected == true ) {
-                this.selected = false;
-                G.GOM.nbSelected--;    // update the global counter
-              }
-              if( G.I[foundIdx].$elt !== null ) {
-                G.I[foundIdx].$elt.remove();      // delete thumbnail DOM object
-              }
-              G.GOM.items.splice(foundGOMidx, 1);   // delete in GOM
-              if( G.GOM.lastDisplayedIdx != -1 ) {
-                G.GOM.lastDisplayedIdx -= 1;
-              }
-            }
-            
-            // TODO: update medianumber of the other item in the same album
-          }
-
-          NGY2Item.prototype.addToGOM = function( ) {
-            // retrieve index
-            var ID = this.GetID();
-            var l = this.G.I.length;
-            for( var idx = 0; idx < l; idx++ ) {
-              var item = this.G.I[idx];
-              if( item.GetID() == ID ) {
-                var w = item.thumbImg().width;
-                var h = item.thumbImg().height;
-                // set default size if required
-                if( h == 0 ) {
-                  h = this.G.tn.defaultSize.getHeight();
-                }
-                if( w == 0 ) {
-                  w = this.G.tn.defaultSize.getWidth();
-                }
-                // add to GOM -> will be displayed on next refresh/resize
-                var tn = new this.G.GOM.GTn(idx, w, h);
-                this.G.GOM.items.push(tn);
-                break;
-              }
-            }
-            
-          }
-          
-          
-          // function to avoid XSS issue - Cross Site Scripting
-          // original: https://github.com/janl/mustache.js/blob/master/mustache.js#L55
-          var entityMap = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;', '/': '&#x2F;', '`': '&#x60;', '=': '&#x3D;' };
-          function escapeHtml (instance, string) {
-            if( instance.O.allowHTMLinData == true ) {
-              return string;
-            }
-            else {
-              return String(string).replace(/[&<>"'`=\/]/g, function fromEntityMap (s) {
-                return entityMap[s];
-              });
-            }
-          }
-          
-          
-          NGY2Item.get_nextId = function () {
-            return nextId;
-          };
-
-          //=== public (shared across instances)
-
-          //--- cached sub elements
-          NGY2Item.prototype.$getElt = function( elt, forceRefresh ) {
-            if( this.$elt == null ) { return null; }
-            if( this.$Elts[elt] !== undefined && !forceRefresh == true ) {
-              return this.$Elts[elt];
-            }
-            else {
-              if( elt == '.nGY2GThumbnail' ) {
-                this.$Elts[elt]=this.$elt;
-              }
-              else {
-                this.$Elts[elt]=this.$elt.find(elt);
-              }
-              return this.$Elts[elt];
-            }
-          };
-          
-          // remove one element (in DOM and in cache)
-          NGY2Item.prototype.removeElt = function( elt ) {
-            if( this.$elt == null ) { return; }
-            if( this.$Elts[elt] == undefined) { return; }
-            this.$Elts[elt].remove();
-            var index = this.$Elts.indexOf(elt);
-            this.$Elts.splice(index, 1);
-          };
-
-          //--- returns the album containing the item
-          NGY2Item.prototype.album = function() {
-            return this.G.I[NGY2Item.GetIdx(this.G, this.albumID)];
-          };
-          
-          //--- viewer - transition can be disabled per media kind - returns true if current media supports transition (swipe)
-          NGY2Item.prototype.mediaTransition = function( ) {
-            if( this.G.O.viewerTransitionMediaKind.indexOf( this.mediaKind ) > -1 ) {
-              return true;
-            }
-            return false;
-          };
-
-          //--- set one image (url and size)
-          NGY2Item.prototype.imageSet = function( src, w, h ) {
-            this.src = src;
-            this.width = w;
-            this.height = h;
-          };
-          
-          //--- set one thumbnail (url and size) - screenSize and level are optional
-          NGY2Item.prototype.thumbSet = function( src, w, h, screenSize, level ) {
-            var lst=['xs','sm','me','la','xl'];
-            if( typeof screenSize === 'undefined' || screenSize == '' || screenSize == null ) {
-              for( var i=0; i< lst.length; i++ ) {
-                if( typeof level === 'undefined' || level == '' ) {
-                  this.thumbs.url.l1[lst[i]]=src;
-                  this.thumbs.height.l1[lst[i]]=h;
-                  this.thumbs.width.l1[lst[i]]=w;
-                  this.thumbs.url.lN[lst[i]]=src;
-                  this.thumbs.height.lN[lst[i]]=h;
-                  this.thumbs.width.lN[lst[i]]=w;
-                }
-                else {
-                  this.thumbs.url[level][lst[i]]=src;
-                  this.thumbs.height[level][lst[i]]=h;
-                  this.thumbs.width[level][lst[i]]=w;
-                }
-              }
-            }
-            else {
-              if( typeof level === 'undefined' || level == '' || level == null ) {
-                this.thumbs.url.l1[screenSize]=src;
-                this.thumbs.height.l1[screenSize]=h;
-                this.thumbs.width.l1[screenSize]=w;
-                this.thumbs.url.lN[screenSize]=src;
-                this.thumbs.height.lN[screenSize]=h;
-                this.thumbs.width.lN[screenSize]=w;
-              }
-              else {
-                this.thumbs.url[level][screenSize]=src;
-                this.thumbs.height[level][screenSize]=h;
-                this.thumbs.width[level][screenSize]=w;
-              }
-            }
-          
-            var lst=['xs','sm','me','la','xl'];
-            for( var i=0; i< lst.length; i++ ) {
-              this.thumbs.height.l1[lst[i]]=h;
-            }
-            for( var i=0; i< lst.length; i++ ) {
-              if( this.G.tn.settings.height.lN[lst[i]] == this.G.tn.settings.getH() && this.G.tn.settings.width.l1[lst[i]] == this.G.tn.settings.getW() ) {
-                this.thumbs.height.lN[lst[i]]=h;
-              }
-            }
-          };
-
-          //--- set thumbnail image real height for current level/resolution, and for all others level/resolutions having the same settings
-          NGY2Item.prototype.thumbSetImgHeight = function( h ) {              
-            var lst=['xs','sm','me','la','xl'];
-            for( var i=0; i< lst.length; i++ ) {
-              if( this.G.tn.settings.height.l1[lst[i]] == this.G.tn.settings.getH() && this.G.tn.settings.width.l1[lst[i]] == this.G.tn.settings.getW() ) {
-                this.thumbs.height.l1[lst[i]]=h;
-              }
-            }
-            for( var i=0; i< lst.length; i++ ) {
-              if( this.G.tn.settings.height.lN[lst[i]] == this.G.tn.settings.getH() && this.G.tn.settings.width.l1[lst[i]] == this.G.tn.settings.getW() ) {
-                this.thumbs.height.lN[lst[i]]=h;
-              }
-            }
-          };
-
-          //--- set thumbnail image real width for current level/resolution, and for all others level/resolutions having the same settings
-          NGY2Item.prototype.thumbSetImgWidth = function( w ) {              
-            var lst=['xs','sm','me','la','xl'];
-            for( var i=0; i< lst.length; i++ ) {
-              if( this.G.tn.settings.height.l1[lst[i]] == this.G.tn.settings.getH() && this.G.tn.settings.width.l1[lst[i]] == this.G.tn.settings.getW() ) {
-                this.thumbs.width.l1[lst[i]]=w;
-              }
-            }
-            for( var i=0; i< lst.length; i++ ) {
-              if( this.G.tn.settings.height.lN[lst[i]] == this.G.tn.settings.getH() && this.G.tn.settings.width.l1[lst[i]] == this.G.tn.settings.getW() ) {
-                this.thumbs.width.lN[lst[i]]=w;
-              }
-            }
-          };
-          
-          //--- Returns Thumbnail image (depending of the screen resolution)
-          NGY2Item.prototype.thumbImg = function () {   
-            var tnImg = { src: '', width: 0, height: 0 };
-
-            if( this.title == 'image gallery by nanogallery2 [build]' ) {
-              tnImg.src = this.G.emptyGif;
-              tnImg.url = this.G.emptyGif;
-              return tnImg;
-            }
-            tnImg.src = this.thumbs.url[this.G.GOM.curNavLevel][this.G.GOM.curWidth];
-            tnImg.width = this.thumbs.width[this.G.GOM.curNavLevel][this.G.GOM.curWidth];
-            tnImg.height = this.thumbs.height[this.G.GOM.curNavLevel][this.G.GOM.curWidth];
-            return tnImg;
-          };
-          
-          //--- Set tags to items and add these tags to the album
-          NGY2Item.prototype.setTags = function( tags ) {              
-          if( tags.length > 0 ) {
-              this.tags = tags;
-              var lstTags = this.album().albumTagList;
-              for( var i = 0; i < tags.length; i++ ) {
-                var tfound = false;
-                for( var j = 0; j < lstTags.length; j++ ) {
-                  if( tags[i].toUpperCase() == lstTags[j].toUpperCase() ) {
-                    tfound = true;
-                  }
-                }
-                if( tfound == false) {
-                  this.album().albumTagList.push(tags[i])
-                  // this.album().albumTagListSel.push(tags[i])
-                }
-              }
-            }
-          };
-          
-          //--- check if 1 of current item's tags is selected (tag filter)
-          NGY2Item.prototype.checkTagFilter = function() {
-            if( this.G.galleryFilterTags.Get() != false && this.album().albumTagList.length > 0 ) {
-              if( this.G.O.thumbnailLevelUp && this.kind == 'albumUp' ) {
-                return true;
-              }
-              var found = false;
-              var lstTags = this.album().albumTagListSel;
-              if( lstTags.length == 0 ) {       
-                // no tag is selected -> display all items
-                return true;
-              }
-              for( var i = 0; i < this.tags.length; i++ ) {
-                for( var j = 0; j < lstTags.length; j++ ) {
-                  if( this.tags[i].toUpperCase() == lstTags[j].toUpperCase() ) {
-                    found = true;
-                    break;
-                  }
-                }
-              }
-              return found;
-            }
-            else
-              return true;
-          };
-          
-          //--- check if 1 of current item's tags is found using API search
-          NGY2Item.prototype.isSearchTagFound = function() {
-            if( this.G.GOM.albumSearchTags == '' ) { return true; }
-            if( this.G.O.thumbnailLevelUp && this.kind == 'albumUp' ) { return true; }
-
-            //var lstTags=this.album().albumTagListSel;
-            for( var i = 0; i < this.tags.length; i++ ) {
-              if( this.tags[i].toUpperCase().indexOf( this.G.GOM.albumSearchTags ) >= 0 ) {
-                return true;
-              }
-            }
-            return false;
-          };
-          
-          //--- set the URL of the media to display in the viewer
-          //--- markup is defined for images
-          NGY2Item.prototype.setMediaURL = function( url, mediaKind ) {
-            this.src = url;
-            this.mediaKind = mediaKind;
-            if( mediaKind == 'img' ) {
-              this.mediaMarkup = '<img class="nGY2ViewerMedia" src="' + url + '" alt=" " itemprop="contentURL" draggable="false">';
-            }
-          };
-          
-          
-          //--- check if current item should be displayed
-          NGY2Item.prototype.isToDisplay = function( albumID ) {
-            return this.albumID == albumID && this.checkTagFilter() && this.isSearchFound() && this.isSearchTagFound() && this.deleted == false;
-          };
-          
-          
-          
-          //--- returns the number of items of the current album
-          //--- count using tags filter
-          NGY2Item.prototype.getContentLength = function( filterTags ) {
-            if( filterTags == false || this.albumTagList.length == 0 || this.G.galleryFilterTags.Get() == false ) {
-              return this.contentLength;
-            }
-            else {
-              var l = this.G.I.length;
-              var cnt = 0;
-              var albumID = this.GetID();
-              for( var idx = 0; idx < l; idx++ ) {
-                var item = this.G.I[idx];
-                if( item.isToDisplay(albumID) ) {
-                  cnt++;
-                }
-              }
-              return cnt;
-            }
-          };
-          
-          NGY2Item.prototype.isSearchFound = function() {
-            if( this.G.GOM.albumSearch != '' ) {
-              if( this.title.toUpperCase().indexOf( this.G.GOM.albumSearch ) == -1 ) {
-                return false;
-              }
-            }
-            return true;
-          }
-          
-          
-          //--- for future use...
-          NGY2Item.prototype.responsiveURL = function () {
-            var url = '';
-            switch(this.G.O.kind) {
-              case '':
-                url = this.src;
-                break;
-              case 'flickr':
-                url = this.src;
-                break;
-              case 'picasa':
-              case 'google':
-              case 'google2':
-              default:
-                url = this.src;
-                break;
-            }
-            return url;
-          };
-          
-          
-          //--- Reveal the thumbnail image with animation on opacity
-          NGY2Item.prototype.ThumbnailImageReveal = function () {
-
-            if( this.thumbnailImgRevealed == false ) {
-              this.thumbnailImgRevealed = true;
-              new NGTweenable().tween({
-                from:         { opacity: 0 },
-                to:           { opacity: 1 },
-                attachment:   { item: this },
-                delay:        30,
-                duration:     400,
-                easing:       'easeOutQuart',
-                step:         function (state, att) {
-                  var $e=att.item.$getElt('.nGY2TnImg');
-                  if( $e != null ) {
-                    $e.css( state );
-                  }
-                }
-              });
-            }
-          };
-          
-          
-          // In case of thumbnails with stacks - apply a percent to a value which include a unit
-          function ValueApplyPercent( str, percent ) {
-            str=String(str);
-            if( str === '0' || percent == 1 ) { return str; }
-            var n = Number(str.replace(/[a-zA-Z]/g, ''));
-            var ar = str.match(/([^\-0-9\.]+)/g);
-            var a = '';
-            if( ar != null && ar.length > 0 ) {
-              a = ar.join();
-            }
-             
-            if( isNaN(n) || n == 0 ) {
-              return str;
-            }
-
-            n = n * percent;
-            return n + a;
-          } 
-          
-          //--- 2D/3D CSS transform - apply the cached value to element
-          NGY2Item.prototype.CSSTransformApply = function ( eltClass ) {
-            var obj = this.eltTransform[eltClass];
-
-            if( eltClass == '.nGY2GThumbnail' ) {
-              // thumbnail
-              var nbStacks = obj.$elt.length-1;
-              var pTranslateX = 1;
-              var pTranslateY = 1;
-              var pTranslateZ = 1;
-              var pTranslate = 1;
-              var pRotateX = 1;
-              var pRotateY = 1;
-              var pRotateZ = 1;
-              var pRotate = 1;
-              var pScale = 1;
-              for( var n = nbStacks; n >= 0; n-- ) {
-                // units must be given with
-                var v = 'translateX(' + ValueApplyPercent(obj.translateX,pTranslateX) + ') translateY(' + ValueApplyPercent(obj.translateY,pTranslateY) + ') translateZ(' + ValueApplyPercent(obj.translateZ,pTranslateZ) + ') scale(' + ValueApplyPercent(obj.scale,pScale) + ') translate(' + ValueApplyPercent(obj.translate,pTranslate) + ')';
-                if( !(this.G.IE <= 9) && !this.G.isGingerbread ) {
-                  v += ' rotateX(' + ValueApplyPercent(obj.rotateX,pRotateX) + ') rotateY(' + ValueApplyPercent(obj.rotateY,pRotateY) + ') rotateZ(' + ValueApplyPercent(obj.rotateZ,pRotateZ) + ') rotate(' + ValueApplyPercent(obj.rotate,pRotate) + ')';
-                }
-                else {
-                  v += ' rotate(' + ValueApplyPercent(obj.rotateZ,pRotateZ) + ')';
-                }
-                obj.$elt[n].style[this.G.CSStransformName] = v;
-                
-                if( nbStacks > 0 ) {
-                  // apply a percent to the stack elements
-                  pTranslateX -= this.G.tn.opt.Get('stacksTranslateX');
-                  pTranslateY -= this.G.tn.opt.Get('stacksTranslateY');
-                  pTranslateZ -= this.G.tn.opt.Get('stacksTranslateZ');
-                  pRotateX    -= this.G.tn.opt.Get('stacksRotateX');
-                  pRotateY    -= this.G.tn.opt.Get('stacksRotateY');
-                  pRotateZ    -= this.G.tn.opt.Get('stacksRotateZ');
-                  pScale      -= this.G.tn.opt.Get('stacksScale');
-                }
-              }
-            }
-            else {
-              // thumbnail sub element
-              if( obj.$elt != null ) {
-                for( var n = 0; n < obj.$elt.length; n++ ) {
-                  if( obj.$elt[n] != undefined ) {
-                    // units must be given with
-                    var v = 'translateX(' + obj.translateX + ') translateY(' + obj.translateY + ') translateZ(' + obj.translateZ + ') scale(' + obj.scale + ') translate(' + obj.translate + ')';
-                    if( !(this.G.IE <= 9) && !this.G.isGingerbread ) {
-                      v += ' rotateX(' + obj.rotateX + ') rotateY(' + obj.rotateY + ') rotateZ(' + obj.rotateZ + ') rotate(' + obj.rotate + ')';
-                    }
-                    else {
-                      v += ' rotate(' + obj.rotateZ + ')';
-                    }
-                    obj.$elt[n].style[this.G.CSStransformName] = v;
-                    }
-                }
-              }
-            }
-          };
-
-          //--- 2D/3D CSS transform - set a value in cache
-          NGY2Item.prototype.CSSTransformSet = function ( eltClass, transform, value, forceRefresh ) {
-            if( this.eltTransform[eltClass] == undefined ) {
-              this.eltTransform[eltClass] = { translateX: 0, translateY: 0, translateZ: 0, rotateX: 0, rotateY: 0, rotateZ: 0, scale: 1, translate: '0px,0px', rotate: 0 };
-              this.eltTransform[eltClass].$elt = this.$getElt(eltClass);
-            }
-            this.eltTransform[eltClass][transform] = value;
-            if( forceRefresh === true ) {
-              this.eltTransform[eltClass].$elt = this.$getElt(eltClass, true);
-            }
-          };
-
-          //--- CSS Filters - apply the cached value to element
-          NGY2Item.prototype.CSSFilterApply = function ( eltClass ) {
-            var obj = this.eltFilter[eltClass];
-            var v = 'blur(' + obj.blur + ') brightness(' + obj.brightness + ') grayscale(' + obj.grayscale + ') sepia(' + obj.sepia + ') contrast(' + obj.contrast + ') opacity(' + obj.opacity + ') saturate(' + obj.saturate + ')';
-            if( obj.$elt != null ) {
-              for( var n = 0; n < obj.$elt.length; n++ ) {
-                if( obj.$elt[n] != undefined ) {
-                  obj.$elt[n].style.WebkitFilter = v;
-                  obj.$elt[n].style.filter = v;
-                }
-              }
-            }
-          };
-
-          //--- CSS Filters - set a value in cache
-          NGY2Item.prototype.CSSFilterSet = function ( eltClass, filter, value, forceRefresh ) {
-            if( this.eltFilter[eltClass] == undefined ) {
-              this.eltFilter[eltClass] = { blur: 0, brightness: '100%', grayscale: '0%', sepia: '0%', contrast: '100%', opacity: '100%', saturate: '100%' };
-              this.eltFilter[eltClass].$elt = this.$getElt(eltClass);
-            }
-            this.eltFilter[eltClass][filter] = value;
-            if( forceRefresh === true ) {
-              this.eltTransform[eltClass].$elt = this.$getElt(eltClass, true);
-            }
-          };
-
-          //--- thumbnail hover animation
-          NGY2Item.prototype.animate = function ( effect, delay, hoverIn ) {
-            if( this.$getElt() == null  ) { return; }
-
-            var context = {};
-            context.G = this.G;
-            context.item = this;
-            context.effect = effect;
-            context.hoverIn = hoverIn;
-            context.cssKind = '';
-            if( hoverIn ) {
-              // HOVER IN
-              
-              if( this.eltEffect[effect.element] == undefined ) {
-                this.eltEffect[effect.element] = [];
-              }
-              if( this.eltEffect[effect.element][effect.type] == undefined ) {
-                this.eltEffect[effect.element][effect.type] = { initialValue: 0, lastValue: 0 };
-              }
-              if( effect.firstKeyframe ) {
-                // store initial and current value -> for use in the back animation
-                this.eltEffect[effect.element][effect.type] = { initialValue: effect.from, lastValue: effect.from};
-              }
-              
-              context.animeFrom = effect.from;
-              context.animeTo = effect.to;
-              context.animeDuration = parseInt(effect.duration);
-              context.animeDelay = 30 + parseInt(effect.delay + delay);  // 30ms is a default delay to avoid conflict with other initializations
-              context.animeEasing = effect.easing;
-            }
-            else {
-              // HOVER OUT
-              if( effect.firstKeyframe ) {
-                context.animeFrom = this.eltEffect[effect.element][effect.type].lastValue;
-                context.animeTo = this.eltEffect[effect.element][effect.type].initialValue;
-                // context.animeTo=effect.from;
-              }
-              else {
-                // context.animeFrom=effect.from;
-                context.animeFrom = this.eltEffect[effect.element][effect.type].lastValue;
-                context.animeTo = this.eltEffect[effect.element][effect.type].initialValue;
-                // context.animeTo=effect.to;
-                
-              }
-              
-              context.animeDuration = parseInt(effect.durationBack);
-              context.animeDelay = 30 + parseInt(effect.delayBack + delay);   // 30ms is a default delay to avoid conflict with other initializations
-              context.animeEasing = effect.easingBack;
-            }
-            
-
-            // detect if animation on CSS transform
-            var transform=['translateX', 'translateY', 'translateZ', 'scale', 'rotateX', 'rotateY', 'rotateZ'];
-            for( var i = 0; i < transform.length; i++ ) {
-              if( effect.type == transform[i] ) {
-                context.cssKind = 'transform';
-                break;
-              }
-            }
-
-            // detect if animation on CSS filter
-            var filter=['blur', 'brightness', 'grayscale', 'sepia', 'contrast', 'opacity', 'saturate'];
-            for( var i = 0; i < filter.length; i++ ) {
-              if( effect.type == filter[i] ) {
-                context.cssKind = 'filter';
-                break;
-              }
-            }
-            // handle some special cases
-            if( hoverIn && effect.element == '.nGY2GThumbnail' && ( effect.type == 'scale' || effect.type == 'rotateX') ) {
-              this.G.GOM.lastZIndex++;
-              this.$getElt(effect.element).css('z-index', this.G.GOM.lastZIndex);
-              // setElementOnTop(this.G.$E.base, this.$getElt(effect.element) );
-            }
-            
-            // animation
-            var tweenable = new NGTweenable();
-            context.tweenable=tweenable;
-            tweenable.tween({
-              attachment:   context,
-              from:         { 'v': context.animeFrom },
-              to:           { 'v': context.animeTo },
-              duration:     context.animeDuration,    //parseInt(effect.duration),
-              delay:        context.animeDelay,       //parseInt(effect.delay),
-              easing:       context.animeEasing,      //'easeOutQuart',
-              
-              step: function (state, att) {
-                if( att.item.$getElt() == null ) {
-                  // the thumbnail may be destroyed since the start of the animation
-                  att.tweenable.stop(false);
-                  // att.tweenable.dispose();
-                  return;
-                }
-                if( att.hoverIn && !att.item.hovered ) {
-                  // thumbnail no more hovered
-                  att.tweenable.stop(false);
-                  // att.tweenable.dispose();
-                  return;
-                }
-
-                if( att.G.VOM.viewerDisplayed ) {
-                  att.tweenable.stop(false);
-                  // att.tweenable.dispose();
-                  return;
-                }
-                
-                // test if in delay phase
-                if( state.v == att.animeFrom ) { return; }
-                
-                switch( att.cssKind ) {
-                  case 'transform':
-                    // window.ng_draf( function() {
-                      att.item.CSSTransformSet(att.effect.element, att.effect.type, state.v);
-                      att.item.CSSTransformApply( att.effect.element );
-                    // });
-                    break;
-                  case 'filter':
-                    // window.ng_draf( function() {
-                      att.item.CSSFilterSet(att.effect.element, att.effect.type, state.v);
-                      att.item.CSSFilterApply( att.effect.element );
-                    // });
-                    break;
-                  default:
-                    var v=state.v;
-                    if( state.v.substring(0,4) == 'rgb(' || state.v.substring(0,5) == 'rgba(' ) {
-                      // to remove values after the dot (not supported by RGB/RGBA)
-                      // v=ngtinycolor(state.v).toRgbString();
-                      v = ShadeBlendConvert(0, v);
-                    }
-                    // window.ng_draf( function() {
-                      att.item.$getElt( att.effect.element ).css( att.effect.type, v );
-                    // });
-                    break;
-                }
-                if( hoverIn ) {
-                  // store value for back animation
-                  att.item.eltEffect[att.effect.element][att.effect.type].lastValue = state.v;
-                }
-              },
-              
-              finish: function (state, att) {
-                if( hoverIn ) {
-                  // store value for back animation
-                  att.item.eltEffect[att.effect.element][att.effect.type].lastValue = state.v;
-                }
-
-                if( att.item.$getElt() == null ) {
-                  // the thumbnail may be destroyed since the start of the animation
-                  return;
-                }
-                if( att.hoverIn && !att.item.hovered ) {
-                  // thumbnail no more hovered
-                  return;
-                }
-
-                if( att.G.VOM.viewerDisplayed ) {
-                  return;
-                }
-
-                // window.ng_draf( function() {
-                  switch( att.cssKind ) {
-                    case 'transform':
-                      att.item.CSSTransformSet(att.effect.element, att.effect.type, att.animeTo);
-                      att.item.CSSTransformApply(att.effect.element);
-                      break;
-                    case 'filter':
-                      att.item.CSSFilterSet(att.effect.element, att.effect.type, att.animeTo);
-                      att.item.CSSFilterApply(att.effect.element);
-                      break;
-                    default:
-                      att.item.$getElt(att.effect.element).css(att.effect.type, att.animeTo);
-                      break;
-                  }
-                // });
-              }
-            });
-          };
-
-          return NGY2Item;
-        })();
-          
-      }
-
-      _this.options = jQuery.extend(true, {}, jQuery.nanogallery2.defaultOptions, options);
-      // Initialization code
-      _this.nG2 = null;
-      _this.nG2 = new nanoGALLERY2();
-      _this.nG2.initiateGallery2(_this.e, _this.options );
-
-    };
-      
-    // PUBLIC EXPOSED METHODS
-    _this.test = function() {
-      //alert('test');
-      // console.dir(_this.nG.G.I.length);
-      // console.dir(_this.nG);
-      // debugger;
-      //privateTest();
-    }
-
-    
-    // Run initializer
-    _this.init();
-  };
- 
-  jQuery.nanogallery2.defaultOptions = {
-    kind :                        '',
-    userID :                      '',
-    photoset :                    '',
-    album:                        '',
-    blockList :                   'scrapbook|profil|auto backup',
-    allowList :                   '',
-    albumList :                   '',
-    albumList2 :                  null,
-    RTL :                         false,
-    flickrSkipOriginal :          true,
-    breadcrumbAutoHideTopLevel :  true,
-    displayBreadcrumb :           true,
-    breadcrumbOnlyCurrentLevel :  true,
-    breadcrumbHideIcons :         true,
-    theme :                       'nGY2',
+            this.rotationAngle =        0;        // image display rotation angle
+          }
+
+          // public static
+          
+          NGY2Item.Get = function( instance, ID ) {
+            var l = instance.I.length;
+            for( var i = 0; i < l; i++ ) {
+              if( instance.I[i].GetID() == ID ) {
+                return instance.I[i];
+              }
+            }
+            return null;
+          };
+            
+          NGY2Item.GetIdx = function( instance, ID ) {
+            var l = instance.I.length;
+            for( var i = 0; i < l; i++ ) {
+              if( instance.I[i].GetID() == ID ) {
+                return i;
+              }
+            }
+            return -1;
+          };
+          
+          // create new item (image, album or albumUp)
+          NGY2Item.New = function( instance, title, description, ID, albumID, kind, tags ) {
+            var album = NGY2Item.Get( instance, albumID );
+            
+            // title translation
+            if( instance.O.title_translation_map !== null ) {
+              let obj = instance.O.title_translation_map.find(o => o.title === title);
+              if( obj !== undefined ) {
+                title = obj.replace;
+              }
+            }
+            
+            
+            if( albumID != -1 && albumID != 0 && title !='image gallery by nanogallery2 [build]'  ) {
+              if( instance.O.thumbnailLevelUp && album.getContentLength(false) == 0 && instance.O.album == '' ) {
+                // add navigation thumbnail (album up)
+                var item = new NGY2Item('0');
+                instance.I.push( item );
+                album.contentLength += 1;
+                item.title = 'UP';
+                item.albumID = albumID;
+                item.kind = 'albumUp';
+                item.G = instance;
+
+                jQuery.extend( true, item.thumbs.width, instance.tn.defaultSize.width);
+                jQuery.extend( true, item.thumbs.height, instance.tn.defaultSize.height);
+              }
+            }
+            
+            var item = NGY2Item.Get(instance, ID);
+            if( item === null ){
+              // create a new item (otherwise, just update the existing one)
+              item = new NGY2Item(ID);
+              instance.I.push(item);
+              if( albumID != -1 && title !='image gallery by nanogallery2 [build]' ) {
+                album.contentLength+=1;
+              }
+            }
+            item.G = instance;
+
+            item.albumID = albumID;
+            item.kind = kind;
+            if( kind == 'image' ) {
+              album.mediaCounter += 1;
+              item.mediaNumber = album.mediaCounter;
+            }
+
+            // check keyword to find features images/albums
+            var kw = instance.O.thumbnailFeaturedKeyword;
+            if( kw != '' ) {
+              // check if item featured based on a keyword in the title or in the description
+              kw = kw.toUpperCase();
+              var p = title.toUpperCase().indexOf(kw);
+              if( p > -1) {
+                item.featured = true;
+                // remove keyword case unsensitive
+                title = title.substring(0, p) + title.substring(p+kw.length, title.length);
+              }
+              p = description.toUpperCase().indexOf(kw);
+              if( p > -1) {
+                item.featured=true;
+                // remove keyword case unsensitive
+                description=description.substring(0, p) + description.substring(p + kw.length, description.length);
+              }
+            }
+            
+            // TAGS 
+            // if( instance.galleryFilterTags.Get() != false ) {
+              // if( instance.galleryFilterTags.Get() == true ) {
+                // if( tags != '' && tags != undefined ) {
+                  // use set tags
+                  // item.setTags(tags.split(' '));
+                // }
+              // }
+              // else {
+                // extract tags starting with # (in title)
+              if( typeof instance.galleryFilterTags.Get() == 'string' ) {
+                switch( instance.galleryFilterTags.Get().toUpperCase() ) {
+                  case 'TITLE':
+                    var re = /(?:^|\W)#(\w+)(?!\w)/g, match, matches = [];
+                    var tags = "";
+                    while (match = re.exec(title)) {
+                      matches.push(match[1].replace(/^\s*|\s*$/, ''));   //trim trailing/leading whitespace
+                    }
+                    item.setTags(matches);  //tags;
+                    title = title.split('#').join('');   //replaceall
+                    break;
+                  case 'DESCRIPTION':
+                    var re = /(?:^|\W)#(\w+)(?!\w)/g, match, matches = [];
+                    var tags = "";
+                    while (match = re.exec(description)) {
+                      matches.push(match[1].replace(/^\s*|\s*$/, ''));   //trim trailing/leading whitespace
+                    }
+                    item.setTags(matches);  //tags;
+                    description = description.split('#').join('');   //replaceall
+                    break;
+                }
+              }
+                else {
+                  if( tags != '' && tags != undefined ) {
+                    // use set tags
+                    item.setTags(tags.split(' '));
+                  }
+                }
+              // }
+            // }
+            
+            // set (maybe modified) fields title and description
+            item.title = escapeHtml(instance, title);
+            item.description = escapeHtml(instance, description);
+            return item;
+          };
+          
+          
+          // removes logically current item
+          NGY2Item.prototype.delete = function( ) {
+            this.deleted = true;
+            
+            // update content length of parent album
+            this.G.I[NGY2Item.GetIdx(this.G, this.albumID)].contentLength--;
+            this.G.I[NGY2Item.GetIdx(this.G, this.albumID)].numberItems--;
+            
+            // check if in GOM and removes it
+            var nbTn = this.G.GOM.items.length;
+            var ID = this.GetID();
+            var foundIdx = -1;
+            var foundGOMidx = -1;
+            for( var i = 0; i < nbTn ; i++ ) {
+              var curTn = this.G.GOM.items[i];
+              var item=this.G.I[curTn.thumbnailIdx];
+              if( item.GetID() == ID ) {
+                // FOUND
+                if( !curTn.neverDisplayed ) {
+                  foundIdx = curTn.thumbnailIdx;
+                  foundGOMidx = i;
+                }
+              }
+              else {
+                if( foundIdx != -1 ) {
+                  if( !curTn.neverDisplayed ) {
+                    // update index value
+                    item.$getElt('.nGY2GThumbnail').data('index', i-1);
+                    item.$getElt('.nGY2GThumbnailImg').data('index', i-1);
+                  }
+                }
+              }
+            }
+            if( foundIdx != -1 ) {
+              // delete item in GOM and delete thumbnail
+              var G = this.G;
+              if( this.selected == true ) {
+                this.selected = false;
+                G.GOM.nbSelected--;    // update the global counter
+              }
+              if( G.I[foundIdx].$elt !== null ) {
+                G.I[foundIdx].$elt.remove();      // delete thumbnail DOM object
+              }
+              G.GOM.items.splice(foundGOMidx, 1);   // delete in GOM
+              if( G.GOM.lastDisplayedIdx != -1 ) {
+                G.GOM.lastDisplayedIdx -= 1;
+              }
+            }
+            
+            // TODO: update medianumber of the other item in the same album
+          }
+
+          NGY2Item.prototype.addToGOM = function( ) {
+            // retrieve index
+            var ID = this.GetID();
+            var l = this.G.I.length;
+            for( var idx = 0; idx < l; idx++ ) {
+              var item = this.G.I[idx];
+              if( item.GetID() == ID ) {
+                var w = item.thumbImg().width;
+                var h = item.thumbImg().height;
+                // set default size if required
+                if( h == 0 ) {
+                  h = this.G.tn.defaultSize.getHeight();
+                }
+                if( w == 0 ) {
+                  w = this.G.tn.defaultSize.getWidth();
+                }
+                // add to GOM -> will be displayed on next refresh/resize
+                var tn = new this.G.GOM.GTn(idx, w, h);
+                this.G.GOM.items.push(tn);
+                break;
+              }
+            }
+            
+          }
+          
+          
+          // function to avoid XSS issue - Cross Site Scripting
+          // original: https://github.com/janl/mustache.js/blob/master/mustache.js#L55
+          var entityMap = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;', '/': '&#x2F;', '`': '&#x60;', '=': '&#x3D;' };
+          function escapeHtml (instance, string) {
+            if( instance.O.allowHTMLinData == true ) {
+              return string;
+            }
+            else {
+              return String(string).replace(/[&<>"'`=\/]/g, function fromEntityMap (s) {
+                return entityMap[s];
+              });
+            }
+          }
+          
+          
+          NGY2Item.get_nextId = function () {
+            return nextId;
+          };
+
+          //=== public (shared across instances)
+
+          //--- cached sub elements
+          NGY2Item.prototype.$getElt = function( elt, forceRefresh ) {
+            if( this.$elt == null ) { return null; }
+            if( this.$Elts[elt] !== undefined && !forceRefresh == true ) {
+              return this.$Elts[elt];
+            }
+            else {
+              if( elt == '.nGY2GThumbnail' ) {
+                this.$Elts[elt]=this.$elt;
+              }
+              else {
+                this.$Elts[elt]=this.$elt.find(elt);
+              }
+              return this.$Elts[elt];
+            }
+          };
+          
+          // remove one element (in DOM and in cache)
+          NGY2Item.prototype.removeElt = function( elt ) {
+            if( this.$elt == null ) { return; }
+            if( this.$Elts[elt] == undefined) { return; }
+            this.$Elts[elt].remove();
+            var index = this.$Elts.indexOf(elt);
+            this.$Elts.splice(index, 1);
+          };
+
+          //--- returns the album containing the item
+          NGY2Item.prototype.album = function() {
+            return this.G.I[NGY2Item.GetIdx(this.G, this.albumID)];
+          };
+          
+          //--- viewer - transition can be disabled per media kind - returns true if current media supports transition (swipe)
+          NGY2Item.prototype.mediaTransition = function( ) {
+            if( this.G.O.viewerTransitionMediaKind.indexOf( this.mediaKind ) > -1 ) {
+              return true;
+            }
+            return false;
+          };
+
+          //--- set one image (url and size)
+          NGY2Item.prototype.imageSet = function( src, w, h ) {
+            this.src = src;
+            this.width = w;
+            this.height = h;
+          };
+          
+          //--- set one thumbnail (url and size) - screenSize and level are optional
+          NGY2Item.prototype.thumbSet = function( src, w, h, screenSize, level ) {
+            var lst=['xs','sm','me','la','xl'];
+            if( typeof screenSize === 'undefined' || screenSize == '' || screenSize == null ) {
+              for( var i=0; i< lst.length; i++ ) {
+                if( typeof level === 'undefined' || level == '' ) {
+                  this.thumbs.url.l1[lst[i]]=src;
+                  this.thumbs.height.l1[lst[i]]=h;
+                  this.thumbs.width.l1[lst[i]]=w;
+                  this.thumbs.url.lN[lst[i]]=src;
+                  this.thumbs.height.lN[lst[i]]=h;
+                  this.thumbs.width.lN[lst[i]]=w;
+                }
+                else {
+                  this.thumbs.url[level][lst[i]]=src;
+                  this.thumbs.height[level][lst[i]]=h;
+                  this.thumbs.width[level][lst[i]]=w;
+                }
+              }
+            }
+            else {
+              if( typeof level === 'undefined' || level == '' || level == null ) {
+                this.thumbs.url.l1[screenSize]=src;
+                this.thumbs.height.l1[screenSize]=h;
+                this.thumbs.width.l1[screenSize]=w;
+                this.thumbs.url.lN[screenSize]=src;
+                this.thumbs.height.lN[screenSize]=h;
+                this.thumbs.width.lN[screenSize]=w;
+              }
+              else {
+                this.thumbs.url[level][screenSize]=src;
+                this.thumbs.height[level][screenSize]=h;
+                this.thumbs.width[level][screenSize]=w;
+              }
+            }
+          
+            var lst=['xs','sm','me','la','xl'];
+            for( var i=0; i< lst.length; i++ ) {
+              this.thumbs.height.l1[lst[i]]=h;
+            }
+            for( var i=0; i< lst.length; i++ ) {
+              if( this.G.tn.settings.height.lN[lst[i]] == this.G.tn.settings.getH() && this.G.tn.settings.width.l1[lst[i]] == this.G.tn.settings.getW() ) {
+                this.thumbs.height.lN[lst[i]]=h;
+              }
+            }
+          };
+
+          //--- set thumbnail image real height for current level/resolution, and for all others level/resolutions having the same settings
+          NGY2Item.prototype.thumbSetImgHeight = function( h ) {              
+            var lst=['xs','sm','me','la','xl'];
+            for( var i=0; i< lst.length; i++ ) {
+              if( this.G.tn.settings.height.l1[lst[i]] == this.G.tn.settings.getH() && this.G.tn.settings.width.l1[lst[i]] == this.G.tn.settings.getW() ) {
+                this.thumbs.height.l1[lst[i]]=h;
+              }
+            }
+            for( var i=0; i< lst.length; i++ ) {
+              if( this.G.tn.settings.height.lN[lst[i]] == this.G.tn.settings.getH() && this.G.tn.settings.width.l1[lst[i]] == this.G.tn.settings.getW() ) {
+                this.thumbs.height.lN[lst[i]]=h;
+              }
+            }
+          };
+
+          //--- set thumbnail image real width for current level/resolution, and for all others level/resolutions having the same settings
+          NGY2Item.prototype.thumbSetImgWidth = function( w ) {              
+            var lst=['xs','sm','me','la','xl'];
+            for( var i=0; i< lst.length; i++ ) {
+              if( this.G.tn.settings.height.l1[lst[i]] == this.G.tn.settings.getH() && this.G.tn.settings.width.l1[lst[i]] == this.G.tn.settings.getW() ) {
+                this.thumbs.width.l1[lst[i]]=w;
+              }
+            }
+            for( var i=0; i< lst.length; i++ ) {
+              if( this.G.tn.settings.height.lN[lst[i]] == this.G.tn.settings.getH() && this.G.tn.settings.width.l1[lst[i]] == this.G.tn.settings.getW() ) {
+                this.thumbs.width.lN[lst[i]]=w;
+              }
+            }
+          };
+          
+          //--- Returns Thumbnail image (depending of the screen resolution)
+          NGY2Item.prototype.thumbImg = function () {   
+            var tnImg = { src: '', width: 0, height: 0 };
+
+            if( this.title == 'image gallery by nanogallery2 [build]' ) {
+              tnImg.src = this.G.emptyGif;
+              tnImg.url = this.G.emptyGif;
+              return tnImg;
+            }
+            tnImg.src = this.thumbs.url[this.G.GOM.curNavLevel][this.G.GOM.curWidth];
+            tnImg.width = this.thumbs.width[this.G.GOM.curNavLevel][this.G.GOM.curWidth];
+            tnImg.height = this.thumbs.height[this.G.GOM.curNavLevel][this.G.GOM.curWidth];
+            return tnImg;
+          };
+          
+          //--- Set tags to items and add these tags to the album
+          NGY2Item.prototype.setTags = function( tags ) {              
+          if( tags.length > 0 ) {
+              this.tags = tags;
+              var lstTags = this.album().albumTagList;
+              for( var i = 0; i < tags.length; i++ ) {
+                var tfound = false;
+                for( var j = 0; j < lstTags.length; j++ ) {
+                  if( tags[i].toUpperCase() == lstTags[j].toUpperCase() ) {
+                    tfound = true;
+                  }
+                }
+                if( tfound == false) {
+                  this.album().albumTagList.push(tags[i])
+                  // this.album().albumTagListSel.push(tags[i])
+                }
+              }
+            }
+          };
+          
+          //--- check if 1 of current item's tags is selected (tag filter)
+          NGY2Item.prototype.checkTagFilter = function() {
+            if( this.G.galleryFilterTags.Get() != false && this.album().albumTagList.length > 0 ) {
+              if( this.G.O.thumbnailLevelUp && this.kind == 'albumUp' ) {
+                return true;
+              }
+              var found = false;
+              var lstTags = this.album().albumTagListSel;
+              if( lstTags.length == 0 ) {       
+                // no tag is selected -> display all items
+                return true;
+              }
+              for( var i = 0; i < this.tags.length; i++ ) {
+                for( var j = 0; j < lstTags.length; j++ ) {
+                  if( this.tags[i].toUpperCase() == lstTags[j].toUpperCase() ) {
+                    found = true;
+                    break;
+                  }
+                }
+              }
+              return found;
+            }
+            else
+              return true;
+          };
+          
+          //--- check if 1 of current item's tags is found using API search
+          NGY2Item.prototype.isSearchTagFound = function() {
+            if( this.G.GOM.albumSearchTags == '' ) { return true; }
+            if( this.G.O.thumbnailLevelUp && this.kind == 'albumUp' ) { return true; }
+
+            //var lstTags=this.album().albumTagListSel;
+            for( var i = 0; i < this.tags.length; i++ ) {
+              if( this.tags[i].toUpperCase().indexOf( this.G.GOM.albumSearchTags ) >= 0 ) {
+                return true;
+              }
+            }
+            return false;
+          };
+          
+          //--- set the URL of the media to display in the viewer
+          //--- markup is defined for images
+          NGY2Item.prototype.setMediaURL = function( url, mediaKind ) {
+            this.src = url;
+            this.mediaKind = mediaKind;
+            if( mediaKind == 'img' ) {
+              this.mediaMarkup = '<img class="nGY2ViewerMedia" src="' + url + '" alt=" " itemprop="contentURL" draggable="false">';
+            }
+          };
+          
+          
+          //--- check if current item should be displayed
+          NGY2Item.prototype.isToDisplay = function( albumID ) {
+            return this.albumID == albumID && this.checkTagFilter() && this.isSearchFound() && this.isSearchTagFound() && this.deleted == false;
+          };
+          
+          
+          
+          //--- returns the number of items of the current album
+          //--- count using tags filter
+          NGY2Item.prototype.getContentLength = function( filterTags ) {
+            if( filterTags == false || this.albumTagList.length == 0 || this.G.galleryFilterTags.Get() == false ) {
+              return this.contentLength;
+            }
+            else {
+              var l = this.G.I.length;
+              var cnt = 0;
+              var albumID = this.GetID();
+              for( var idx = 0; idx < l; idx++ ) {
+                var item = this.G.I[idx];
+                if( item.isToDisplay(albumID) ) {
+                  cnt++;
+                }
+              }
+              return cnt;
+            }
+          };
+          
+          NGY2Item.prototype.isSearchFound = function() {
+            if( this.G.GOM.albumSearch != '' ) {
+              if( this.title.toUpperCase().indexOf( this.G.GOM.albumSearch ) == -1 ) {
+                return false;
+              }
+            }
+            return true;
+          }
+          
+          
+          //--- for future use...
+          NGY2Item.prototype.responsiveURL = function () {
+            var url = '';
+            switch(this.G.O.kind) {
+              case '':
+                url = this.src;
+                break;
+              case 'flickr':
+                url = this.src;
+                break;
+              case 'picasa':
+              case 'google':
+              case 'google2':
+              default:
+                url = this.src;
+                break;
+            }
+            return url;
+          };
+          
+          
+          //--- Reveal the thumbnail image with animation on opacity
+          NGY2Item.prototype.ThumbnailImageReveal = function () {
+
+            if( this.thumbnailImgRevealed == false ) {
+              this.thumbnailImgRevealed = true;
+              new NGTweenable().tween({
+                from:         { opacity: 0 },
+                to:           { opacity: 1 },
+                attachment:   { item: this },
+                delay:        30,
+                duration:     400,
+                easing:       'easeOutQuart',
+                step:         function (state, att) {
+                  var $e=att.item.$getElt('.nGY2TnImg');
+                  if( $e != null ) {
+                    $e.css( state );
+                  }
+                }
+              });
+            }
+          };
+          
+          
+          // In case of thumbnails with stacks - apply a percent to a value which include a unit
+          function ValueApplyPercent( str, percent ) {
+            str=String(str);
+            if( str === '0' || percent == 1 ) { return str; }
+            var n = Number(str.replace(/[a-zA-Z]/g, ''));
+            var ar = str.match(/([^\-0-9\.]+)/g);
+            var a = '';
+            if( ar != null && ar.length > 0 ) {
+              a = ar.join();
+            }
+             
+            if( isNaN(n) || n == 0 ) {
+              return str;
+            }
+
+            n = n * percent;
+            return n + a;
+          } 
+          
+          //--- 2D/3D CSS transform - apply the cached value to element
+          NGY2Item.prototype.CSSTransformApply = function ( eltClass ) {
+            var obj = this.eltTransform[eltClass];
+
+            if( eltClass == '.nGY2GThumbnail' ) {
+              // thumbnail
+              var nbStacks = obj.$elt.length-1;
+              var pTranslateX = 1;
+              var pTranslateY = 1;
+              var pTranslateZ = 1;
+              var pTranslate = 1;
+              var pRotateX = 1;
+              var pRotateY = 1;
+              var pRotateZ = 1;
+              var pRotate = 1;
+              var pScale = 1;
+              for( var n = nbStacks; n >= 0; n-- ) {
+                // units must be given with
+                var v = 'translateX(' + ValueApplyPercent(obj.translateX,pTranslateX) + ') translateY(' + ValueApplyPercent(obj.translateY,pTranslateY) + ') translateZ(' + ValueApplyPercent(obj.translateZ,pTranslateZ) + ') scale(' + ValueApplyPercent(obj.scale,pScale) + ') translate(' + ValueApplyPercent(obj.translate,pTranslate) + ')';
+                if( !(this.G.IE <= 9) && !this.G.isGingerbread ) {
+                  v += ' rotateX(' + ValueApplyPercent(obj.rotateX,pRotateX) + ') rotateY(' + ValueApplyPercent(obj.rotateY,pRotateY) + ') rotateZ(' + ValueApplyPercent(obj.rotateZ,pRotateZ) + ') rotate(' + ValueApplyPercent(obj.rotate,pRotate) + ')';
+                }
+                else {
+                  v += ' rotate(' + ValueApplyPercent(obj.rotateZ,pRotateZ) + ')';
+                }
+                obj.$elt[n].style[this.G.CSStransformName] = v;
+                
+                if( nbStacks > 0 ) {
+                  // apply a percent to the stack elements
+                  pTranslateX -= this.G.tn.opt.Get('stacksTranslateX');
+                  pTranslateY -= this.G.tn.opt.Get('stacksTranslateY');
+                  pTranslateZ -= this.G.tn.opt.Get('stacksTranslateZ');
+                  pRotateX    -= this.G.tn.opt.Get('stacksRotateX');
+                  pRotateY    -= this.G.tn.opt.Get('stacksRotateY');
+                  pRotateZ    -= this.G.tn.opt.Get('stacksRotateZ');
+                  pScale      -= this.G.tn.opt.Get('stacksScale');
+                }
+              }
+            }
+            else {
+              // thumbnail sub element
+              if( obj.$elt != null ) {
+                for( var n = 0; n < obj.$elt.length; n++ ) {
+                  if( obj.$elt[n] != undefined ) {
+                    // units must be given with
+                    var v = 'translateX(' + obj.translateX + ') translateY(' + obj.translateY + ') translateZ(' + obj.translateZ + ') scale(' + obj.scale + ') translate(' + obj.translate + ')';
+                    if( !(this.G.IE <= 9) && !this.G.isGingerbread ) {
+                      v += ' rotateX(' + obj.rotateX + ') rotateY(' + obj.rotateY + ') rotateZ(' + obj.rotateZ + ') rotate(' + obj.rotate + ')';
+                    }
+                    else {
+                      v += ' rotate(' + obj.rotateZ + ')';
+                    }
+                    obj.$elt[n].style[this.G.CSStransformName] = v;
+                    }
+                }
+              }
+            }
+          };
+
+          //--- 2D/3D CSS transform - set a value in cache
+          NGY2Item.prototype.CSSTransformSet = function ( eltClass, transform, value, forceRefresh ) {
+            if( this.eltTransform[eltClass] == undefined ) {
+              this.eltTransform[eltClass] = { translateX: 0, translateY: 0, translateZ: 0, rotateX: 0, rotateY: 0, rotateZ: 0, scale: 1, translate: '0px,0px', rotate: 0 };
+              this.eltTransform[eltClass].$elt = this.$getElt(eltClass);
+            }
+            this.eltTransform[eltClass][transform] = value;
+            if( forceRefresh === true ) {
+              this.eltTransform[eltClass].$elt = this.$getElt(eltClass, true);
+            }
+          };
+
+          //--- CSS Filters - apply the cached value to element
+          NGY2Item.prototype.CSSFilterApply = function ( eltClass ) {
+            var obj = this.eltFilter[eltClass];
+            var v = 'blur(' + obj.blur + ') brightness(' + obj.brightness + ') grayscale(' + obj.grayscale + ') sepia(' + obj.sepia + ') contrast(' + obj.contrast + ') opacity(' + obj.opacity + ') saturate(' + obj.saturate + ')';
+            if( obj.$elt != null ) {
+              for( var n = 0; n < obj.$elt.length; n++ ) {
+                if( obj.$elt[n] != undefined ) {
+                  obj.$elt[n].style.WebkitFilter = v;
+                  obj.$elt[n].style.filter = v;
+                }
+              }
+            }
+          };
+
+          //--- CSS Filters - set a value in cache
+          NGY2Item.prototype.CSSFilterSet = function ( eltClass, filter, value, forceRefresh ) {
+            if( this.eltFilter[eltClass] == undefined ) {
+              this.eltFilter[eltClass] = { blur: 0, brightness: '100%', grayscale: '0%', sepia: '0%', contrast: '100%', opacity: '100%', saturate: '100%' };
+              this.eltFilter[eltClass].$elt = this.$getElt(eltClass);
+            }
+            this.eltFilter[eltClass][filter] = value;
+            if( forceRefresh === true ) {
+              this.eltTransform[eltClass].$elt = this.$getElt(eltClass, true);
+            }
+          };
+
+          //--- thumbnail hover animation
+          NGY2Item.prototype.animate = function ( effect, delay, hoverIn ) {
+            if( this.$getElt() == null  ) { return; }
+
+            var context = {};
+            context.G = this.G;
+            context.item = this;
+            context.effect = effect;
+            context.hoverIn = hoverIn;
+            context.cssKind = '';
+            if( hoverIn ) {
+              // HOVER IN
+              
+              if( this.eltEffect[effect.element] == undefined ) {
+                this.eltEffect[effect.element] = [];
+              }
+              if( this.eltEffect[effect.element][effect.type] == undefined ) {
+                this.eltEffect[effect.element][effect.type] = { initialValue: 0, lastValue: 0 };
+              }
+              if( effect.firstKeyframe ) {
+                // store initial and current value -> for use in the back animation
+                this.eltEffect[effect.element][effect.type] = { initialValue: effect.from, lastValue: effect.from};
+              }
+              
+              context.animeFrom = effect.from;
+              context.animeTo = effect.to;
+              context.animeDuration = parseInt(effect.duration);
+              context.animeDelay = 30 + parseInt(effect.delay + delay);  // 30ms is a default delay to avoid conflict with other initializations
+              context.animeEasing = effect.easing;
+            }
+            else {
+              // HOVER OUT
+              if( effect.firstKeyframe ) {
+                context.animeFrom = this.eltEffect[effect.element][effect.type].lastValue;
+                context.animeTo = this.eltEffect[effect.element][effect.type].initialValue;
+                // context.animeTo=effect.from;
+              }
+              else {
+                // context.animeFrom=effect.from;
+                context.animeFrom = this.eltEffect[effect.element][effect.type].lastValue;
+                context.animeTo = this.eltEffect[effect.element][effect.type].initialValue;
+                // context.animeTo=effect.to;
+                
+              }
+              
+              context.animeDuration = parseInt(effect.durationBack);
+              context.animeDelay = 30 + parseInt(effect.delayBack + delay);   // 30ms is a default delay to avoid conflict with other initializations
+              context.animeEasing = effect.easingBack;
+            }
+            
+
+            // detect if animation on CSS transform
+            var transform=['translateX', 'translateY', 'translateZ', 'scale', 'rotateX', 'rotateY', 'rotateZ'];
+            for( var i = 0; i < transform.length; i++ ) {
+              if( effect.type == transform[i] ) {
+                context.cssKind = 'transform';
+                break;
+              }
+            }
+
+            // detect if animation on CSS filter
+            var filter=['blur', 'brightness', 'grayscale', 'sepia', 'contrast', 'opacity', 'saturate'];
+            for( var i = 0; i < filter.length; i++ ) {
+              if( effect.type == filter[i] ) {
+                context.cssKind = 'filter';
+                break;
+              }
+            }
+            // handle some special cases
+            if( hoverIn && effect.element == '.nGY2GThumbnail' && ( effect.type == 'scale' || effect.type == 'rotateX') ) {
+              this.G.GOM.lastZIndex++;
+              this.$getElt(effect.element).css('z-index', this.G.GOM.lastZIndex);
+              // setElementOnTop(this.G.$E.base, this.$getElt(effect.element) );
+            }
+            
+            // animation
+            var tweenable = new NGTweenable();
+            context.tweenable=tweenable;
+            tweenable.tween({
+              attachment:   context,
+              from:         { 'v': context.animeFrom },
+              to:           { 'v': context.animeTo },
+              duration:     context.animeDuration,    //parseInt(effect.duration),
+              delay:        context.animeDelay,       //parseInt(effect.delay),
+              easing:       context.animeEasing,      //'easeOutQuart',
+              
+              step: function (state, att) {
+                if( att.item.$getElt() == null ) {
+                  // the thumbnail may be destroyed since the start of the animation
+                  att.tweenable.stop(false);
+                  // att.tweenable.dispose();
+                  return;
+                }
+                if( att.hoverIn && !att.item.hovered ) {
+                  // thumbnail no more hovered
+                  att.tweenable.stop(false);
+                  // att.tweenable.dispose();
+                  return;
+                }
+
+                if( att.G.VOM.viewerDisplayed ) {
+                  att.tweenable.stop(false);
+                  // att.tweenable.dispose();
+                  return;
+                }
+                
+                // test if in delay phase
+                if( state.v == att.animeFrom ) { return; }
+                
+                switch( att.cssKind ) {
+                  case 'transform':
+                    // window.ng_draf( function() {
+                      att.item.CSSTransformSet(att.effect.element, att.effect.type, state.v);
+                      att.item.CSSTransformApply( att.effect.element );
+                    // });
+                    break;
+                  case 'filter':
+                    // window.ng_draf( function() {
+                      att.item.CSSFilterSet(att.effect.element, att.effect.type, state.v);
+                      att.item.CSSFilterApply( att.effect.element );
+                    // });
+                    break;
+                  default:
+                    var v=state.v;
+                    if( state.v.substring(0,4) == 'rgb(' || state.v.substring(0,5) == 'rgba(' ) {
+                      // to remove values after the dot (not supported by RGB/RGBA)
+                      // v=ngtinycolor(state.v).toRgbString();
+                      v = ShadeBlendConvert(0, v);
+                    }
+                    // window.ng_draf( function() {
+                      att.item.$getElt( att.effect.element ).css( att.effect.type, v );
+                    // });
+                    break;
+                }
+                if( hoverIn ) {
+                  // store value for back animation
+                  att.item.eltEffect[att.effect.element][att.effect.type].lastValue = state.v;
+                }
+              },
+              
+              finish: function (state, att) {
+                if( hoverIn ) {
+                  // store value for back animation
+                  att.item.eltEffect[att.effect.element][att.effect.type].lastValue = state.v;
+                }
+
+                if( att.item.$getElt() == null ) {
+                  // the thumbnail may be destroyed since the start of the animation
+                  return;
+                }
+                if( att.hoverIn && !att.item.hovered ) {
+                  // thumbnail no more hovered
+                  return;
+                }
+
+                if( att.G.VOM.viewerDisplayed ) {
+                  return;
+                }
+
+                // window.ng_draf( function() {
+                  switch( att.cssKind ) {
+                    case 'transform':
+                      att.item.CSSTransformSet(att.effect.element, att.effect.type, att.animeTo);
+                      att.item.CSSTransformApply(att.effect.element);
+                      break;
+                    case 'filter':
+                      att.item.CSSFilterSet(att.effect.element, att.effect.type, att.animeTo);
+                      att.item.CSSFilterApply(att.effect.element);
+                      break;
+                    default:
+                      att.item.$getElt(att.effect.element).css(att.effect.type, att.animeTo);
+                      break;
+                  }
+                // });
+              }
+            });
+          };
+
+          return NGY2Item;
+        })();
+          
+      }
+
+      _this.options = jQuery.extend(true, {}, jQuery.nanogallery2.defaultOptions, options);
+      // Initialization code
+      _this.nG2 = null;
+      _this.nG2 = new nanoGALLERY2();
+      _this.nG2.initiateGallery2(_this.e, _this.options );
+
+    };
+      
+    // PUBLIC EXPOSED METHODS
+    _this.test = function() {
+      //alert('test');
+      // console.dir(_this.nG.G.I.length);
+      // console.dir(_this.nG);
+      // debugger;
+      //privateTest();
+    }
+
+    / Run initializer
+    _this.init();
+  };
+ 
+  jQuery.nanogallery2.defaultOptions = {
+    kind :                        '',
+    userID :                      '',
+    photoset :                    '',
+    album:                        '',
+
+    blackList :                   'scrapbook|profil|auto backup',
+    whiteList :                   '',
+    tagBlackList :                '',
+
+    albumList :                   '',
+    albumList2 :                  null,
+    RTL :                         false,
+    flickrSkipOriginal :          true,
+    breadcrumbAutoHideTopLevel :  true,
+    displayBreadcrumb :           true,
+    breadcrumbOnlyCurrentLevel :  true,
+    breadcrumbHideIcons :         true,
+    theme :                       'nGY2',
     galleryTheme :                'dark',
-    viewerTheme :                 'dark',
-    items :                       null,
-    itemsBaseURL :                '',
-    thumbnailSelectable :         false,
-    dataProvider:                 '',
+    viewerTheme :                 'dark',
+    items :                       null,
+    itemsBaseURL :                '',
+    thumbnailSelectable :         false,
+    dataProvider:                 '',
     allowHTMLinData:              false,
     locationHash :                true,
     slideshowDelay :              3000,
     slideshowAutoStart :          false,
 
     debugMode:                    false,
-
-    title_translation_map:        null,
-    galleryDisplayMoreStep :      2,
-    galleryDisplayMode :          'fullContent',
-    galleryL1DisplayMode :        null,
-    galleryPaginationMode :       'rectangles',   // 'dots', 'rectangles', 'numbers'
-    galleryPaginationTopButtons : true,
-    galleryMaxRows :              2,
-    galleryL1MaxRows :            null,
-    galleryLastRowFull:           false,
-    galleryL1LastRowFull:         null,
-    galleryLayoutEngine :         'default',
-    paginationSwipe:              true,
-    paginationVisiblePages :      10,
-    galleryFilterTags :           false,    // possible values: false, true, 'title', 'description'
-    galleryL1FilterTags :         null,     // possible values: false, true, 'title', 'description'
-    galleryFilterTagsMode :       'single',
-    galleryL1FilterTagsMode :     null,
-    galleryMaxItems :             0,        // maximum number of items per album  --> only flickr, google2, nano_photos_provider2
-    galleryL1MaxItems :           null,     // maximum number of items per gallery page --> only flickr, google2, nano_photos_provider2
-    gallerySorting :              '',
-    galleryL1Sorting :            null,
-    galleryDisplayTransition :    'none',
-    galleryL1DisplayTransition :  null,
-    galleryDisplayTransitionDuration :    1000,
-    galleryL1DisplayTransitionDuration :  null,
+
+    title_translation_map:        null,
+    galleryDisplayMoreStep :      2,
+    galleryDisplayMode :          'fullContent',
+    galleryL1DisplayMode :        null,
+    galleryPaginationMode :       'rectangles',   // 'dots', 'rectangles', 'numbers'
+    galleryPaginationTopButtons : true,
+    galleryMaxRows :              2,
+    galleryL1MaxRows :            null,
+    galleryLastRowFull:           false,
+    galleryL1LastRowFull:         null,
+    galleryLayoutEngine :         'default',
+    paginationSwipe:              true,
+    paginationVisiblePages :      10,
+    galleryFilterTags :           false,    // possible values: false, true, 'title', 'description'
+    galleryL1FilterTags :         null,     // possible values: false, true, 'title', 'description'
+    galleryFilterTagsMode :       'single',
+    galleryL1FilterTagsMode :     null,
+    galleryMaxItems :             0,        // maximum number of items per album  --> only flickr, google2, nano_photos_provider2
+    galleryL1MaxItems :           null,     // maximum number of items per gallery page --> only flickr, google2, nano_photos_provider2
+    gallerySorting :              '',
+    galleryL1Sorting :            null,
+    galleryDisplayTransition :    'none',
+    galleryL1DisplayTransition :  null,
+    galleryDisplayTransitionDuration :    1000,
+    galleryL1DisplayTransitionDuration :  null,
     galleryResizeAnimation :      true,
     galleryRenderDelay :          30,
 
-    thumbnailCrop :               true,
+    thumbnailCrop :               true,
     thumbnailL1Crop :             null,
     thumbnailCropScaleFactor :    1.5,
     thumbnailLevelUp :            false,
     thumbnailAlignment :          'fillWidth',
-    thumbnailWidth :              300,
+    thumbnailWidth :              300,
     thumbnailL1Width :            null,
-    thumbnailHeight :             200,
-    thumbnailL1Height :           null,
-    thumbnailBaseGridHeight :     0,
+    thumbnailHeight :             200,
+    thumbnailL1Height :           null,
+    thumbnailBaseGridHeight :     0,
     thumbnailL1BaseGridHeight :   null,
-    thumbnailGutterWidth :        2,
+    thumbnailGutterWidth :        2,
     thumbnailL1GutterWidth :      null,
-    thumbnailGutterHeight :       2,
+    thumbnailGutterHeight :       2,
     thumbnailL1GutterHeight :     null,
-    thumbnailBorderVertical :     2,
+    thumbnailBorderVertical :     2,
     thumbnailL1BorderVertical :   null,
-    thumbnailBorderHorizontal :   2,
+    thumbnailBorderHorizontal :   2,
     thumbnailL1BorderHorizontal : null,
     thumbnailFeaturedKeyword :    '*featured',
     thumbnailAlbumDisplayImage :  false,
     thumbnailHoverEffect2 :       'toolsAppear',
-    thumbnailBuildInit2 :         '',
-    thumbnailStacks :             0,
-    thumbnailL1Stacks :           null,
-    thumbnailStacksTranslateX :   0,
-    thumbnailL1StacksTranslateX : null,
-    thumbnailStacksTranslateY :   0,
-    thumbnailL1StacksTranslateY : null,
-    thumbnailStacksTranslateZ :   0,
-    thumbnailL1StacksTranslateZ : null,
-    thumbnailStacksRotateX :      0,
-    thumbnailL1StacksRotateX :    null,
-    thumbnailStacksRotateY :      0,
-    thumbnailL1StacksRotateY :    null,
-    thumbnailStacksRotateZ :      0,
-    thumbnailL1StacksRotateZ :    null,
-    thumbnailStacksScale :        0,
-    thumbnailL1StacksScale :      null,
-    thumbnailDisplayOutsideScreen: true,
-    thumbnailWaitImageLoaded:     true,
-    thumbnailSliderDelay:         2000,
-    galleryBuildInit2 :           '',
-    portable :                    false,
+    thumbnailBuildInit2 :         '',
+    thumbnailStacks :             0,
+    thumbnailL1Stacks :           null,
+    thumbnailStacksTranslateX :   0,
+    thumbnailL1StacksTranslateX : null,
+    thumbnailStacksTranslateY :   0,
+    thumbnailL1StacksTranslateY : null,
+    thumbnailStacksTranslateZ :   0,
+    thumbnailL1StacksTranslateZ : null,
+    thumbnailStacksRotateX :      0,
+    thumbnailL1StacksRotateX :    null,
+    thumbnailStacksRotateY :      0,
+    thumbnailL1StacksRotateY :    null,
+    thumbnailStacksRotateZ :      0,
+    thumbnailL1StacksRotateZ :    null,
+    thumbnailStacksScale :        0,
+    thumbnailL1StacksScale :      null,
+    thumbnailDisplayOutsideScreen: true,
+    thumbnailWaitImageLoaded:     true,
+    thumbnailSliderDelay:         2000,
+    galleryBuildInit2 :           '',
+    portable :                    false,
     eventsDebounceDelay:          30,
     
-    touchAnimation :              false,
+    touchAnimation :              false,
     touchAnimationL1 :            undefined,
     touchAutoOpenDelay :          0,
 
     thumbnailLabel : {
       position :                  'overImageOnBottom',
-      align:                      'center',
+      align:                      'center',
       valign:                     'bottom',
       display :                   true,
       displayDescription :        false,
-      titleMaxLength :            0,
+      titleMaxLength :            0,
       titleMultiLine :            false,
-      descriptionMaxLength :      0,
+      descriptionMaxLength :      0,
       descriptionMultiLine :      false,
       hideIcons :                 true,
       title :                     ''
@@ -1514,14444 +1533,14450 @@ TODO:
 
     thumbnailToolbarImage :       { topLeft: 'select', topRight : 'featured' },
     thumbnailToolbarAlbum :       { topLeft: 'select', topRight : 'counter'  },
-    thumbnailDisplayOrder :       '',
-    thumbnailL1DisplayOrder :     null,
-    thumbnailDisplayInterval :    15,
+    thumbnailDisplayOrder :       '',
+    thumbnailL1DisplayOrder :     null,
+    thumbnailDisplayInterval :    15,
     thumbnailL1DisplayInterval :  null,
-    thumbnailDisplayTransition :  'fadeIn',
+    thumbnailDisplayTransition :  'fadeIn',
     thumbnailL1DisplayTransition : null,
-    thumbnailDisplayTransitionEasing :  'easeOutQuart',
+    thumbnailDisplayTransitionEasing :  'easeOutQuart',
     thumbnailL1DisplayTransitionEasing : null,
-    thumbnailDisplayTransitionDuration:   240,
+    thumbnailDisplayTransitionDuration:   240,
     thumbnailL1DisplayTransitionDuration: null,
-    thumbnailOpenInLightox :      true,
+    thumbnailOpenInLightox :      true,
     thumbnailOpenOriginal :       false,
     
-    lightboxStandalone:						false,
-		viewer :                      'internal',
-    viewerFullscreen:             false,
-    imageTransition :             'swipe2',
-    viewerTransitionMediaKind :   'img',
-    viewerZoom :                  true,
-    viewerImageDisplay :          '',
-    openOnStart :                 '',
-    viewerHideToolsDelay :        4000,
-    viewerToolbar : {
-      display :                   true,
-      position :                  'bottom',
-      fullWidth :                 false,
-      align :                     'center',
-      autoMinimize :              0,
-      standard :                  'minimizeButton,label',
-      minimized :                 'minimizeButton,label,infoButton,shareButton,downloadButton,fullscreenButton'
-    },
-    viewerTools : {
-      topLeft :                   'pageCounter,playPauseButton',
-      topRight :                  'rotateLeft,rotateRight,fullscreenButton,closeButton' 
-    },
-		viewerGallery:								'bottomOverMedia',
-		viewerGalleryTWidth:					40,
-		viewerGalleryTHeight:  				40,
-    
+    lightboxStandalone:						false,
+		viewer :                      'internal',
+    viewerFullscreen:             false,
+    imageTransition :             'swipe2',
+    viewerTransitionMediaKind :   'img',
+    viewerZoom :                  true,
+    viewerImageDisplay :          '',
+    openOnStart :                 '',
+    viewerHideToolsDelay :        4000,
+    viewerToolbar : {
+      display :                   true,
+      position :                  'bottom',
+      fullWidth :                 false,
+      align :                     'center',
+      autoMinimize :              0,
+      standard :                  'minimizeButton,label',
+      minimized :                 'minimizeButton,label,infoButton,shareButton,downloadButton,fullscreenButton'
+    },
+    viewerTools : {
+      topLeft :                   'pageCounter,playPauseButton',
+      topRight :                  'rotateLeft,rotateRight,fullscreenButton,closeButton' 
+    },
+		viewerGallery:								'bottomOverMedia',
+		viewerGalleryTWidth:					40,
+		viewerGalleryTHeight:  				40,
+    
     breakpointSizeSM :            480,
     breakpointSizeME :            992,
     breakpointSizeLA :            1200,
-    breakpointSizeXL :            1800,
+    breakpointSizeXL :            1800,
     
     fnThumbnailInit :             null,
     fnThumbnailHoverInit :        null,
     fnThumbnailHover :            null,
     fnThumbnailHoverOut :         null,
-    fnThumbnailDisplayEffect :    null,
-    fnViewerInfo :                null,
+    fnThumbnailDisplayEffect :    null,
+    fnViewerInfo :                null,
     fnImgToolbarCustInit :        null,
     fnImgToolbarCustDisplay :     null,
-    fnImgToolbarCustClick :       null,
-    fnProcessData :               null,
-    fnThumbnailSelection :        null,
-    fnGalleryRenderStart :        null,
-    fnGalleryRenderEnd :          null,
-    fnGalleryObjectModelBuilt :   null,
-    fnGalleryLayoutApplied :      null,
+    fnImgToolbarCustClick :       null,
+    fnProcessData :               null,
+    fnThumbnailSelection :        null,
+    fnGalleryRenderStart :        null,
+    fnGalleryRenderEnd :          null,
+    fnGalleryObjectModelBuilt :   null,
+    fnGalleryLayoutApplied :      null,
     fnThumbnailClicked :          null,
     fnShoppingCartUpdated :       null,
     fnThumbnailToolCustAction :   null,
     fnThumbnailOpen :             null,
-    fnImgDisplayed :              null,
+    fnImgDisplayed :              null,
     fnPopupMediaInfo :            null,
 
-    i18n : {
-      'breadcrumbHome' : 'Galleries', 'breadcrumbHome_FR' : 'Galeries',
-      'thumbnailImageTitle' : '', 'thumbnailAlbumTitle' : '',
-      'thumbnailImageDescription' : '', 'thumbnailAlbumDescription' : '',
-      'infoBoxPhoto' : 'Photo', 'infoBoxDate' : 'Date', 'infoBoxAlbum' : 'Album', 'infoBoxDimensions' : 'Dimensions', 'infoBoxFilename' : 'Filename', 'infoBoxFileSize' : 'File size', 'infoBoxCamera' : 'Camera', 'infoBoxFocalLength' : 'Focal length', 'infoBoxExposure' : 'Exposure', 'infoBoxFNumber' : 'F Number', 'infoBoxISO' : 'ISO', 'infoBoxMake' : 'Make', 'infoBoxFlash' : 'Flash', 'infoBoxViews' : 'Views', 'infoBoxComments' : 'Comments'
-    },
-    icons : {
-      // example for font awesome: <i style="color:#eee;" class="fa fa-search-plus"></i>
-      thumbnailAlbum:               '<i class="nGY2Icon-folder-empty"></i>',
-      thumbnailImage:               '<i class="nGY2Icon-picture"></i>',
-      breadcrumbAlbum:              '<i class="nGY2Icon-folder-empty"></i>',
-      breadcrumbHome:               '<i class="nGY2Icon-home"></i>',
-      breadcrumbSeparator:          '<i class="nGY2Icon-left-open"></i>',
-      breadcrumbSeparatorRtl:       '<i class="nGY2Icon-right-open"></i>',
-      navigationFilterSelected:     '<i style="color:#fff;" class="nGY2Icon-ok"></i>',
-      navigationFilterUnselected:   '<i style="color:#ddd;opacity:0.3;" class="nGY2Icon-circle-empty"></i>',
-      navigationFilterSelectedAll:  '<i class="nGY2Icon-ccw"></i>',
-      navigationPaginationPrevious: '<i class="nGY2Icon-ngy2_chevron-left"></i>',
-      navigationPaginationNext:     '<i class="nGY2Icon-ngy2_chevron-right"></i>',
-      thumbnailSelected:            '<i style="color:#bff;" class="nGY2Icon-ok-circled"></i>',
-      thumbnailUnselected:          '<i style="color:#bff;" class="nGY2Icon-circle-empty"></i>',
-      thumbnailFeatured:            '<i style="color:#dd5;" class="nGY2Icon-star"></i>',
-      thumbnailCounter:             '<i class="nGY2Icon-picture"></i>',
-      thumbnailShare:               '<i class="nGY2Icon-ngy2_share2"></i>',
-      thumbnailDownload:            '<i class="nGY2Icon-ngy2_download2"></i>',
-      thumbnailInfo:                '<i class="nGY2Icon-ngy2_info2"></i>',
-      thumbnailShoppingcart:        '<i class="nGY2Icon-basket"></i>',
-      thumbnailDisplay:             '<i class="nGY2Icon-resize-full"></i>',
-      thumbnailCustomTool1:         'T1',
-      thumbnailCustomTool2:         'T2',
-      thumbnailCustomTool3:         'T3',
-      thumbnailCustomTool4:         'T4',
-      thumbnailCustomTool5:         'T5',
-      thumbnailCustomTool6:         'T6',
-      thumbnailCustomTool7:         'T7',
-      thumbnailCustomTool8:         'T8',
-      thumbnailCustomTool9:         'T9',
-      thumbnailCustomTool10:        'T10',
-      thumbnailAlbumUp:             '<i style="font-size: 3em;" class="nGY2Icon-ngy2_chevron_up2"></i>',
-      paginationNext:               '<i class="nGY2Icon-right-open"></i>',
+    i18n : {
+      'breadcrumbHome' : 'Galleries', 'breadcrumbHome_FR' : 'Galeries',
+      'thumbnailImageTitle' : '', 'thumbnailAlbumTitle' : '',
+      'thumbnailImageDescription' : '', 'thumbnailAlbumDescription' : '',
+      'infoBoxPhoto' : 'Photo', 'infoBoxDate' : 'Date', 'infoBoxAlbum' : 'Album', 'infoBoxDimensions' : 'Dimensions', 'infoBoxFilename' : 'Filename', 'infoBoxFileSize' : 'File size', 'infoBoxCamera' : 'Camera', 'infoBoxFocalLength' : 'Focal length', 'infoBoxExposure' : 'Exposure', 'infoBoxFNumber' : 'F Number', 'infoBoxISO' : 'ISO', 'infoBoxMake' : 'Make', 'infoBoxFlash' : 'Flash', 'infoBoxViews' : 'Views', 'infoBoxComments' : 'Comments'
+    },
+    icons : {
+      // example for font awesome: <i style="color:#eee;" class="fa fa-search-plus"></i>
+      thumbnailAlbum:               '<i class="nGY2Icon-folder-empty"></i>',
+      thumbnailImage:               '<i class="nGY2Icon-picture"></i>',
+      breadcrumbAlbum:              '<i class="nGY2Icon-folder-empty"></i>',
+      breadcrumbHome:               '<i class="nGY2Icon-home"></i>',
+      breadcrumbSeparator:          '<i class="nGY2Icon-left-open"></i>',
+      breadcrumbSeparatorRtl:       '<i class="nGY2Icon-right-open"></i>',
+      navigationFilterSelected:     '<i style="color:#fff;" class="nGY2Icon-ok"></i>',
+      navigationFilterUnselected:   '<i style="color:#ddd;opacity:0.3;" class="nGY2Icon-circle-empty"></i>',
+      navigationFilterSelectedAll:  '<i class="nGY2Icon-ccw"></i>',
+      navigationPaginationPrevious: '<i class="nGY2Icon-ngy2_chevron-left"></i>',
+      navigationPaginationNext:     '<i class="nGY2Icon-ngy2_chevron-right"></i>',
+      thumbnailSelected:            '<i style="color:#bff;" class="nGY2Icon-ok-circled"></i>',
+      thumbnailUnselected:          '<i style="color:#bff;" class="nGY2Icon-circle-empty"></i>',
+      thumbnailFeatured:            '<i style="color:#dd5;" class="nGY2Icon-star"></i>',
+      thumbnailCounter:             '<i class="nGY2Icon-picture"></i>',
+      thumbnailShare:               '<i class="nGY2Icon-ngy2_share2"></i>',
+      thumbnailDownload:            '<i class="nGY2Icon-ngy2_download2"></i>',
+      thumbnailInfo:                '<i class="nGY2Icon-ngy2_info2"></i>',
+      thumbnailShoppingcart:        '<i class="nGY2Icon-basket"></i>',
+      thumbnailDisplay:             '<i class="nGY2Icon-resize-full"></i>',
+      thumbnailCustomTool1:         'T1',
+      thumbnailCustomTool2:         'T2',
+      thumbnailCustomTool3:         'T3',
+      thumbnailCustomTool4:         'T4',
+      thumbnailCustomTool5:         'T5',
+      thumbnailCustomTool6:         'T6',
+      thumbnailCustomTool7:         'T7',
+      thumbnailCustomTool8:         'T8',
+      thumbnailCustomTool9:         'T9',
+      thumbnailCustomTool10:        'T10',
+      thumbnailAlbumUp:             '<i style="font-size: 3em;" class="nGY2Icon-ngy2_chevron_up2"></i>',
+      paginationNext:               '<i class="nGY2Icon-right-open"></i>',
       paginationPrevious:           '<i class="nGY2Icon-left-open"></i>',
-      galleryMoreButton:            '<i class="nGY2Icon-picture"></i> &nbsp; <i class="nGY2Icon-right-open"></i>',
-      buttonClose:                  '<i class="nGY2Icon-ngy2_close2"></i>',
-      viewerPrevious:               '<i class="nGY2Icon-ngy2_chevron-left"></i>',
-      viewerNext:                   '<i class="nGY2Icon-ngy2_chevron-right"></i>',
-      viewerImgPrevious:            '<i class="nGY2Icon-ngy2_chevron_left3"></i>',
-      viewerImgNext:                '<i class="nGY2Icon-ngy2_chevron_right3"></i>',
-      viewerDownload:               '<i class="nGY2Icon-ngy2_download2"></i>',
-      viewerToolbarMin:             '<i class="nGY2Icon-ellipsis-vert"></i>',
-      viewerToolbarStd:             '<i class="nGY2Icon-menu"></i>',
-      viewerPlay:                   '<i class="nGY2Icon-play"></i>',
-      viewerPause:                  '<i class="nGY2Icon-pause"></i>',
-      viewerFullscreenOn:           '<i class="nGY2Icon-resize-full"></i>',
-      viewerFullscreenOff:          '<i class="nGY2Icon-resize-small"></i>',
-      viewerZoomIn:                 '<i class="nGY2Icon-ngy2_zoom_in2"></i>',
-      viewerZoomOut:                '<i class="nGY2Icon-ngy2_zoom_out2"></i>',
-      viewerLinkOriginal:           '<i class="nGY2Icon-ngy2_external2"></i>',
-      viewerInfo:                   '<i class="nGY2Icon-ngy2_info2"></i>',
+      galleryMoreButton:            '<i class="nGY2Icon-picture"></i> &nbsp; <i class="nGY2Icon-right-open"></i>',
+      buttonClose:                  '<i class="nGY2Icon-ngy2_close2"></i>',
+      viewerPrevious:               '<i class="nGY2Icon-ngy2_chevron-left"></i>',
+      viewerNext:                   '<i class="nGY2Icon-ngy2_chevron-right"></i>',
+      viewerImgPrevious:            '<i class="nGY2Icon-ngy2_chevron_left3"></i>',
+      viewerImgNext:                '<i class="nGY2Icon-ngy2_chevron_right3"></i>',
+      viewerDownload:               '<i class="nGY2Icon-ngy2_download2"></i>',
+      viewerToolbarMin:             '<i class="nGY2Icon-ellipsis-vert"></i>',
+      viewerToolbarStd:             '<i class="nGY2Icon-menu"></i>',
+      viewerPlay:                   '<i class="nGY2Icon-play"></i>',
+      viewerPause:                  '<i class="nGY2Icon-pause"></i>',
+      viewerFullscreenOn:           '<i class="nGY2Icon-resize-full"></i>',
+      viewerFullscreenOff:          '<i class="nGY2Icon-resize-small"></i>',
+      viewerZoomIn:                 '<i class="nGY2Icon-ngy2_zoom_in2"></i>',
+      viewerZoomOut:                '<i class="nGY2Icon-ngy2_zoom_out2"></i>',
+      viewerLinkOriginal:           '<i class="nGY2Icon-ngy2_external2"></i>',
+      viewerInfo:                   '<i class="nGY2Icon-ngy2_info2"></i>',
       viewerShare:                  '<i class="nGY2Icon-ngy2_share2"></i>',
       viewerRotateLeft:             '<i class="nGY2Icon-ccw"></i>',
-      viewerRotateRight:            '<i class="nGY2Icon-cw"></i>',
-      viewerShoppingcart:           '<i class="nGY2Icon-basket"></i>',
-      user:                         '<i class="nGY2Icon-user"></i>',
-      location:                     '<i class="nGY2Icon-location"></i>',
-      picture:                     '<i class="nGY2Icon-picture"></i>',
-      config:                       '<i class="nGY2Icon-wrench"></i>',
-      shareFacebook:                '<i style="color:#3b5998;" class="nGY2Icon-facebook-squared"></i>',
-      shareTwitter:                 '<i style="color:#00aced;" class="nGY2Icon-twitter-squared"></i>',
-      // shareGooglePlus:              '<i style="color:#dd4b39;" class="nGY2Icon-gplus-squared"></i>',
-      shareTumblr:                  '<i style="color:#32506d;" class="nGY2Icon-tumblr-squared"></i>',
-      sharePinterest:               '<i style="color:#cb2027;" class="nGY2Icon-pinterest-squared"></i>',
-      shareVK:                      '<i style="color:#3b5998;" class="nGY2Icon-vkontakte"></i>',
-      shareMail:                    '<i style="color:#555;" class="nGY2Icon-mail-alt"></i>',
-      viewerCustomTool1:            'T1',
-      viewerCustomTool2:            'T2',
-      viewerCustomTool3:            'T3',
-      viewerCustomTool4:            'T4',
-      viewerCustomTool5:            'T5',
-      viewerCustomTool6:            'T6',
-      viewerCustomTool7:            'T7',
-      viewerCustomTool8:            'T8',
-      viewerCustomTool9:            'T9',
-      viewerCustomTool10:           'T10'
-    }
-  };
-
-  jQuery.fn.nanogallery2 = function (args, option, value) {
-
-	if( typeof jQuery(this).data('nanogallery2data') === 'undefined'){
-      if( args == 'destroy' ) {
-        // command to destroy but no instance yet --> exit
-        return;
-      }
-      
-      return this.each( function(){
-        (new jQuery.nanogallery2(this, args));
-      });
-    }
-    else {
-			// no options -->
-      // This function breaks the chain, but provides some API methods
-      var nG2 = $(this).data('nanogallery2data').nG2;
-
-			// Lightbox standalone
-      // (Another click on an already opened media)
-			if( args !== undefined && args.lightboxStandalone === true ) {
-        // items exist already (G.I is populated) -> just open the lightbox again
-        nG2.LightboxReOpen();
-				return;
-			}
-			
-      switch(args){
-        case 'displayItem':
-          nG2.DisplayItem(option);
-          break;
-          
-        case 'search':
-          return( nG2.Search(option));
-          break;
-          
-        case 'search2':
-          return nG2.Search2(option, value);
-          break;
-          
-        case 'search2Execute':
-          return nG2.Search2Execute();
-          break;
-          
-        case 'refresh':
-          nG2.Refresh();
-          break;
-
-        case 'resize':
-          nG2.Resize();
-          break;
-          
-        case 'instance':
-          return nG2;
-          break;
-          
-        case 'data':
-          nG2.data= {
-            items: nG2.I,
-            gallery: nG2.GOM,
-            lightbox: nG2.VOM,
-						shoppingcart: nG2.shoppingCart
-          };
-          return nG2.data;
-          break;
-          
-        case 'reload':
-          nG2.ReloadAlbum();
-          return $(this);
-          break;
-          
-        case 'itemsSelectedGet':
-          return nG2.ItemsSelectedGet();
-          break;
-          
-        case 'itemsSetSelectedValue':
-          nG2.ItemsSetSelectedValue(option, value);
-          break;
-          
-        case 'option':
-          if(typeof value === 'undefined'){
-            return nG2.Get(option);
-          }else{
-            nG2.Set(option,value);
-            if( option == 'demoViewportWidth' ) {
-              // force resize event -> for demo purposes
-              $(window).trigger('resize');
-            }
-          }
-          break;
-          
-        case 'destroy':
-          nG2.Destroy();
-          $(this).removeData('nanogallery2data');
-          break;
-          
-        case 'shoppingCartGet':
-          // returns the content of the shoppingcart
-          return nG2.shoppingCart;
-          break;
-          
-        case 'shoppingCartUpdate':
-          // parameters :
-          //  - option = item's ID
-          //  - value = new quantity
-			
-          if( typeof value === 'undefined' || typeof option === 'undefined' ){
-            return false;
-          }
-          
-          var item_ID = option;
-          var new_qty = value;
-
-          for( var i=0; i < nG2.shoppingCart.length; i++) {
-            if( nG2.shoppingCart[i].ID == item_ID ) {
-              
-              // updates counter
-              nG2.shoppingCart[i].qty = new_qty;
-              
-              var item = nG2.I[nG2.shoppingCart[i].idx];
-
-              // updates thumbnail
-              nG2.ThumbnailToolbarOneCartUpdate( item );
-              
-              if( new_qty == 0 ) {
-                // removes item from shoppingcart
-                nG2.shoppingCart.splice(i, 1);
-              }
-              
-              var fu = nG2.O.fnShoppingCartUpdated;
-              if( fu !== null ) {
-                typeof fu == 'function' ? fu(nG2.shoppingCart, item, 'api') : window[fu](nG2.shoppingCart, item, 'api');
-              }
-
-              break;
-            }
-          }
-         
-          return nG2.shoppingCart;
-          break;
-          
-        case 'shoppingCartRemove':
-          // parameters :
-          //  - option = item's ID
-          if( typeof option === 'undefined' ){
-            return false;
-          }
-          var ID = option;
-          for( var i=0; i < nG2.shoppingCart.length; i++) {
-            if( nG2.shoppingCart[i].ID == ID ) {
-              
-              var item = nG2.I[nG2.shoppingCart[i].idx];
-
-              // updates thumbnail
-              nG2.shoppingCart[i].qty = 0;
-              nG2.ThumbnailToolbarOneCartUpdate( item );
-              
-              // removes item from shoppingcart
-              nG2.shoppingCart.splice(i, 1);
-              
-              
-              var fu = nG2.O.fnShoppingCartUpdated;
-              if( fu !== null ) {
-                typeof fu == 'function' ? fu(nG2.shoppingCart, item, 'api') : window[fu](nG2.shoppingCart, item, 'api');
-              }
-
-              break;
-            }
-          }
-          
-          return nG2.shoppingCart;
-          break;
-         
-        case 'closeViewer':
-          nG2.CloseViewer();
-          break;
-        case 'minimizeToolbar':
-          nG2.MinimizeToolbar();
-          break;
-        case 'maximizeToolbar':
-          nG2.MaximizeToolbar();
-          break;
-        case 'paginationPreviousPage':
-          nG2.PaginationPreviousPage();
-          break;
-        case 'paginationNextPage':
-          nG2.paginationNextPage();
-          break;
-        case 'paginationGotoPage':
-          nG2.PaginationGotoPage( option );
-          break;
-        case 'paginationCountPages':
-          nG2.PaginationCountPages();
-          break;
-         
-      }
-      return $(this);
-
-    }
-  };
-  
-
-  // ###############################
-  // ##### nanogallery2 script #####
-  // ###############################
-
-  /** @function nanoGALLERY2 */
-  function nanoGALLERY2() {
-    "use strict";
-
-    /**
-    * Force reload the current album, if provided by Json
-    */
-    this.LightboxReOpen = function(){
-      LightboxStandaloneDisplay();
-    }
-
-    /**
-    * Force reload the current album, if provided by Json
-    */
-    this.ReloadAlbum = function(){
-      if( G.O.kind === '' ) {
-        throw 'Not supported for this content source:' + G.O.kind;
-      }
-
-      var albumIdx = G.GOM.albumIdx;
-      if( albumIdx == -1 ) {
-        throw ('Current album not found.');
-      }
-      
-      var albumID = G.I[albumIdx].GetID();
-
-      // unselect everything & remove link to album (=logical delete)
-      var l = G.I.length;
-      for( var i = 0; i < l ; i++ ) {
-        var item = G.I[i];
-        if( item.albumID == albumID ) {
-          item.selected = false;
-        }
-      }
-      
-      G.I[albumIdx].contentIsLoaded = false;
-      
-      DisplayAlbum('-1', albumID);
-    };
-    
-    /**
-     * Set one or several items selected/unselected
-     * @param {array} items
-     */
-    this.ItemsSetSelectedValue = function(items, value){
-      var l = items.length;
-      for( var j = 0; j < l ; j++) {
-        ThumbnailSelectionSet(items[j], value);
-      }
-    };
-    
-    /**
-     * Returns an array of selected items
-     * @returns {Array}
-     */
-    this.ItemsSelectedGet = function(){
-      var selectedItems = [];
-      var l = G.I.length;
-      for( var i = 0; i < l ; i++ ) {
-        if( G.I[i].selected == true ) {
-          selectedItems.push(G.I[i]);
-        }
-      }
-      return selectedItems;
-    };    
-    
-    /**
-     * Returns the value of an option
-     * @param {string} option
-     * @returns {nanoGALLERY.G.O}
-     */
-    this.Get = function(option){
-        return G.O[option];
-    };
-
-    /**
-     * Set a new value for a defined option
-     * @param {string} option
-     */
-    this.Set = function(option, value){
-        G.O[option] = value;
-        switch( option ) {
-          case 'thumbnailSelectable':
-            ThumbnailSelectionClear();
-            // refresh the displayed gallery
-            GalleryRender( G.GOM.albumIdx );
-            break;
-        }
-    };
-    
-    /**
-     * Refresh the current gallery
-     */
-    this.Refresh = function() {
-      // Refresh the displayed gallery
-      GalleryRender( G.GOM.albumIdx );
-    };
-    /**
-     * Resize the current gallery
-     */
-    this.Resize = function() {
-      // resize the displayed gallery
-      GalleryResize();
-    };
-
-    /**
-     * display one item (image or gallery)
-     *   itemID syntax:
-     *    - albumID --> display one album
-     *    - albumID/imageID --> display one image
-    */
-    this.DisplayItem = function( itemID ) {
-      var IDs=parseIDs( itemID );
-      if( IDs.imageID != '0' ) {
-        DisplayPhoto( IDs.imageID, IDs.albumID );
-      }
-      else {
-        DisplayAlbum( '-1', IDs.albumID );
-      }
-    };
-    
-    this.ThumbnailToolbarOneCartUpdate = function ( item ) {
-      ThumbnailBuildToolbarOneCartUpdate( item );
-    }
-    
-      
-      
-    var CountItemsToDisplay = function( gIdx ) {
-      if( G.I[gIdx] == undefined ) { return 0; }
-      var albumID = G.I[gIdx].GetID();
-      var l = G.I.length;
-      var cnt = 0;
-      for( var idx = 0; idx < l; idx++ ) {
-        var item = G.I[idx];
-        if( item.isToDisplay(albumID) ) {
-          cnt++;
-        }
-      }
-      return cnt;
-    } 
-    /**
-     * Search in the displayed gallery (in thumbnails title)
-     */
-    this.Search = function( search ) {
-      G.GOM.albumSearch = search.toUpperCase();
-      var gIdx = G.GOM.albumIdx;
-      GalleryRender( G.GOM.albumIdx );
-      return CountItemsToDisplay( gIdx );
-    };
-
-    /**
-     * Search2 in title and tags - set search values
-     */
-    this.Search2 = function( searchTitle, searchTags ) {
-      if( searchTitle != null && searchTitle != undefined ) {
-        G.GOM.albumSearch = searchTitle.toUpperCase().trim();
-      }
-      else {
-        G.GOM.albumSearch = '';
-      }
-      
-      if( searchTags != null && searchTags != undefined ) {
-        G.GOM.albumSearchTags = searchTags.toUpperCase().trim();
-      }
-      else {
-        G.GOM.albumSearchTags = '';
-      }
-      return CountItemsToDisplay( G.GOM.albumIdx );
-    };
-    
-    /**
-     * Search2 - execute the search on title and tags
-     */
-    this.Search2Execute = function() {
-      var gIdx = G.GOM.albumIdx;
-      GalleryRender( G.GOM.albumIdx );
-      return CountItemsToDisplay( gIdx );
-    };
-    
-    
-    /**
-     * Destroy the current gallery
-     */
-    this.Destroy = function(){
-
-      if( G.GOM.hammertime != null ) {
-        G.GOM.hammertime.destroy();
-        G.GOM.hammertime = null;
-      }
-
-      if( G.VOM.hammertime != null ) {
-        G.VOM.hammertime.destroy();
-        G.VOM.hammertime = null;
-      }
-      
-      // color scheme
-      $('#ngycs_' + G.baseEltID).remove()
-      
-      G.GOM.items = [];
-      NGY2Item.New( G, G.i18nTranslations.breadcrumbHome, '', '0', '-1', 'album' );
-      G.GOM.navigationBar.$newContent = null;
-      G.$E.base.empty();
-      G.$E.base.removeData();
-      if( G.O.locationHash ) {
-        jQuery(window).off('hashchange.nanogallery2.' + G.baseEltID);
-      }
-
-      jQuery(window).off('resize.nanogallery2.' + G.baseEltID);
-      jQuery(window).off('orientationChange.nanogallery2.' + G.baseEltID);
-      jQuery(window).off('scroll.nanogallery2.' + G.baseEltID);
-			if( G.$E.scrollableParent !== null ) {
-				G.$E.scrollableParent.off('scroll.nanogallery2.' + G.baseEltID);
-			}
-      G.GOM.firstDisplay = true;
-    };
-    
-    /**
-     * CloseViewer - close the media viewer
-     */
-    this.CloseViewer = function() {
-      LightboxClose(null);
-      return false;
-    };
-    
-    /**
-     * MinimizeToolbar - display the minimized lightbox main toolbar
-     */
-    this.MinimizeToolbar = function() {
-      ViewerToolbarForVisibilityMin();
-      return false;
-    };
-    
-    /**
-     * MaximizeToolbar - display the maximized/standard lightbox main toolbar
-     */
-    this.MaximizeToolbar = function() {
-      ViewerToolbarForVisibilityStd();
-      return false;
-    };
-    
-    /**
-     * PaginationPreviousPage - gallery paginate to previous page
-     */
-    this.PaginationPreviousPage = function() {
-      paginationPreviousPage();
-      return false;
-    };
-    
-    
-    /**
-     * PaginationNextPage - gallery paginate to next page
-     */
-    this.PaginationNextPage = function() {
-      paginationNextPage();
-      return false;
-    };
-    
-    
-    /**
-     * PaginationGotoPage - gallery paginate to specific page
-     */
-    this.PaginationGotoPage = function( page ) {
-      var aIdx = G.$E.conPagin.data('galleryIdx');
-      if( page > 1 ) { page--; }
-      G.GOM.pagination.currentPage = page;
-
-      // scroll to top of gallery if not displayed
-      G.GOM.ScrollToTop();
-
-      GalleryDisplayPart1( true );
-      GalleryDisplayPart2( true );
-      return false;
-    };
-
-    /**
-     * PaginationCountPages - gallery pagination - returns the number of pages
-     */
-    this.PaginationCountPages = function() {
-      if( G.GOM.items.length == 0 ) { return 0; }   // no thumbnail to display
-
-      var nbPages = Math.ceil((G.GOM.items[G.GOM.items.length - 1].row + 1) / G.galleryMaxRows.Get());
-      return nbPages;
-    };
-    
-    /**
-     * PaginationCountPages - gallery pagination - returns the number of pages
-     */
-
-    
-    
-    // throttle()
-    // author: underscore.js - http://underscorejs.org/docs/underscore.html
-    // Returns a function, that, when invoked, will only be triggered at most once during a given window of time.
-    // Normally, the throttled function will run as much as it can, without ever going more than once per wait duration;
-    // but if you�d like to disable the execution on the leading edge, pass {leading: false}.
-    // To disable execution on the trailing edge, ditto.
-    var throttle = function(func, wait, options) {
-      var context, args, result;
-      var timeout = null;
-      var previous = 0;
-      if (!options) options = {};
-      var later = function() {
-        previous = options.leading === false ? 0 : new Date().getTime();
-        timeout = null;
-        result = func.apply(context, args);
-        if (!timeout) context = args = null;
-      };
-      return function() {
-        var now = new Date().getTime();
-        if (!previous && options.leading === false) previous = now;
-        var remaining = wait - (now - previous);
-        context = this;
-        args = arguments;
-        if (remaining <= 0 || remaining > wait) {
-          if (timeout) {
-            clearTimeout(timeout);
-            timeout = null;
-          }
-          previous = now;
-          result = func.apply(context, args);
-          if (!timeout) context = args = null;
-        } else if (!timeout && options.trailing !== false) {
-          timeout = setTimeout(later, remaining);
-        }
-        return result;
-      };
-    };
-    
-    
-    // DEBOUNCE
-    // author: John Hann - http://unscriptable.com/2009/03/20/debouncing-javascript-methods/
-    // execAsap - false means executing at the end of the detection period
-    var debounce = function (func, threshold, execAsap) {
-      var timeout;
-      return function debounced () {
-          var obj = this, args = arguments;
-          function delayed () {
-              if (!execAsap)
-                  func.apply(obj, args);
-              timeout = null; 
-          };
-   
-          if (timeout)
-              clearTimeout(timeout);
-              // clearRequestTimeout(timeout);
-          else if (execAsap)
-              func.apply(obj, args);
-          timeout = setTimeout(delayed, threshold || 100); 
-          // timeout = requestTimeout(delayed, threshold || 100); 
-      };
-    }
-    
-
-    // Double requestAnimationFrame
-    window.ng_draf = function (cb) {
-      return requestAnimationFrame(function() {
-        window.requestAnimationFrame(cb)
-      })
-    }    
-    
-    // REQUESTTIMEOUT - replace SETTIMEOUT - https://gist.github.com/joelambert/1002116
-    /**
-     * Behaves the same as setTimeout except uses requestAnimationFrame() where possible for better performance
-     * @param {function} fn The callback function
-     * @param {int} delay The delay in milliseconds
-     */
-
-    window.requestTimeout = function(fn, delay) {
-      // if( !window.requestAnimationFrame      	&& 
-        // !window.webkitRequestAnimationFrame && 
-        // !(window.mozRequestAnimationFrame && window.mozCancelRequestAnimationFrame) && // Firefox 5 ships without cancel support
-        // !window.oRequestAnimationFrame      && 
-        // !window.msRequestAnimationFrame)
-          return window.setTimeout(fn, delay);
-          
-      var start = new Date().getTime(),
-        handle = new Object();
-        
-      function loop(){
-        var current = new Date().getTime(),
-          delta = current - start;
-  delta = delay;
-  
-        delta >= delay ? fn.call() : handle.value = requestAnimFrame(loop);
-      };
-      
-      handle.value = requestAnimFrame(loop);
-      return handle;
-    };
-
-    
-    // requestAnimationFrame() shim by Paul Irish
-    // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
-    window.requestAnimFrame = (function() {
-      return  window.requestAnimationFrame       || 
-          window.webkitRequestAnimationFrame || 
-          window.mozRequestAnimationFrame    || 
-          window.oRequestAnimationFrame      || 
-          window.msRequestAnimationFrame     || 
-          function(/* function */ callback, /* DOMElement */ element){
-            window.setTimeout(callback, 1000 / 60);
-          };
-    })();    
-    
-    
-    // CLEARREQUESTTIMEOUT - to replace CLEARTIMEOUT - https://gist.github.com/joelambert/1002116
-    /**
-     * Behaves the same as clearTimeout except uses cancelRequestAnimationFrame() where possible for better performance
-     * @param {int|object} fn The callback function
-     */
-    window.clearRequestTimeout = function(handle) {
-        window.cancelAnimationFrame ? window.cancelAnimationFrame(handle.value) :
-        window.webkitCancelAnimationFrame ? window.webkitCancelAnimationFrame(handle.value) :
-        window.webkitCancelRequestAnimationFrame ? window.webkitCancelRequestAnimationFrame(handle.value) : /* Support for legacy API */
-        window.mozCancelRequestAnimationFrame ? window.mozCancelRequestAnimationFrame(handle.value) :
-        window.oCancelRequestAnimationFrame	? window.oCancelRequestAnimationFrame(handle.value) :
-        window.msCancelRequestAnimationFrame ? window.msCancelRequestAnimationFrame(handle.value) :
-        clearTimeout(handle);
-    };    
-    
-    
-    
-    /*
-    ** Global data for this nanogallery2 instance
-    **/
-    var G=this;
-    G.I =                       [];           // gallery items
-    G.Id =                      [];           // gallery items
-    G.O =                       null;         // user options
-    G.baseEltID =               null;         // ID of the base element
-    G.$E = { 
-      base:                     null,         // base element
-      conTnParent:              null,         // $g_containerThumbnailsParent
-      conLoadingB:              null,         // loading bar - nanoGalleryLBarOff
-      conConsole:               null,         // console for error messages
-      conNavigationBar:         null,         // gallery navigation bar
-      conTnBottom:              null,         // container on the bottom of the gallery
-			scrollableParent:					null					// first scrollable parent container
-    };
-    G.shoppingCart =            [];
-    G.layout = {                              // Layout informations
-      internal :                true,
-      engine :                  '',
-      support :                 { rows: false },
-      prerequisite :            { imageSize: false },
-      SetEngine: function() {
-
-      if( G.layout.internal ) {
-          if( G.tn.settings.width[G.GOM.curNavLevel][G.GOM.curWidth] == 'auto' || G.tn.settings.width[G.GOM.curNavLevel][G.GOM.curWidth] == '' ) {
-            // do not use getH() / getW() here!
-            G.layout.engine = 'JUSTIFIED';
-            G.layout.support.rows = true;
-            G.layout.prerequisite.imageSize = true;
-            return;
-          }
-          if( G.tn.settings.height[G.GOM.curNavLevel][G.GOM.curWidth] == 'auto' || G.tn.settings.height[G.GOM.curNavLevel][G.GOM.curWidth] == '' ) {
-            // do not use getH() / getW() here!
-            G.layout.engine = 'CASCADING';
-            G.layout.support.rows = false;
-            G.layout.prerequisite.imageSize = true;
-            return;
-          }
-
-          if( G.tn.settings.getMosaic() != null ) {
-            G.layout.engine = 'MOSAIC';
-            G.layout.support.rows = true;
-            G.layout.prerequisite.imageSize = false;
-            return;
-          }
-
-          G.layout.engine = 'GRID';
-          G.layout.support.rows=true;
-          // if( G.tn.opt.Get('crop') === true ) {
-            // G.layout.prerequisite.imageSize = true;
-          // }
-          // else {
-            G.layout.prerequisite.imageSize = false;
-          // }
-        }
-      }
-    };
-    G.galleryResizeEventEnabled = false;
-    G.galleryMaxRows = { l1: 0, lN: 0,
-      Get: function() {
-        return G.galleryMaxRows[G.GOM.curNavLevel];
-      }
-    };
-    G.galleryMaxItems = { l1: 0, lN: 0,
-      Get: function() {
-        return G.galleryMaxItems[G.GOM.curNavLevel];
-      }
-    };
-    G.galleryFilterTags = { l1: 0, lN: 0,
-      Get: function() {
-        return G.galleryFilterTags[G.GOM.curNavLevel];
-      }
-    };
-    G.galleryFilterTagsMode = { l1: 0, lN: 0,
-      Get: function() {
-        return G.galleryFilterTagsMode[G.GOM.curNavLevel];
-      }
-    };
-    G.galleryDisplayMode = { l1: 'FULLCONTENT', lN: 'FULLCONTENT',
-      Get: function() {
-        return G.galleryDisplayMode[G.GOM.curNavLevel];
-      }
-    };
-    G.galleryLastRowFull = { l1: false, lN: false,
-      Get: function() {
-        return G.galleryLastRowFull[G.GOM.curNavLevel];
-      }
-    };
-    G.gallerySorting = { l1: '', lN: '',
-      Get: function() {
-        return G.gallerySorting[G.GOM.curNavLevel];
-      }
-    };
-    G.galleryDisplayTransition = { l1: 'none', lN: 'none',
-      Get: function() {
-        return G.galleryDisplayTransition[G.GOM.curNavLevel];
-      }
-    };
-    G.galleryDisplayTransitionDuration = { l1: 500, lN: 500,
-      Get: function() {
-        return G.galleryDisplayTransitionDuration[G.GOM.curNavLevel];
-      }
-    };
-    G.$currentTouchedThumbnail = null;    
-    
-    
-    
-    
-    // ##### GENERAL THUMBNAILS PROPERTIES -->
-    G.tn = {
-      // levell specific options
-      opt:  {
-        l1: { crop: true, stacks: 0, stacksTranslateX: 0, stacksTranslateY: 0, stacksTranslateZ: 0, stacksRotateX: 0, stacksRotateY: 0, stacksRotateZ: 0, stacksScale: 0, borderHorizontal: 0, borderVertical: 0, baseGridHeight: 0, displayTransition: 'FADEIN', displayTransitionStartVal: 0, displayTransitionEasing: 'easeOutQuart', displayTransitionDuration: 240, displayInterval: 15 },
-        lN: { crop: true, stacks: 0, stacksTranslateX: 0, stacksTranslateY: 0, stacksTranslateZ: 0, stacksRotateX: 0, stacksRotateY: 0, stacksRotateZ: 0, stacksScale: 0, borderHorizontal: 0, borderVertical: 0, baseGridHeight: 0, displayTransition: 'FADEIN', displayTransitionStartVal: 0, displayTransitionEasing: 'easeOutQuart', displayTransitionDuration: 240, displayInterval: 15 },
-        Get: function(opt) {
-          return G.tn.opt[G.GOM.curNavLevel][opt];
-        }
-      },
-      scale:                          1,         // image scale depending of the hover effect
-      labelHeight: {                  // in case label on bottom, otherwise always=0
-        l1: 0, lN: 0,
-        get: function() {
-          return G.tn.labelHeight[G.GOM.curNavLevel];
-        }
-      },
-      defaultSize: {                  // default thumbnail size
-                                      // label height is not included
-        width: {  l1 : { xs:0, sm:0, me:0, la:0, xl:0 }, lN : { xs:0, sm:0, me:0, la:0, xl:0 } },
-        height: { l1 : { xs:0, sm:0, me:0, la:0, xl:0 }, lN : { xs:0, sm:0, me:0, la:0, xl:0 } },
-        getWidth: function() {
-          return G.tn.defaultSize.width[G.GOM.curNavLevel][G.GOM.curWidth];
-        },
-        getOuterWidth: function() {     // width including border
-      G.tn.borderWidth = G.tn.opt.Get('borderHorizontal');
-      G.tn.borderHeight = G.tn.opt.Get('borderVertical');
-          var w = G.tn.defaultSize.width[G.GOM.curNavLevel][G.GOM.curWidth] + G.tn.opt.Get('borderHorizontal') * 2;
-          if( G.O.thumbnailLabel.get('position') == 'right' || G.O.thumbnailLabel.get('position') == 'left' ) {
-            w += G.tn.defaultSize.width[G.GOM.curNavLevel][G.GOM.curWidth];
-          }
-          return w;
-        },
-        getHeight: function() {
-          return G.tn.defaultSize.height[G.GOM.curNavLevel][G.GOM.curWidth];
-        },
-        getOuterHeight: function() {     // height, border included
-          return G.tn.defaultSize.height[G.GOM.curNavLevel][G.GOM.curWidth]+G.tn.opt.Get('borderVertical')*2;
-        }
-      },
-      settings: {                     // user defined width/height of the image to display depending on the screen size
-        width: {  l1 : { xs:0, sm:0, me:0, la:0, xl:0, xsc:'u', smc:'u', mec:'u', lac:'u', xlc:'u' },
-                  lN : { xs:0, sm:0, me:0, la:0, xl:0, xsc:'u', smc:'u', mec:'u', lac:'u', xlc:'u' } },
-        height: { l1 : { xs:0, sm:0, me:0, la:0, xl:0, xsc:'u', smc:'u', mec:'u', lac:'u', xlc:'u' }, 
-                  lN : { xs:0, sm:0, me:0, la:0, xl:0, xsc:'u', smc:'u', mec:'u', lac:'u', xlc:'u' } },
-        getH: function(l, w) {
-          var cl = (l == undefined ? G.GOM.curNavLevel : l);
-          var cw = (w == undefined ? G.GOM.curWidth : w);
-          if( G.layout.engine == 'MOSAIC' ) {
-          return this.height[cl][cw] * this.mosaic[cl+'Factor']['h'][cw];
-          }
-          else {
-            return this.height[cl][cw];
-          }
-        },
-        getW: function(l, w) {
-          var cl = (l == undefined ? G.GOM.curNavLevel : l);
-          var cw = (w == undefined ? G.GOM.curWidth : w);
-          if( G.layout.engine == 'MOSAIC' ) {
-            return this.width[cl][cw] * this.mosaic[cl+'Factor']['w'][cw];
-          }
-          else {
-            return this.width[cl][cw];
-            // return G.tn.settings.width[G.GOM.curNavLevel][G.GOM.curWidth];
-          }
-        },
-        mosaic: { l1 : { xs: null, sm: null, me: null, la: null, xl: null },
-                  lN : { xs: null, sm: null, me: null, la: null, xl: null },
-                  l1Factor : { h :{ xs: 1, sm: 1, me: 1, la: 1, xl: 1 }, w :{ xs: 1, sm: 1, me: 1, la: 1, xl: 1 }},
-                  lNFactor : { h :{ xs: 1, sm: 1, me: 1, la: 1, xl: 1 }, w :{ xs: 1, sm: 1, me: 1, la: 1, xl: 1 }}
-                  },
-				getMosaic: function() {
-          return this.mosaic[G.GOM.curNavLevel][G.GOM.curWidth];
-        },
-        mosaicCalcFactor: function(l, w) {
-            // retrieve max size multiplicator
-            var maxW = 1;
-            var maxH = 1;
-            for( var n = 0; n < G.tn.settings.mosaic[l][w].length; n++ ) {
-              maxW = Math.max(maxW, this.mosaic[l][w][n]['w']);
-              maxH = Math.max(maxH, this.mosaic[l][w][n]['h']);
-            }
-            this.mosaic[l + 'Factor']['h'][w] = maxH;
-            this.mosaic[l + 'Factor']['w'][w] = maxW;
-        },
-        gutterHeight: { l1 : { xs:0, sm:0, me:0, la:0, xl:0 }, lN : { xs:0, sm:0, me:0, la:0, xl:0 } },
-        gutterWidth:  { l1 : { xs:0, sm:0, me:0, la:0, xl:0 }, lN : { xs:0, sm:0, me:0, la:0, xl:0 } },
-        GetResponsive: function( setting ) {
-          return this[setting][G.GOM.curNavLevel][G.GOM.curWidth];
-        }
-      },
-      // thumbnail hover effects
-      hoverEffects : {
-        std :   [],
-        level1: [],
-        get: function() {
-          if( G.GOM.curNavLevel == 'l1' && this.level1.length !== 0 ) {
-            return this.level1;
-          }
-          else {
-            return this.std;
-          }
-        }
-      },
-      // thumbnail init
-      buildInit : {
-        std :   [],
-        level1: [],
-        get: function() {
-          if( G.GOM.curNavLevel == 'l1' && this.level1.length !== 0 ) {
-            return this.level1;
-          }
-          else {
-            return this.std;
-          }
-        }
-      },
-      // thumbnail toolbars
-      toolbar: {
-        album :   { topLeft : '', topRight: '', bottomLeft: '', bottomRight: '' },
-        image :   { topLeft : '', topRight: '', bottomLeft: '', bottomRight: '' },
-        albumUp : { topLeft : '', topRight: '', bottomLeft: '', bottomRight: '' },
-        get: function( item ) {
-          return this[item.kind];
-        },
-      },
-      style: {
-        // inline CSS
-        l1 : { label: '', title: '', desc: '' },
-        lN : { label: '', title: '', desc: '' },
-        getTitle : function() {
-          return ('style="' + this[G.GOM.curNavLevel].title + '"');
-        },
-        getDesc : function() {
-          return ('style="' + this[G.GOM.curNavLevel].desc + '"');
-        },
-        getLabel: function() {
-          var s='style="'+ this[G.GOM.curNavLevel].label;
-          s+= (G.O.RTL ? '"direction:RTL;"' :'');
-          s+='"';
-          return s;
-        }
-      }
-    };
-    G.scrollTimeOut =             0;
-    G.i18nTranslations =          {'paginationPrevious':'Previous', 'paginationNext':'Next', 'breadcrumbHome':'List of Albums', 'thumbnailImageTitle':'', 'thumbnailAlbumTitle':'', 'thumbnailImageDescription':'', 'thumbnailAlbumDescription':'' };
-    G.emptyGif =                  'data:image/gif;base64,R0lGODlhEAAQAIAAAP///////yH5BAEKAAEALAAAAAAQABAAAAIOjI+py+0Po5y02ouzPgUAOw==';
-    G.CSStransformName =          FirstSupportedPropertyName(["transform", "msTransform", "MozTransform", "WebkitTransform", "OTransform"]);
-    // G.CSSfilterName =          FirstSupportedPropertyName(["filter", "WebkitFilter"]);
-    G.CSStransformStyle =         FirstSupportedPropertyName(["transformStyle", "msTransformStyle", "MozTransformStyle", "WebkitTransformStyle", "OTransformStyle"]);
-    G.CSSperspective =            FirstSupportedPropertyName(["perspective", "msPerspective", "MozPerspective", "WebkitPerspective", "OPerspective"]);
-    G.CSSbackfaceVisibilityName = FirstSupportedPropertyName(["backfaceVisibility", "msBackfaceVisibility", "MozBackfaceVisibility", "WebkitBackfaceVisibility", "OBackfaceVisibility"]);
-    G.CSStransitionName =         FirstSupportedPropertyName(["transition", "msTransition", "MozTransition", "WebkitTransition", "OTransition"]);
-    G.CSSanimationName =          FirstSupportedPropertyName(["animation", "msAnimation", "MozAnimation", "WebkitAnimation", "OAnimation"]);
-    G.GalleryResizeThrottled =    throttle(GalleryResize, 30, {leading: false});
-    
-    G.blockList =                 null;     // album white list
-    G.allowList =                 null;     // album black list
-    G.albumList =                 [];       // album list
-    G.locationHashLastUsed =      '';
-    G.custGlobals =               {};
-    G.touchAutoOpenDelayTimerID = 0;
-    G.i18nLang =                  '';
-    G.timeLastTouchStart =        0;
-    G.custGlobals =               {};
-    G.markupOrApiProcessed =      false;
-    
-    //------------------------
-    //--- Gallery Object Model
-    G.GOM = {
-      albumIdx :                  -1, // index (in G.I) of the currently displayed album
-      clipArea :                  { top: 0, height: 0 }, // area of the GOM to display on screen
-      displayArea :               { width: 0 , height: 0 }, // size of the GOM area (=used area, not available area)
-      displayAreaLast :           { width: 0 , height: 0 }, // previous size of the GOM area
-      displayedMoreSteps :        0,  // current number of displayed steps (moreButton mode)
-      items:                      [], // current items of the GOMS
-      $imgPreloader:              [],
-      thumbnails2Display:         [],
-      itemsDisplayed :            0, // number of currently displayed thumbnails
-      firstDisplay :              true,
-      firstDisplayTime :          0,      // in conjunction with galleryRenderDelay
-      navigationBar : {           // content of the navigation bar (for breadcrumb, filter tags and next/previous pagination)
-        displayed:                false,
-        $newContent:              ''
-      },
-      cache : {                   // cached data
-        viewport:                 null,
-        containerOffset:          null,
-        areaWidth:                100         // available area width
-      },
-      nbSelected :                0,        // number of selected items
-      pagination :                { currentPage: 0 }, // pagination data
-      panThreshold:               60,       // threshold value (in pixels) to block horizontal pan/swipe
-      panYOnly:                   false,    // threshold value reach -> definitively block horizontal pan until end of pan
-      lastFullRow :               -1,       // number of the last row without holes
-      lastDisplayedIdx:           -1,       // used to display the counter of not displayed items
-      displayInterval :           { from: 0, len: 0 },
-      userEvents:                 null,
-      hammertime:                 null,
-      curNavLevel:                'l1',   // current navigation level (l1 or LN)
-      curWidth:                   'me',
-      albumSearch:                '',     // current search string -> title (used to filter the thumbnails on screen)
-      albumSearchTags:            '',     // current search string -> tags
-      lastZIndex:                 0,      // used to put a thumbnail on top of all others (for exemple for scale hover effect)
-      lastRandomValue:            0,
-      slider : {                          // slider on last thumbnail
-        hostIdx:                  -1,       // idx of the thumbnail hosting the slider
-        hostItem:                 null,     // item hosting the slider
-        currentIdx:               0,        // idx of the current displayed item
-        nextIdx:                  0,        // idx of the next item to display in the slider
-        timerID:                  0,
-        tween:                    null      // tranistion tween instance
-      },
-      NGY2Item: function( idx ) {   // returns a NGY2Item or null if it does not exist
-        if( G.GOM.items[idx] == undefined || G.GOM.items[idx] == null ) { return null; }
-        var i = G.GOM.items[idx].thumbnailIdx;
-        return G.I[i];
-      },
-      // One GOM item (thumbnail)
-      // function GTn(index, width, height) {
-      GTn: function(index, width, height) {
-        this.thumbnailIdx = index;
-        this.width =                0;      // thumbnail width
-        this.height =               0;      // thumbnail height
-        this.top =                  0;      // position: top
-        this.left =                 0;      // position: left
-        this.row =                  0;      // position: row number
-        this.imageWidth =           width;  // image width
-        this.imageHeight =          height; // image height
-        this.resizedContentWidth =  0;
-        this.resizedContentHeight = 0;
-        this.displayed =            false;
-        this.neverDisplayed =       true;
-        this.inDisplayArea =        false;
-      },
-      // Position the top of the gallery to make it visible, if not displayed
-      ScrollToTop: function() {
-    
-        if( G.$E.scrollableParent === null && !topInViewportVert(G.$E.base, 20) ) {
-          // $('html, body').animate({scrollTop: G.$E.base.offset().top}, 200);
-          G.$E.base.get(0).scrollIntoView();
-        }
-        
-        if( G.$E.scrollableParent !== null ) {
-          // gallery in a scrollable container: check if we have to scroll up so that the top of the gallery is visible
-          // vertical position of the scrollbar
-          var scrollTop = G.$E.scrollableParent.scrollTop();
-          // top of the gallery relative to the top of the scrollable container
-          var dist = Math.abs(G.$E.scrollableParent.offset().top - G.$E.base.offset().top - scrollTop);
-          if( scrollTop > dist ) {
-            window.ng_draf( function() {
-              // we need a little delay before setting the new scrollbar (but why?....)
-               G.$E.scrollableParent.scrollTop(dist);
-            });
-          }
-        }
-      }
-    };
-    
-    
-    //------------------------
-    //--- Viewer Object Model
-    
-    G.VOM = {
-      viewerDisplayed:            false,  // is the viewer currently displayed
-      viewerIsFullscreen:         false,  // viewer in fullscreen mode
-      infoDisplayed:              false,  // is the info box displayed
-      toolbarsDisplayed:          true,   // the toolbars are displayed
-      toolsHide:                  null,
-      zoom : {
-        posX:                     0,      // position to center zoom in/out
-        posY:                     0,
-        userFactor:               1,      // user zoom factor (applied to the baseZoom factor)
-        isZooming:                false
-      },
-      padding:                    { H: 0, V: 0 }, // padding for the image
-      window:                     { lastWidth: 0, lastHeight: 0 },
-      $viewer:                    null,
-      $toolbar:                   null,   // viewerToolbar
-      $toolbarTL:                 null,   // viewer toolbar on top left
-      $toolbarTR:                 null,   // viewer toolbar on top right
-      
-      toolbarMode:                'std',  // current toolbar mode (standard, minimized)
-      playSlideshow :             false,  // slide show mode status
-      playSlideshowTimerID:       0,      // slideshow mode time
-      slideshowDelay:             3000,   // slideshow mode - delay before next image
-      albumID:                    -1,
-      viewerMediaIsChanged:       false,  // media display is currently modified
-      items:                      [],     // current list of images to be managed by the viewer
-
-      panMode:                    'off',  // if panning, which element -> media, gallery, or zoom - if not -> off
-
-      $baseCont:                  null,   // lightbox container
-      $content:                   null,   // pointer to the 3 media in the viewer
-      content: {
-        previous : {
-            vIdx: -1,
-            $media: null,
-            NGY2Item: function() {
-              return G.I[ G.VOM.items[G.VOM.content.previous.vIdx].ngy2ItemIdx ];
-            }
-        },
-        current : {
-            vIdx: -1,
-            $media: null,
-            NGY2Item: function() {
-              return G.I[ G.VOM.items[G.VOM.content.current.vIdx].ngy2ItemIdx ];
-            }
-        },
-        next : {
-            vIdx: -1,
-            $media: null,
-            NGY2Item: function() {
-              return G.I[ G.VOM.items[G.VOM.content.next.vIdx].ngy2ItemIdx ];
-            }
-        }
-      },
-      IdxNext: function() {
-        var n = 0;
-        // if( G.VOM.currItemIdx <= (G.VOM.items.length-1) ) {
-        if( G.VOM.content.current.vIdx < (G.VOM.items.length-1) ) {
-          n = G.VOM.content.current.vIdx + 1;
-        }
-        return n;
-      },
-      IdxPrevious: function() {
-        var n = G.VOM.content.current.vIdx - 1;
-        if( G.VOM.content.current.vIdx == 0 ) {
-          n = G.VOM.items.length - 1;
-        }
-        return n;
-      },
-			
-			gallery: {
-        $elt: null,             // Base container
-        $tmbCont: null,         // Thumbnail container
-				gwidth: 0,              // thumbnail container width (all thumbnails)
-        vwidth: 0,              // visible width of the gallery (just for the visible thumbnails)
-				oneTmbWidth: 0,
-				firstDisplay: true,
-				posX: 0,
-				SetThumbnailActive() {
-					if( G.O.viewerGallery == 'none' ) { return; }
-					this.$tmbCont.children().removeClass('activeVThumbnail');
-					this.$tmbCont.children().eq( G.VOM.content.current.vIdx ).addClass('activeVThumbnail');
-					this.firstDisplay = false;
-				},
-				Resize: function() {
-					if( G.O.viewerGallery == 'none' ) { return; }
-
-					if( !this.firstDisplay ) {
-						var viewerW = G.VOM.$viewer.width();
-						
-						// Center base element 
-						var maxTmb = Math.trunc(viewerW / this.oneTmbWidth);      // max thumbnail that can be displayed
-            this.vwidth = maxTmb * this.oneTmbWidth;
-						this.$elt.css({ width: this.vwidth, left: (viewerW - this.vwidth)/2 });
-            
-						// Set the position the thumbnails container (if there's no enough space for all thumbnails)
-						if( G.VOM.items.length >= maxTmb ) {
-							var tmbPos = this.oneTmbWidth * G.VOM.content.current.vIdx;    // left position of the selected thumbnail
-              
-              if( (tmbPos + this.posX) < this.vwidth ) {
-                  if( tmbPos + this.posX < 0 ) {
-                    this.posX = -tmbPos;
-                  }
-              }
-              else {
-                if( tmbPos + this.posX >= this.vwidth ) {
-                  this.posX = this.vwidth - (tmbPos + this.oneTmbWidth)
-                }
-              }
-						}
-						
-            this.PanGallery(0);
-					}
-          else {
-            // first display of the gallery -> opacity transition
-            new NGTweenable().tween({
-              from:         { opacity: 0 },
-              to:           { opacity: 1 },
-              easing:       'easeInOutSine',
-              duration:     1000,
-              step:         function (state) {
-                // G.VOM.gallery.$elt.css( state );
-              },
-              finish:       function (state) {
-                // G.VOM.gallery.$elt.css({ opacity: 1});
-              }
-            });
-
-          }
-				},
-        PanGallery: function( panX ){
-          
-          // all thumbnails are visible -> center the base element
-          if( this.gwidth < G.VOM.$viewer.width() ) {       // this.oneTmbWidth
-            this.posX = (G.VOM.$viewer.width() - this.gwidth) / 2;   
-            panX = 0;   // block pan
-          }
-          
-          // if( this.posX > (this.vwidth - this.oneTmbWidth) ) {
-          if( this.posX > (this.vwidth - this.oneTmbWidth) ) {
-            // gallery is outside of the screen, right side
-            this.posX = this.vwidth - this.oneTmbWidth;
-          }
-          if( (this.posX+this.gwidth) < this.oneTmbWidth ) {
-            // gallery is outside of the screen, left side
-            this.posX = -this.gwidth + this.oneTmbWidth;
-          }
-          
-          this.$tmbCont.css( G.CSStransformName , 'translateX(' + (this.posX + panX) + 'px)');
-        },
-        PanGalleryEnd: function( velocity ) {      // velocity = pixels/millisecond
-
-          var d = velocity * 100;         // distance 
-          new NGTweenable().tween({
-            from:         { pan: G.VOM.gallery.posX },
-            to:           { pan: G.VOM.gallery.posX + d },
-            easing:       'easeOutQuad',
-            duration:     500,
-            step:         function (state) {
-              G.VOM.gallery.posX = state.pan;
-              G.VOM.gallery.PanGallery( 0 );
-            }
-          });
-        }
-				
-			},
-      userEvents:         null,   // user events management
-      hammertime:         null,   // hammer.js manager
-      swipePosX:          0,      // current horizontal swip position
-      panPosX:            0,      // position for manual pan
-      panPosY:            0,
-      panThreshold:       60,     // threshold value (in pixels) to block vertical pan
-      panXOnly:           false,  // threshold value reach -> definitively block vertical pan until end of pan
-      singletapTime:			0,
-			viewerTheme:        '',
-      timeImgChanged:     0,
-      ImageLoader: {
-        // fires a callback when image size is know (during download)
-        // inspired by ROB - http://stackoverflow.com/users/226507/rob
-        maxChecks:        1000,
-        list:             [],
-        intervalHandle :  null,
-
-        loadImage : function (callback, ngitem) {
-          if( ngitem.mediaKind != 'img' ) { return; }     // ignore - only for images
-          var img = new Image ();
-          img.src = ngitem.responsiveURL();
-          if (img.width && img.height) {
-            callback (img.width, img.height, ngitem, 0);
-            }
-          else {
-            var obj = {image: img, url: ngitem.responsiveURL(), ngitem: ngitem, callback: callback, checks: 1};
-            var i;
-            for (i=0; i < this.list.length; i++)    {
-              if (this.list[i] == null)
-                break;
-              }
-            this.list[i] = obj;
-            if (!this.intervalHandle)
-              this.intervalHandle = setInterval(this.interval, 50);
-            }
-          },
-
-        // called by setInterval  
-        interval : function () {
-          var count = 0;
-          var list = G.VOM.ImageLoader.list, item;
-          for (var i=0; i<list.length; i++) {
-            item = list[i];
-            if (item != null) {
-              if (item.image.width && item.image.height) {
-                G.VOM.ImageLoader.list[i] = null;
-                item.callback (item.image.width, item.image.height, item.ngitem, item.checks);
-                }
-              else if (item.checks > G.VOM.ImageLoader.maxChecks) {
-                G.VOM.ImageLoader.list[i] = null;
-                item.callback (0, 0, item.ngitem, item.checks);
-                }
-              else {
-                count++;
-                item.checks++;
-                }
-              }
-            }
-          if (count == 0) {
-            G.VOM.ImageLoader.list = [];
-            clearInterval (G.VOM.ImageLoader.intervalHandle);
-            delete G.VOM.ImageLoader.intervalHandle;
-            }
-          }
-        }
-    }
-    // One VOM item (image)
-    function VImg( index ) {
-      this.$e = null;
-      this.ngy2ItemIdx = index;
-      this.mediaNumber = G.VOM.items.length + 1;     
-      this.posX = 0;    // to center the element
-      this.posY = 0;
-    }
-    
-    
-    //------------------------
-    //--- popup
-    G.popup = {
-      isDisplayed:      false,
-      $elt:             null,
-      close: function() {
-        if( this.$elt != null ) {
-          var tweenable = new NGTweenable();
-          tweenable.tween({
-            from:       { opacity:1  },
-            to:         { opacity:0 },
-            attachment: { t: this },
-            easing:     'easeInOutSine',
-            duration:   100,
-            step: function (state, att) {
-              if( att.t.$elt != null ) {
-                att.t.$elt.css('opacity',state.opacity);
-              }
-            },
-            finish: function (state, att) {
-              if( att.t.$elt != null ) {
-                att.t.$elt.remove();
-                att.t.$elt=null;
-              }
-              att.t.isDisplayed=false;
-            }
-          });
-        }
-      }
-    }
-    
-    
-    // Color schemes - Gallery
-    // Gradient generator: https://www.grabient.com/
-    G.galleryTheme_dark = {
-      navigationBar :         { background: 'none', borderTop: '', borderBottom: '', borderRight: '', borderLeft: '' },
-      navigationBreadcrumb :  { background: '#111', color: '#fff', colorHover: '#ccc', borderRadius: '4px' },
-      navigationFilter :      { color: '#ddd', background: '#111', colorSelected: '#fff', backgroundSelected: '#111', borderRadius: '4px' },
-      navigationPagination :  { background: '#111', color: '#fff', colorHover: '#ccc', borderRadius: '4px' },
-      thumbnail :             { background: '#444', backgroundImage: 'linear-gradient(315deg, #111 0%, #445 90%)', borderColor: '#000', borderRadius: '0px', labelOpacity : 1, labelBackground: 'rgba(34, 34, 34, 0)', titleColor: '#fff', titleBgColor: 'transparent', titleShadow: '', descriptionColor: '#ccc', descriptionBgColor: 'transparent', descriptionShadow: '', stackBackground: '#aaa' },
-      thumbnailIcon :         { padding: '5px', color: '#fff', shadow:'' },
-      pagination :            { background: '#181818', backgroundSelected: '#666', color: '#fff', borderRadius: '2px', shapeBorder: '3px solid #666', shapeColor: '#444', shapeSelectedColor: '#aaa'}
-    };
-
-    G.galleryTheme_light = {
-      navigationBar :         { background: 'none', borderTop: '', borderBottom: '', borderRight: '', borderLeft: '' },
-      navigationBreadcrumb :  { background: '#eee', color: '#000', colorHover: '#333', borderRadius: '4px' },
-      navigationFilter :      { background: '#eee', color: '#222', colorSelected: '#000', backgroundSelected: '#eee', borderRadius: '4px' },
-      navigationPagination :  { background: '#eee', color: '#000', colorHover: '#333', borderRadius: '4px' },
-      thumbnail :             { background: '#444', backgroundImage: 'linear-gradient(315deg, #111 0%, #445 90%)', borderColor: '#000', labelOpacity : 1, labelBackground: 'rgba(34, 34, 34, 0)', titleColor: '#fff', titleBgColor: 'transparent', titleShadow: '', descriptionColor: '#ccc', descriptionBgColor: 'transparent', descriptionShadow: '', stackBackground: '#888' },
-      thumbnailIcon :         { padding: '5px', color: '#fff' },
-      pagination :            { background: '#eee', backgroundSelected: '#aaa', color: '#000', borderRadius: '2px', shapeBorder: '3px solid #666', shapeColor: '#444', shapeSelectedColor: '#aaa'}
-    };
-
-    // Color schemes - lightbox
-    G.viewerTheme_dark = {
-      background:             '#000',
-      barBackground:          'rgba(4, 4, 4, 0.2)',
-      barBorder:              '0px solid #111',
-      barColor:               '#fff',
-      barDescriptionColor:    '#ccc'
-    };
-    G.viewerTheme_light = {
-      background:             '#f8f8f8',
-      barBackground:          'rgba(4, 4, 4, 0.7)',
-      barBorder:              '0px solid #111',
-      barColor:               '#fff',
-      barDescriptionColor:    '#ccc'
-    };
-
-      
-    
-    // shortcut with G context to NGY2TOOLS
-    // var NanoAlert =           NGY2Tools.NanoAlert.bind(G);
-    // var NanoConsoleLog =      NGY2Tools.NanoConsoleLog.bind(G);
-    var NanoAlert =           NGY2Tools.NanoAlert;
-    var NanoConsoleLog =      NGY2Tools.NanoConsoleLog;
-
-    
-    /** @function initiateGallery2 */
-    this.initiateGallery2 = function( element, params ) {
-
-      // GLOBAL OPTIONS
-      G.O =               params;
-      // Base element
-      G.$E.base =         jQuery(element);
-      G.baseEltID =       G.$E.base.attr('id');
-      if( G.baseEltID == undefined ) {
-        // set a default ID to the root container
-        var base_id = 'my_nanogallery';
-        var c = '';
-        var f = true;
-        while( f ) {
-          if (document.getElementById(base_id + c)) {
-            // ID already exists
-            if( c == '' ) {
-              c = 1;
-            }
-            else {
-              c++;
-            }
-          }
-          else {
-            f = false;
-            G.baseEltID = 'my_nanogallery' + c;
-          }
-        }
-        G.$E.base.attr('id', G.baseEltID)
-      }
-      G.O.$markup =       [];
-      
-      DefineVariables();
-      SetPolyFills();
-      BuildSkeleton();
-      G.GOM.firstDisplayTime = Date.now();
-      
-      SetGlobalEvents();
-
-      // check if only one specific album will be used
-      if( !G.O.lightboxStandalone ) {
-				var albumToDisplay = G.O.album;
-				if( albumToDisplay == '' && G.O.photoset != '' ) {
-					albumToDisplay = G.O.photoset;
-					G.O.album = G.O.photoset;
-				}
-				if( albumToDisplay != '' ) {
-					G.O.displayBreadcrumb = false;    // no breadcrumb since only 1 album
-					if( albumToDisplay.toUpperCase() != 'NONE' ) {
-						// open a public album
-						if( G.O.kind == "nano_photos_provider2") {
-							if( albumToDisplay == decodeURIComponent(albumToDisplay)) {
-								// album ID must be encoded
-								albumToDisplay = encodeURIComponent(albumToDisplay);
-								G.O.album = albumToDisplay;
-							}
-						}
-						NGY2Item.New( G, '', '', albumToDisplay, '-1', 'album' );
-						if( !ProcessLocationHash() ) {
-							DisplayAlbum('-1', albumToDisplay);
-						}
-						return;
-					}
-				}
-      }
-     
-      // use full content
-      // add base album
-      NGY2Item.New( G, G.i18nTranslations.breadcrumbHome, '', '0', '-1', 'album' );
-
-      processStartOptions();
-
-    }
-
-
-    /** @function processStartOptions */
-    function processStartOptions() {
-      // open image or album
-      // 1. load hidden albums
-      // 2. check if location hash set (deep linking)
-      // 3. check openOnStart parameter
-      // 4. open root album (ID=-1)
-
-      // hidden/private albums are loaded on plugin start (Picasa) --> no more available in Google Photos
-      // if( G.albumListHidden.length > 0 ) {
-      //  jQuery.nanogallery2['data_'+G.O.kind](G, 'GetHiddenAlbums', G.albumListHidden, processStartOptionsPart2);
-      //  return;
-      //}
-      
-      if( !ProcessLocationHash() ) {
-        processStartOptionsPart2();
-      }
-    }
-
-    /** @function processStartOptionsPart2 */
-    function processStartOptionsPart2() {
-  
-      // Check location hash + start parameters -> determine what to do on start
-      if( G.O.lightboxStandalone ) {
-        LightboxStandaloneFindContent();
-			}
-			else {
-				// openOnStart parameter
-				if( G.O.openOnStart != '' ) {
-					var IDs = parseIDs(G.O.openOnStart);
-					if( IDs.imageID != '0' ) {
-						DisplayPhoto(IDs.imageID, IDs.albumID);
-					}
-					else {
-						DisplayAlbum('-1', IDs.albumID);
-					}
-				}
-				else {
-					// open root album (ID = -1)
-					DisplayAlbum('-1', 0);
-				}
-      }
-    }
-    
-    
-    // Lightbox standaone -> retrieve the items to display
-    // Each item needs at least a thumbnail image and a big image
-    // ONLY IMAGES SUPPORTED
-    function LightboxStandaloneFindContent() {
-
-      G.GOM.curNavLevel = 'l1';
-      
-      // retrieve all element having "data-nanogallery2-lightbox" and from the same group if defined
-      var elts = jQuery('[data-nanogallery2-Lightbox');
-      
-      // element group
-      var g = G.$E.base[0].dataset.nanogallery2Lgroup;
-
-      GetContentMarkup( elts, g );
-
-      LightboxStandaloneDisplay();
-        
-    }
-    
-    
-    // Populate G.VOM.items + open the lightbox
-    function LightboxStandaloneDisplay() {
-
-      G.VOM.items = [];
-      G.VOM.albumID = '0';
-      G.GOM.curNavLevel = 'l1';
-      var vcnt = 0;
-
-      var srct = G.$E.base[0].src;
-      var displayIdx = undefined;
-      for( var idx = 0; idx < G.I.length; idx++ ) {
-        if( G.I[idx].kind == 'image' ) {
-          var vimg = new VImg(idx);
-          G.VOM.items.push(vimg);
-
-          if( G.I[idx].thumbImg().src == srct ) {
-            // same thumbnail URL
-            displayIdx = vcnt;
-          }
-          vcnt++;
-        }
-        
-      }
-      if( G.VOM.items.length > 0 ) {
-        LightboxOpen( displayIdx );
-      }
-      else {
-        NanoConsoleLog(G, 'No content for Lightbox standalone.');
-      }
-    }
-   
-    
-    
-    // Parse string to extract albumID and imageID (format albumID/imageID)
-    function parseIDs( IDs ) {
-      var r = { albumID: '0', imageID: '0' };
-      
-      var t = IDs.split('/');
-      if( t.length > 0 ) {
-        r.albumID = t[0];
-        if( t.length > 1 ) {
-          r.imageID = t[1];
-        }
-      }
-      return r;
-    }
-    
-
-    /** @function DisplayAlbum */
-    function DisplayAlbum( imageID, albumID ) {
-      // close viewer if already displayed
-      if( G.VOM.viewerDisplayed ) {
-        LightboxClose(null);
-      }
-    
-      // set current navigation level (l1 or lN)
-      var albumIdx = NGY2Item.GetIdx(G, albumID);
-      G.GOM.curNavLevel = 'lN';
-      if( albumIdx == 0 ) {
-        G.GOM.curNavLevel = 'l1';
-      }
-      G.layout.SetEngine();
-      G.galleryResizeEventEnabled = false;
-
-      if( albumIdx == -1 ) {
-        NGY2Item.New( G, '', '', albumID, '0', 'album' );    // create empty album
-        albumIdx = G.I.length - 1;
-      }
-    
-      if( !G.I[albumIdx].contentIsLoaded ) {
-        // get content of the album if not already loaded
-        AlbumGetContent( albumID, DisplayAlbum, imageID, albumID );
-        return;
-      }
-    
-      ThumbnailSelectionClear();
-    
-      G.GOM.pagination.currentPage = 0;
-      SetLocationHash( albumID, '' );
-      GalleryRender( albumIdx );
-    
-    }
-
-
-    //----- manage the bottom area of the gallery -> "pagination" or "more button"
-    function GalleryBottomManage() {
-
-      switch( G.galleryDisplayMode.Get() ) {
-        case 'PAGINATION':
-          if( G.layout.support.rows && G.galleryMaxRows.Get() > 0 ) {
-            ManagePagination( G.GOM.albumIdx );
-          }
-          break;
-        case 'MOREBUTTON':
-          G.$E.conTnBottom.off('click');
-          var nb = G.GOM.items.length-G.GOM.itemsDisplayed;
-          if( nb == 0 ) {
-            G.$E.conTnBottom.empty();
-          }
-          else {
-            G.$E.conTnBottom.html('<div class="nGY2GalleryMoreButton"><div class="nGY2GalleryMoreButtonAnnotation">+'+nb+' ' + G.O.icons.galleryMoreButton +'</div></div>');
-            G.$E.conTnBottom.on('click', function(e) {
-              G.GOM.displayedMoreSteps++;
-              GalleryResize();
-            });
-          }
-          break;
-        case 'FULLCONTENT':
-        default:
-          break;
-      }
-    }
-    
-    
-    // add one album/folder to the breadcrumb
-    function breadcrumbAdd( albumIdx ) {
-
-      var ic='';
-      if( !G.O.breadcrumbHideIcons ) {
-        ic=G.O.icons.breadcrumbAlbum;
-        if( albumIdx == 0 ) {
-          ic=G.O.icons.breadcrumbHome;
-        }
-      }
-      var $newDiv =jQuery('<div class="oneItem">'+ic + G.I[albumIdx].title+'</div>').appendTo(G.GOM.navigationBar.$newContent.find('.nGY2Breadcrumb'));
-      if( G.O.breadcrumbOnlyCurrentLevel ) {
-        // link to parent folder (only 1 level is displayed in the breadcrumb)
-        if( albumIdx == 0 ) {
-          // no parent level -> stay on current one
-          jQuery($newDiv).data('albumID','0');
-        }
-        else {
-          jQuery($newDiv).data('albumID',G.I[albumIdx].albumID);
-        }
-      }
-      else {
-        // link to current folder
-        jQuery($newDiv).data('albumID',G.I[albumIdx].GetID());
-      }
-      $newDiv.click(function() {
-        var cAlbumID = jQuery(this).data('albumID');
-        DisplayAlbum('-1', cAlbumID);
-        return;
-      });
-    }
-
-    // add one separator to breadcrumb
-    function breadcrumbAddSeparator( lastAlbumID ) {
-      var $newSep=jQuery('<div class="oneItem">'+(G.O.RTL ? G.O.icons.breadcrumbSeparatorRtl : G.O.icons.breadcrumbSeparator)+'</div>').appendTo(G.GOM.navigationBar.$newContent.find('.nGY2Breadcrumb'));
-      jQuery($newSep).data('albumIdx',lastAlbumID);
-      $newSep.click(function() {
-        var sepAlbumIdx=jQuery(this).data('albumIdx');
-        DisplayAlbum('-1', G.I[sepAlbumIdx].GetID());
-        return;
-      });
-    }
-
-    
-    
-    // Manage the gallery toolbar (breadcrumb + tag filter + pagination next/previous)
-    function GalleryNavigationBar( albumIdx ) {
-
-      // Title + background image
-      // var bgImage='';
-      // var l=G.I.length;
-      // var albumID = G.I[albumIdx].GetID();
-      // for( var idx=0; idx<l ; idx++) {
-        // var item=G.I[idx];
-        // if( item.kind == 'image' && item.isToDisplay(albumID) ) {
-          // bgImage='<div id="pipo" class="pipo" style="height: 150px; width:100%; background-image: url("' + item.responsiveURL() + '"); background-size: cover; background-position: center center; filter:blur(2px)">pipo</div>';
-          // break;
-        // }
-      // }
-
-      //console.log(bgImage);
-    
-      // new navigation bar items are not build in the DOM, but in memory
-      G.GOM.navigationBar.$newContent=jQuery('<div class="nGY2Navigationbar"></div>');
-      //G.GOM.navigationBar.$newContent = jQuery(bgImage );
-      //console.log(G.GOM.navigationBar.$newContent);
-
-      //-- manage breadcrumb
-      if( G.O.displayBreadcrumb == true && !G.O.thumbnailAlbumDisplayImage) {
-        // retrieve new folder level
-        var newLevel = 0,
-        lstItems=[];
-        if( albumIdx != 0 ) {
-          var l=G.I.length,
-          parentID=0;
-          
-          lstItems.push(albumIdx);
-          var curIdx=albumIdx;
-          newLevel++;
-          
-          while( G.I[curIdx].albumID != 0 && G.I[curIdx].albumID != -1) {
-            for(var i=1; i < l; i++ ) {
-              if( G.I[i].GetID() == G.I[curIdx].albumID ) {
-                curIdx=i;
-                lstItems.push(curIdx);
-                newLevel++;
-                break;
-              }
-            }
-          }
-        }
-        
-        // build breadcrumb
-        if( !(G.O.breadcrumbAutoHideTopLevel && newLevel == 0) ) {
-          BreadcrumbBuild( lstItems );
-        }
-      }
-
-      
-      //-- manage and build tag filters
-      if( G.galleryFilterTags.Get() != false ) {
-        var nTags = G.I[albumIdx].albumTagList.length;
-        if( nTags > 0 ) {
-          for(var i = 0; i < nTags; i++ ) {
-            var s = G.I[albumIdx].albumTagList[i];
-            var ic = G.O.icons.navigationFilterUnselected;
-            var tagClass = 'Unselected';
-            if( jQuery.inArray(s, G.I[albumIdx].albumTagListSel) >= 0 ) {
-              tagClass = 'Selected';
-              ic = G.O.icons.navigationFilterSelected;
-            }
-            
-            var $newTag = jQuery('<div class="nGY2NavigationbarItem nGY2NavFilter' + tagClass + '">'+ ic +' '+ s +'</div>').appendTo(G.GOM.navigationBar.$newContent);
-
-            $newTag.click(function() {
-  
-              var $this = jQuery(this);
-              var tag = $this.text().replace(/^\s*|\s*$/, '');  //trim trailing/leading whitespace
-  
-              if( G.galleryFilterTagsMode.Get() == 'single' ) {
-                // single TAG selection
-                G.I[albumIdx].albumTagListSel = [];
-                G.I[albumIdx].albumTagListSel.push(tag);
-              }
-              else {
-                // multiple selection of TAGS
-                // if( $this.hasClass('oneTagUnselected') ){
-                if( $this.hasClass('nGY2NavFilterUnselected') ){
-                  G.I[albumIdx].albumTagListSel.push(tag);
-                }
-                else {
-                  var tidx=jQuery.inArray(tag,G.I[albumIdx].albumTagListSel);
-                  if( tidx != -1 ) {
-                    G.I[albumIdx].albumTagListSel.splice(tidx,1);
-                  }
-                }
-                $this.toggleClass('nGY2NavFilters-oneTagUnselected nGY2NavFilters-oneTagSelected');
-              }
-              
-              DisplayAlbum('-1', G.I[albumIdx].GetID());
-            });
-          }
-          
-          // clear/reset TAGS selection
-          var $newClearFilter=jQuery('<div class="nGY2NavigationbarItem nGY2NavFilterSelectAll">'+ G.O.icons.navigationFilterSelectedAll +'</div>').appendTo(G.GOM.navigationBar.$newContent);
-          $newClearFilter.click(function() {
-            // var nTags = G.I[albumIdx].albumTagList.length;
-            G.I[albumIdx].albumTagListSel = [];
-            // for(var i = 0; i < nTags; i++ ) {
-            //  var s = G.I[albumIdx].albumTagList[i];
-            //  G.I[albumIdx].albumTagListSel.push(s);
-            // }
-            DisplayAlbum('-1', G.I[albumIdx].GetID());
-          });
-        }
-      }
-      
-      // --- Gallery pagination next/previous
-      if( G.galleryDisplayMode.Get() == "PAGINATION" && G.O.galleryPaginationTopButtons ) {
-          if( G.layout.support.rows && G.galleryMaxRows.Get() > 0 ) {
-            // ManagePagination( G.GOM.albumIdx );
-            var $newTag = jQuery('<div class="nGY2NavigationbarItem nGY2NavPagination">'+G.O.icons.navigationPaginationPrevious+'</div>').appendTo(G.GOM.navigationBar.$newContent);
-            $newTag.click(function() {
-              paginationPreviousPage();
-            });
-            var $newTag = jQuery('<div class="nGY2NavigationbarItem nGY2NavPagination">'+G.O.icons.navigationPaginationNext+'</div>').appendTo(G.GOM.navigationBar.$newContent);
-            $newTag.click(function() {
-              paginationNextPage();
-            });
-          }
-      }
-
-    }
-    
-    function BreadcrumbBuild(lstItems) {
-
-      // console.log(G.GOM.navigationBar.$newContent);
-      jQuery('<div class="nGY2NavigationbarItem nGY2Breadcrumb"></div>').appendTo(G.GOM.navigationBar.$newContent);
-      // console.log(G.GOM.navigationBar.$newContent);
-      
-      if( G.O.breadcrumbOnlyCurrentLevel ) {
-        // display only 1 separator and the current folder level
-        if( lstItems.length == 0 ) {
-          breadcrumbAdd(0);
-        }
-        else {
-          var last=lstItems.length-1;
-          if( lstItems.length == 1 ) {
-            breadcrumbAddSeparator(0);    // root level
-          }
-          else {
-            breadcrumbAddSeparator(lstItems[0]);
-          }
-          breadcrumbAdd(lstItems[0]);
-        }
-      }
-      else {
-        // display the full breadcrum (full folder levels including root level)
-        breadcrumbAdd(0);
-        if( lstItems.length > 0 ) {
-          breadcrumbAddSeparator(0);
-          for(var i=lstItems.length-1; i>=0 ; i-- ) {
-            breadcrumbAdd(lstItems[i]);
-            if( i > 0 ) {
-              breadcrumbAddSeparator(lstItems[i-1]);
-            }
-          }
-        }
-      }
-    
-    }
-    
-
-    // Display gallery pagination
-    function ManagePagination( albumIdx ) {
-
-      G.$E.conTnBottom.css('opacity', 0);
-      G.$E.conTnBottom.children().remove();
-
-      if( G.GOM.items.length == 0 ) { return; }   // no thumbnail to display
-
-      // calculate the number of pages
-      var nbPages=Math.ceil((G.GOM.items[G.GOM.items.length - 1].row + 1)/G.galleryMaxRows.Get());
-
-      // only one page -> do not display pagination
-      if( nbPages == 1 ) { return; }
-
-      // check if current page still exists (for example after a resize)
-      if( G.GOM.pagination.currentPage > (nbPages-1) ) {
-        G.GOM.pagination.currentPage = nbPages-1;
-      }
-      
-      GalleryRenderGetInterval();
-      // nothing to display --> exit
-      if( G.GOM.displayInterval.len == 0 ) { return; }
-      
-      // display "previous"
-      if( G.O.galleryPaginationMode == 'NUMBERS' && G.GOM.pagination.currentPage > 0 ) {
-        var $eltPrev = jQuery('<div class="nGY2PaginationPrev">'+G.O.icons.paginationPrevious+'</div>').appendTo(G.$E.conTnBottom);
-        $eltPrev.click(function(e) {
-          paginationPreviousPage();
-        });
-      }
-
-      var firstPage = 0;
-      var lastPage = nbPages;
-      if( G.O.galleryPaginationMode != 'NUMBERS' ) {
-        // no 'previous'/'next' and no max number of pagination items
-        firstPage = 0;
-      }
-      else {
-        // display pagination numbers and previous/next
-        var vp = G.O.paginationVisiblePages;
-        var numberOfPagesToDisplay = G.O.paginationVisiblePages;
-        if( numberOfPagesToDisplay >= nbPages ) {
-          firstPage = 0;
-        }
-        else {
-          // we have more pages than we want to display
-          var nbBeforeAfter = 0;
-          if( isOdd(numberOfPagesToDisplay) ) {
-            nbBeforeAfter = (numberOfPagesToDisplay + 1) / 2;
-          }
-          else {
-            nbBeforeAfter = numberOfPagesToDisplay / 2;
-          }
-          
-          if( G.GOM.pagination.currentPage < nbBeforeAfter ) {
-            firstPage = 0;
-            lastPage = numberOfPagesToDisplay - 1;
-            if( lastPage > nbPages ) {
-              lastPage = nbPages - 1;
-            }
-          }
-          else {
-            firstPage = G.GOM.pagination.currentPage - nbBeforeAfter;
-            lastPage = firstPage + numberOfPagesToDisplay;
-            if( lastPage > nbPages ) {
-              lastPage = nbPages - 1;
-            }
-          }
-          
-          if( (lastPage - firstPage) < numberOfPagesToDisplay ) {
-            firstPage = lastPage - numberOfPagesToDisplay;
-            if( firstPage < 0 ) {
-              firstPage = 0;
-            }
-          }
-
-        }
-      }
-
-      // render pagination items
-      for(var i = firstPage; i < lastPage; i++ ) {
-        var c = '';
-        var p = '';
-
-        switch( G.O.galleryPaginationMode ) {
-          case 'NUMBERS':
-            c = 'nGY2paginationItem';
-            p = i + 1;
-            break;
-          case 'DOTS':
-            c = 'nGY2paginationDot';
-            break;
-          case 'RECTANGLES':
-            c = 'nGY2paginationRectangle';
-            break;
-        }
-        if( i == G.GOM.pagination.currentPage ) {
-          c += 'CurrentPage';
-        }
-
-        var elt$ = jQuery('<div class="' + c + '">' + p + '</div>').appendTo(G.$E.conTnBottom);
-        elt$.data('pageNumber', i );
-        elt$.click( function(e) {
-          G.GOM.pagination.currentPage = jQuery(this).data('pageNumber');
-          TriggerCustomEvent('pageChanged');
-
-          // scroll to top of gallery if not displayed
-          G.GOM.ScrollToTop();
- 
-          GalleryDisplayPart1( true );
-          GalleryDisplayPart2( true );
-        });
-
-      }
-
-      // display "next"
-      if( G.O.galleryPaginationMode == 'NUMBERS' && (G.GOM.pagination.currentPage + 1) < nbPages ) {
-        var $eltNext = jQuery('<div class="nGY2PaginationNext">' + G.O.icons.paginationNext + '</div>').appendTo(G.$E.conTnBottom);
-        $eltNext.click( function(e) {
-          paginationNextPage();
-        });
-      }
-
-      G.$E.conTnBottom.css('opacity', 1);
-
-    }
-    function isOdd(num) { return (num % 2) == 1;}
-    
-    // pagination - next page
-    function paginationNextPage() {
-      var aIdx = G.GOM.albumIdx,
-      n1 = 0;
-      ThumbnailHoverOutAll();
-      
-      // pagination - max lines per page mode
-      if( G.galleryMaxRows.Get() > 0 ) {
-        // number of pages
-        n1 = (G.GOM.items[G.GOM.items.length - 1].row + 1) / G.galleryMaxRows.Get();
-      }
-      var n2 = Math.ceil(n1);
-      var pn = G.GOM.pagination.currentPage;
-      if( pn < (n2-1) ) {
-        pn++;
-      }
-      else {
-        pn = 0;
-      }
-      
-      G.GOM.pagination.currentPage = pn;
-      TriggerCustomEvent('pageChanged');
-
-      // scroll to top of gallery if not displayed
-      G.GOM.ScrollToTop();
-
-      GalleryDisplayPart1( true );
-      GalleryDisplayPart2( true );
-    }
-    
-    // pagination - previous page
-    function paginationPreviousPage() {
-      // var aIdx=G.$E.conTnBottom.data('galleryIdx'),
-      var aIdx = G.GOM.albumIdx,
-      n1 = 0;
+      viewerRotateRight:            '<i class="nGY2Icon-cw"></i>',
+      viewerShoppingcart:           '<i class="nGY2Icon-basket"></i>',
+      user:                         '<i class="nGY2Icon-user"></i>',
+      location:                     '<i class="nGY2Icon-location"></i>',
+      picture:                     '<i class="nGY2Icon-picture"></i>',
+      config:                       '<i class="nGY2Icon-wrench"></i>',
+      shareFacebook:                '<i style="color:#3b5998;" class="nGY2Icon-facebook-squared"></i>',
+      shareTwitter:                 '<i style="color:#00aced;" class="nGY2Icon-twitter-squared"></i>',
+      // shareGooglePlus:              '<i style="color:#dd4b39;" class="nGY2Icon-gplus-squared"></i>',
+      shareTumblr:                  '<i style="color:#32506d;" class="nGY2Icon-tumblr-squared"></i>',
+      sharePinterest:               '<i style="color:#cb2027;" class="nGY2Icon-pinterest-squared"></i>',
+      shareVK:                      '<i style="color:#3b5998;" class="nGY2Icon-vkontakte"></i>',
+      shareMail:                    '<i style="color:#555;" class="nGY2Icon-mail-alt"></i>',
+      viewerCustomTool1:            'T1',
+      viewerCustomTool2:            'T2',
+      viewerCustomTool3:            'T3',
+      viewerCustomTool4:            'T4',
+      viewerCustomTool5:            'T5',
+      viewerCustomTool6:            'T6',
+      viewerCustomTool7:            'T7',
+      viewerCustomTool8:            'T8',
+      viewerCustomTool9:            'T9',
+      viewerCustomTool10:           'T10'
+    }
+  };
 
-      ThumbnailHoverOutAll();
-      
-      // pagination - max lines per page mode
-      if( G.galleryMaxRows.Get() > 0 ) {
-        // number of pages
-        n1 = (G.GOM.items[G.GOM.items.length - 1].row + 1) / G.galleryMaxRows.Get();
-      }
-      var n2 = Math.ceil(n1);
-      
-      // var pn=G.$E.conTnBottom.data('currentPageNumber');
-      var pn = G.GOM.pagination.currentPage;
-      if( pn > 0 ) {
-        pn--;
-      }
-      else {
-        pn = n2 - 1;
-      }
-
-      G.GOM.pagination.currentPage = pn;
-      TriggerCustomEvent('pageChanged');
-
-      // scroll to top of gallery if not displayed
-      G.GOM.ScrollToTop();
-
-      GalleryDisplayPart1( true );
-      GalleryDisplayPart2( true );
-    }
-
-    // retrieve the from/to intervall for gallery thumbnail render
-    function GalleryRenderGetInterval() {
-      G.GOM.displayInterval.from = 0;
-      G.GOM.displayInterval.len = G.I.length;
-      
-      switch( G.galleryDisplayMode.Get() ) {
-        case 'PAGINATION':
-          if( G.layout.support.rows ) {
-            var nbTn = G.GOM.items.length;
-            var firstRow = G.GOM.pagination.currentPage * G.galleryMaxRows.Get();
-            var lastRow = firstRow + G.galleryMaxRows.Get();
-            var firstTn = -1;
-            G.GOM.displayInterval.len = 0;
-            for( var i = 0; i < nbTn ; i++ ) {
-              var curTn = G.GOM.items[i];
-              if( curTn.row >= firstRow && curTn.row < lastRow ) {
-                if( firstTn == -1 ) {
-                  G.GOM.displayInterval.from = i;
-                  firstTn = i;
-                }
-                G.GOM.displayInterval.len++;
-              }
-            }
-          }
-          break;
-        case 'MOREBUTTON':
-          if( G.layout.support.rows ) {
-            var nbTn = G.GOM.items.length;
-            var lastRow = G.O.galleryDisplayMoreStep * (G.GOM.displayedMoreSteps+1);
-            G.GOM.displayInterval.len = 0;
-            for( var i = 0; i < nbTn ; i++ ) {
-              var curTn = G.GOM.items[i];
-              if( curTn.row < lastRow ) {
-                G.GOM.displayInterval.len++;
-              }
-            }
-          }
-          break;
-        case 'ROWS':
-          if( G.layout.support.rows ) {
-            var nbTn = G.GOM.items.length;
-            var lastRow = G.galleryMaxRows.Get();
-            if( G.galleryLastRowFull.Get() && G.GOM.lastFullRow != -1 ) {
-              if( lastRow > (G.GOM.lastFullRow + 1) ) {
-                lastRow = G.GOM.lastFullRow + 1;
-              }
-            }
-            G.GOM.displayInterval.len = 0;
-            for( var i = 0; i < nbTn ; i++ ) {
-              var curTn = G.GOM.items[i];
-              if( curTn.row < lastRow ) {
-                G.GOM.displayInterval.len++;
-              }
-            }
-          }
-          break;
-        default:
-        case 'FULLCONTENT':
-        if( G.layout.support.rows && G.galleryLastRowFull.Get() && G.GOM.lastFullRow != -1 ) {
-            var nbTn = G.GOM.items.length;
-            var lastRow = G.GOM.lastFullRow + 1;
-            G.GOM.displayInterval.len = 0;
-            for( var i = 0; i < nbTn ; i++ ) {
-              var curTn = G.GOM.items[i];
-              if( curTn.row < lastRow ) {
-                G.GOM.displayInterval.len++;
-              }
-            }
-          }
-          break;
-      }
-    }
-    
-    
-    // RENDER THE GALLERY
-    function GalleryRender( albumIdx ) {
-      TriggerCustomEvent('galleryRenderStart');
-      clearTimeout(G.GOM.slider.timerID);
-      G.GOM.slider.hostIdx = -1;      // disabled slider on thumbnail
-      
-      var fu=G.O.fnGalleryRenderStart;
-      if( fu !== null ) {
-        typeof fu == 'function' ? fu(albumIdx) : window[fu](albumIdx);
-      }
-
-      G.layout.SetEngine();
-      G.galleryResizeEventEnabled = false;
-      G.GOM.albumIdx = -1;
-      G.GOM.lastDisplayedIdx = -1;
-
-      // pagination
-      if( G.$E.conTnBottom !== undefined ) {
-        // G.$E.conTnBottom.children().remove();
-        G.$E.conTnBottom.empty();
-      }
-
-      // navigation toolbar (breadcrumb + tag filters + pagination next/previous)
-      GalleryNavigationBar(albumIdx);
-      
-      if( G.GOM.firstDisplay ) {
-				// first gallery display
-        G.GOM.firstDisplay = false;
-        var d = Date.now()-G.GOM.firstDisplayTime;
-        if( d < G.O.galleryRenderDelay ) {
-					// display after defined delay
-          // setTimeout( function() { GalleryRenderPart1( albumIdx )}, G.O.galleryRenderDelay-d );
-          requestTimeout( function() { GalleryRenderPart1( albumIdx )}, G.O.galleryRenderDelay-d );
-        }
-        else {
-          GalleryRenderPart1( albumIdx );
-        }
-        G.O.galleryRenderDelay = 0;
-        
-      }
-      else {
-        var hideNavigationBar = false;
-        if( G.GOM.navigationBar.$newContent.children().length == 0 ) {
-          hideNavigationBar = true;
-        }
-
-        // hide everything
-        var tweenable = new NGTweenable();
-        tweenable.tween({
-          from:         { 'opacity': 1 },
-          to:           { 'opacity': 0 },
-          duration:     300,
-          easing:       'easeInQuart',
-          attachment:   { h: hideNavigationBar },
-          step:         function (state, att) {
-            G.$E.conTnParent.css({'opacity': state.opacity });
-            if( att.h ) {
-              G.$E.conNavigationBar.css({ 'opacity': state.opacity });
-            }
-          },
-          finish:       function (state, att) {
-            if( att.h ) {
-              G.$E.conNavigationBar.css({ 'opacity': 0, 'display': 'none' });
-            }
-            // scroll to top of the gallery if needed
-            G.GOM.ScrollToTop();
-
-            GalleryRenderPart1( albumIdx );
-          }
-        });
-      }
-    }
-
-
-    function GalleryRenderPart1( albumIdx ) {
-      // display new navigation bar
-      var oldN = G.$E.conNavigationBar.children().length;
-      G.$E.conNavigationBar.empty();
-      G.GOM.navigationBar.$newContent.children().clone(true,true).appendTo(G.$E.conNavigationBar);
-      // G.GOM.navigationBar.$newContent.appendTo(G.$E.conNavigationBar);
-      if( G.$E.conNavigationBar.children().length > 0 && oldN == 0 ) {
-        G.$E.conNavigationBar.css({ 'opacity': 0, 'display': 'block' });
-        var tweenable = new NGTweenable();
-        tweenable.tween({
-          from:     { opacity: 0 },
-          to:       { opacity: 1 },
-          duration: 200,
-          easing:   'easeInQuart',
-          step:     function (state) {
-            // window.ng_draf( function() {
-              G.$E.conNavigationBar.css( state );
-            // });
-          },
-          finish:   function (state) {
-            // window.ng_draf( function() {
-              G.$E.conNavigationBar.css({ 'opacity': 1 });
-              // display gallery
-              // GalleryRenderPart2( albumIdx );
-              // setTimeout(function(){ GalleryRenderPart2(albumIdx) }, 60);
-              requestTimeout(function(){ GalleryRenderPart2(albumIdx) }, 60);
-            // });
-          }
-        });
-      }
-      else {
-        requestTimeout(function(){ GalleryRenderPart2(albumIdx) }, 60);
-      }
-
-    }
-    
-    // Gallery render part 2 -> remove all existing thumbnails
-    function GalleryRenderPart2(albumIdx) {
-
-			G.GOM.lastZIndex = parseInt(G.$E.base.css('z-index'));
-      if( isNaN(G.GOM.lastZIndex) ) {
-        G.GOM.lastZIndex=0;
-      }
-      G.$E.conTnParent.css({ 'opacity': 0 });
-      G.$E.conTn.off().empty();
-      var l = G.I.length;
-      for( var i = 0; i < l ; i++ ) {
-        // reset each item
-        var item = G.I[i];
-        item.hovered = false;
-        item.$elt = null;
-        item.$Elts = [];
-        item.eltTransform = [];
-        item.eltFilter = [];
-        item.width = 0;
-        item.height = 0;
-        item.left = 0;
-        item.top = 0;
-        item.resizedContentWidth = 0;
-        item.resizedContentHeight = 0;
-        item.thumbnailImgRevealed = false;
-      }
-
-      if( G.CSStransformName == null ) {
-        G.$E.conTn.css('left', '0px');
-      }
-      else {
-        // G.$E.conTn.css( G.CSStransformName, 'translateX(0px)');
-        G.$E.conTn.css( G.CSStransformName, 'none');
-      }
-      
-      // setTimeout(function(){ GalleryRenderPart3(albumIdx) }, 60);
-      requestTimeout(function(){ GalleryRenderPart3(albumIdx) }, 60);
-      // GalleryRenderPart3(albumIdx);
-
-    }
-    
-    // Gallery render part 3 -> start building the new gallery
-    function GalleryRenderPart3(albumIdx) {
-      var d = new Date();      
-      
-      G.$E.conTnParent.css( 'opacity', 1);
-
-      G.GOM.items = [];
-      G.GOM.displayedMoreSteps = 0;
-      // retrieve label height      
-      if( G.O.thumbnailLabel.get('position') == 'onBottom' ) {
-        // retrieve height each time because size can change depending on thumbnail's settings
-        G.tn.labelHeight[G.GOM.curNavLevel] = ThumbnailGetLabelHeight();
-      }
-      else {
-        G.tn.labelHeight[G.GOM.curNavLevel] = 0;
-      }
-      G.GOM.albumIdx=albumIdx;
-
-      TriggerCustomEvent('galleryRenderEnd');
-      var fu=G.O.fnGalleryRenderEnd;
-      if( fu !== null ) {
-        typeof fu == 'function' ? fu(albumIdx) : window[fu](albumIdx);
-      }
-
-      // Step 1: populate GOM
-      if( GalleryPopulateGOM() ) {
-        // step 2: calculate layout
-        GallerySetLayout();
-
-        // step 3: display whole gallery 
-        GalleryAppear();
-        
-        // step 4: display thumbnails
-        GalleryDisplayPart1( false );
-        requestTimeout(function(){ GalleryDisplayPart2( false ) }, 120);
-      }
-      else {
-        G.galleryResizeEventEnabled = true;
-      }
-      
-      if( G.O.debugMode ) { console.log('GalleryRenderPart3: '+ (new Date()-d)); }
-
-    }
-    
-    
-    // Resize the gallery
-    function GalleryResize() {
-      var d = new Date();
-      G.galleryResizeEventEnabled = false;
-      // G.GOM.cache.areaWidth=G.$E.conTnParent.width();
-      if( GallerySetLayout() == false ) {
-        G.galleryResizeEventEnabled = true;
-        if( G.O.debugMode ) { console.log('GalleryResize1: '+ (new Date()-d)); }
-        return;
-      }
-      if( G.O.debugMode ) { console.log('GalleryResizeSetLayout: '+ (new Date()-d)); }
-
-      GalleryDisplayPart1( false );
-      GalleryDisplayPart2( false );
-
-      if( G.O.debugMode ) { console.log('GalleryResizeFull: '+ (new Date()-d)); }
-    }
-    
-    
-    
-    // copy items (album content) to GOM
-		// returns:
-		//		true: thumbnail image size is needed for the layout, but not set -> retrieve the sizes and display gallery
-    function GalleryPopulateGOM() {
-      
-      var preloadImages = '';
-      var imageSizeRequested = false;
-      var albumID = G.I[G.GOM.albumIdx].GetID();
-      var l = G.I.length;
-      var cnt = 0;
-
-      for( var idx = 0; idx < l; idx++ ) {
-        var item = G.I[idx];
-        // check album
-        if( item.isToDisplay(albumID) ) {
-          var w = item.thumbImg().width;
-          var h = item.thumbImg().height;
-          // if unknown image size and layout is not grid --> we need to retrieve the size of the images
-          if( G.layout.prerequisite.imageSize && ( w == 0 || h == 0) ) {
-          // if( true ) {
-            imageSizeRequested = true;
-            preloadImages += '<img src="'+item.thumbImg().src+'" data-idx="'+cnt+'" data-albumidx="'+G.GOM.albumIdx+'">';
-          }
-          
-          // set default size if required
-          if( h == 0 ) {
-            h = G.tn.defaultSize.getHeight();
-          }
-          if( w == 0 ) {
-            w = G.tn.defaultSize.getWidth();
-          }
-          var tn = new G.GOM.GTn(idx, w, h);
-          G.GOM.items.push(tn);
-          cnt++;
-        }
-      }
-
-      TriggerCustomEvent('galleryObjectModelBuilt');
-      var fu = G.O.fnGalleryObjectModelBuilt;
-      if( fu !== null ) {
-        typeof fu == 'function' ? fu() : window[fu]();
-      }
-      
-      if( imageSizeRequested ) {
-        // preload images to retrieve their size and then resize the gallery (=GallerySetLayout()+ GalleryDisplay())
-        var $newImg = jQuery(preloadImages);
-        var gi_imgLoad = ngimagesLoaded( $newImg );
-        $newImg = null;
-        gi_imgLoad.on( 'progress', function( instance, image ) {
-        
-          if( image.isLoaded ) {
-            var idx = image.img.getAttribute('data-idx');
-            var albumIdx = image.img.getAttribute('data-albumidx');
-            if( albumIdx == G.GOM.albumIdx ) {
-              // ignore event if not on current album
-              var curTn = G.GOM.items[idx];
-              curTn.imageWidth = image.img.naturalWidth;
-              curTn.imageHeight = image.img.naturalHeight;
-              var item = G.I[curTn.thumbnailIdx];
-              item.thumbs.width[G.GOM.curNavLevel][G.GOM.curWidth] = curTn.imageWidth;
-              item.thumbs.height[G.GOM.curNavLevel][G.GOM.curWidth] = curTn.imageHeight;
- 
-              // resize the gallery
-              G.GalleryResizeThrottled();
-              
-              // set the retrieved size to all levels with same configuration  
-              var object = item.thumbs.width.l1;
-              for (var property in object) {
-                if (object.hasOwnProperty(property)) {
-                  if( property != G.GOM.curWidth ) {
-                    if( G.tn.settings.width.l1[property] == G.tn.settings.getW() && G.tn.settings.height.l1[property] == G.tn.settings.getH() ) {
-                      item.thumbs.width.l1[property] = curTn.imageWidth;
-                      item.thumbs.height.l1[property] = curTn.imageHeight;
-                    }
-                  }
-                }
-              }
-              object = item.thumbs.width.lN;
-              for (var property in object) {
-                if (object.hasOwnProperty(property)) {
-                  if( property != G.GOM.curWidth ) {
-                    if( G.tn.settings.width.lN[property] == G.tn.settings.getW() && G.tn.settings.height.lN[property] == G.tn.settings.getH() ) {
-                      item.thumbs.width.lN[property] = curTn.imageWidth;
-                      item.thumbs.height.lN[property] = curTn.imageHeight;
-                    }
-                  }
-                }
-              }
-            }
-          }
-        });
-        G.galleryResizeEventEnabled = true;
-        return false;
-      }
-      else {
-        return true;
-      }
-      
-    }
-    
-    //----- Calculate the layout of the thumbnails for the full gallery
-    function GallerySetLayout() {
-      var r = true;
-      // width of the available area
-      G.GOM.cache.areaWidth = G.$E.conTnParent.width();
-      G.GOM.displayArea = { width:0, height:0 };
-
-      switch( G.layout.engine ) {
-        case 'JUSTIFIED':
-          r = GallerySetLayoutWidthtAuto();
-          break;
-        case 'CASCADING':
-          r = GallerySetLayoutHeightAuto();
-          break;
-        case 'MOSAIC':
-          r = GallerySetLayoutMosaic();
-          break;
-        case 'GRID':
-        default:
-          r = GallerySetLayoutGrid();
-          break;
-      }
-      
-      TriggerCustomEvent('galleryLayoutApplied');
-      var fu = G.O.fnGalleryLayoutApplied;
-      if( fu !== null ) {
-        typeof fu == 'function' ? fu() : window[fu]();
-      }
-      return r;
-
-    }
-    
-    
-    //----- CASCADING LAYOUT
-    function GallerySetLayoutHeightAuto() {
-      var curCol =      0,
-      areaWidth =       G.GOM.cache.areaWidth,
-      curRow =          0,
-      colHeight =       [],
-      maxCol =          NbThumbnailsPerRow(areaWidth),
-      gutterWidth =     0,
-      gutterHeight =    G.tn.settings.GetResponsive('gutterHeight');
-      var w =           0;
-      var scaleFactor = 1;
-      var tnWidth =     G.tn.defaultSize.getOuterWidth();
-      var nbTn =        G.GOM.items.length;
-      var curPosY = 0;
-
-      if( G.O.thumbnailAlignment == 'justified' ) {
-        maxCol = Math.min(maxCol, nbTn);
-        gutterWidth = ( maxCol == 1 ? 0 : (areaWidth - (maxCol * tnWidth) ) / (maxCol - 1) );
-      }
-      else {
-        gutterWidth = G.tn.settings.GetResponsive('gutterWidth');
-      }
-
-
-      var borderWidth = G.tn.opt.Get('borderHorizontal') * 2;
-      var borderHeight = G.tn.opt.Get('borderVertical') * 2;
-
-      G.GOM.lastFullRow=-1;   // feature disabled
-
-      // Retrieve the real used width of the area (the evaluation is based on the content of the first line)
-      if( G.O.thumbnailAlignment == 'fillWidth' ) {
-        // fillWidth --> evaluate scale factor and number of columns
-        var totalGutterWidth = (maxCol - 1) * gutterWidth;
-        scaleFactor = (areaWidth - totalGutterWidth) / (maxCol * tnWidth);
-        if( scaleFactor > 1 ) {
-          maxCol++; // add one column and re-evaluate the scale factor
-        }
-        totalGutterWidth = (maxCol - 1) * gutterWidth;
-        scaleFactor = Math.min( (areaWidth - totalGutterWidth) / (maxCol*tnWidth), 1);   // no upscale
-      }
-
-      
-      tnWidth = Math.round( tnWidth * scaleFactor);
-      var contentWidth = tnWidth - borderWidth;
-
-      // loop to position the thumbnails, and set their size
-      var baseHeight = Math.round( G.tn.opt.Get('baseGridHeight') * scaleFactor );
-      for( var i = 0; i < nbTn ; i++ ) {
-        var curTn = G.GOM.items[i];
-        if( curTn.deleted == true ) { break; }    // item is logically deleted
-        if( curTn.imageHeight > 0 && curTn.imageWidth > 0 ) {
-          var curPosX = 0,
-          curPosY = 0;
-          var imageRatio = curTn.imageHeight / curTn.imageWidth;
-          // curTn.resizedContentWidth = tnWidth - borderWidth;
-          curTn.resizedContentWidth = contentWidth;
-          curTn.resizedContentHeight = curTn.resizedContentWidth * imageRatio;
-          if( baseHeight > 0 ) {
-            // grid based vertical position
-            var t = Math.max( Math.trunc(curTn.resizedContentHeight/baseHeight), 1) ;
-            curTn.resizedContentHeight = baseHeight * t + ((t-1)*(borderHeight+gutterHeight));
-          }
-          
-          curTn.height = curTn.resizedContentHeight + borderHeight + G.tn.labelHeight.get();
-          curTn.width = tnWidth;
-          curTn.row = 0;
-          
-          if( curRow == 0 ) {
-            // first row
-            curPosX = curCol * (tnWidth + gutterWidth);
-            colHeight[curCol] = curTn.height + gutterHeight;
-            
-            curCol++;
-            if( curCol >= maxCol ) {
-              curCol = 0;
-              curRow++;
-            }
-          }
-          else {
-            var c=0,
-            minColHeight=colHeight[0];
-            for( var j = 1; j < maxCol; j++) {
-              if( (colHeight[j] + 5) < minColHeight ) {     // +5 --> threshold
-                minColHeight = colHeight[j];
-                c = j;
-                //break;
-              }
-            }
-            curPosY = colHeight[c];
-            curPosX = c * (tnWidth + gutterWidth);
-            colHeight[c] = curPosY + curTn.height + gutterHeight;
-          }
-
-          var x = curPosX;
-          if( G.O.RTL) {
-            x= w - curPosX - tnWidth;
-          }
-
-          curTn.left = x;
-          curTn.top = curPosY;
-        }
-      }
-
-      G.GOM.displayArea.width= maxCol * (tnWidth + gutterWidth) - gutterWidth;
-      return true;
-    }
-    
-    
-    //----- JUSTIFIED LAYOUT
-    function GallerySetLayoutWidthtAuto() {
-      var curWidth =               0,
-      areaWidth =                  G.GOM.cache.areaWidth,
-      lastPosX =                   0,
-      curPosY =                    0,
-      rowLastItem =                [],
-      rowNum =                     0,
-      rowHeight =                  [],
-      bNewRow =                    false,
-      cnt =                        0,
-      gutterWidth =                G.tn.settings.GetResponsive('gutterWidth'),
-      gutterHeight =               G.tn.settings.GetResponsive('gutterHeight');
-      // by grief-of-these-days
-      var maxRowHeightVertical =   0; // max height of a row with vertical thumbs
-      var maxRowHeightHorizontal = 0; // max height of a row with horizontal thumbs
-      var rowHasVertical =         false; // current row has vertical thumbs
-      var rowHasHorizontal =       false; // current row has horizontal thumbs
-
-      var tnHeight = G.tn.defaultSize.getOuterHeight();
-      var borderWidth = G.tn.opt.Get('borderHorizontal') * 2;
-      var borderHeight = G.tn.opt.Get('borderVertical') * 2;
-      var nbTnInCurrRow = 1;
-      var nbTn = G.GOM.items.length;
-
-      // first loop --> retrieve each row image height
-      for( var i = 0; i < nbTn ; i++ ) {
-        var curTn = G.GOM.items[i];
-        if( curTn.deleted == true ) { break; }    // item is logically deleted
-        if( curTn.imageWidth > 0 ) {
-          var imageRatio = curTn.imageWidth / curTn.imageHeight;
-          var imageWidth = Math.floor( tnHeight * imageRatio );
-
-          if( bNewRow ) {
-            bNewRow = false;
-            rowNum++;
-            curWidth = 0;
-            rowHasVertical = false;
-            rowHasHorizontal = false;
-            nbTnInCurrRow = 1;
-          }
-          // by grief-of-these-days
-          if( curTn.imageHeight > curTn.imageWidth ) {
-            rowHasVertical = true;
-          }
-          else {
-            rowHasHorizontal = true;
-          }
-          
-          if( (curWidth + gutterWidth + imageWidth) < (areaWidth - (nbTnInCurrRow * borderWidth)) ) {
-            // enough place left in the current row
-            curWidth += imageWidth + gutterWidth;
-            rowHeight[rowNum] = tnHeight;
-            
-            // prevent incomplete row from being heigher than the previous ones.
-            // by grief-of-these-days
-            var rowHeightLimit = Math.max(rowHasVertical ? maxRowHeightVertical : 0, rowHasHorizontal ? maxRowHeightHorizontal : 0);
-            if( rowHeightLimit > 0 ) {
-              rowHeight[rowNum] = Math.min(rowHeight[rowNum], rowHeightLimit);
-            }
-            
-            rowLastItem[rowNum] = i;
-          }
-          else {
-            // new row after current item --> we need to adujet the row height to have enough space for the current thumbnail
-            curWidth += gutterWidth+imageWidth;
-            var ratio = (areaWidth - nbTnInCurrRow * borderWidth) / curWidth;
-            var rH = Math.floor(tnHeight * ratio);
-            rowHeight[rowNum] = rH;
-            
-            // save the max row height for each thumb orientation.
-            // by grief-of-these-days
-            if( rowHasVertical ) {
-              maxRowHeightVertical = Math.max( maxRowHeightVertical, rH );
-            }
-            if( rowHasHorizontal ) {
-              maxRowHeightHorizontal = Math.max( maxRowHeightHorizontal, rH );
-            }
-            
-            rowLastItem[rowNum] = i;
-            bNewRow = true;
-          }
-          cnt++;
-          nbTnInCurrRow++;
-        }
-      }
-
-      rowNum = 0;
-      curPosY = 0;
-      lastPosX = 0;
-      cnt = 0;
-      
-      G.GOM.lastFullRow = 0;    // display at leat 1 row (even if not full)
-      
-      // second loop --> calculate each thumbnail size
-      for( var i = 0; i < nbTn ; i++ ) {
-        var curTn = G.GOM.items[i];
-        if( curTn.imageWidth > 0 ) {
-          var imageRatio = curTn.imageWidth / curTn.imageHeight;
-          var imageWidth = Math.floor( imageRatio * rowHeight[rowNum] ); // border is already NOT included
-
-          if( i == rowLastItem[rowNum] ) {
-            // row last item --> adjust image width because of rounding problems
-            if( rowLastItem.length != (rowNum+1) ) {
-              // last item in current row -> use the full remaining width
-              imageWidth = areaWidth - lastPosX - borderWidth;
-            }
-            else {
-              // very last item (on the last row)
-              if( (lastPosX + gutterWidth + imageWidth + borderWidth ) > areaWidth ) {
-                // reduce size if image is wider as the remaining space
-                imageWidth = areaWidth - lastPosX - borderWidth;
-              }
-            }
-          }
-          
-          var rh = parseInt( rowHeight[rowNum] );
-          imageWidth = parseInt( imageWidth );
-
-          // thumbnail image size
-          curTn.resizedContentWidth = imageWidth;
-          curTn.resizedContentHeight = rh;
-          // thumbnail position and size
-          curTn.width = imageWidth + borderWidth;
-          curTn.height= rh + G.tn.labelHeight.get() + borderHeight;
-          curTn.row = rowNum;
-
-          curTn.top = curPosY;
-          var x = lastPosX;
-          if( G.O.RTL) {
-            x = areaWidth - lastPosX - curTn.width ;
-          }
-          curTn.left = x;
-
-          lastPosX += curTn.width + gutterWidth;
-
-          if( i == rowLastItem[rowNum] ) {
-            // start a new row
-            curPosY += curTn.height + gutterHeight;
-            G.GOM.lastFullRow = rowNum - 1;
-            rowNum++;
-            lastPosX = 0;
-          }
-          cnt++;
-        }
-        else {
-          return false;
-        }
-      }
-      
-      if( false ) {
-        var newTop = 0;
-        if( typeof GOMidx !== 'undefined' ) {
-          // hover effect on gallery (vs on thumbnail) --> experimental / not used
-          if( G.GOM.albumIdx != -1 ) {
-            var hoveredTn = G.GOM.items[GOMidx];
-            var item = G.I[hoveredTn.thumbnailIdx];
-            
-            // hovered thumbnail
-            hoveredTn.width += 40;
-            hoveredTn.height += 40;
-            // todo : left
-            
-            for( var i = 0; i < nbTn ; i++ ) {
-              var curTn = G.GOM.items[i];
-              if( curTn.imageWidth > 0 ) {
-                if( curTn.row == hoveredTn.row ) {
-                  // hovered row
-                  newTop = 40;
-                  if( hoveredTn.thumbnailIdx != curTn.thumbnailIdx ) {
-                    // not hovered thumbnail
-                    // curTn.resizedContentWidth+=10;
-                    // curTn.resizedContentHeight+=20;
-                    // curTn.width+=10;
-                    curTn.top += 30;
-                    curTn.width -= 20;
-                    curTn.height -= 20;
-                  }
-                }
-                else {
-                  // not hovered row
-                  if( curTn.row == 0 ) {
-                    // first row
-                  }
-                  else {
-                    curTn.top += newTop;
-                  }
-                }
-              }
-            }
-          }
-        }
-      }
-      
-      G.GOM.displayArea.width = areaWidth;
-      return true;
-    }    
-    
-
-    //----- MOSAIC LAYOUT
-    // Grid using a user defined pattern layout
-    // With this layout, a pattern definition is handeld a row
-    function GallerySetLayoutMosaic() {
-      var areaWidth =     G.GOM.cache.areaWidth;
-      var gutterHeight =  G.tn.settings.GetResponsive('gutterHeight');
-      var gutterWidth =   G.tn.settings.GetResponsive('gutterWidth');
-      var borderWidth =   G.tn.opt.Get('borderHorizontal') * 2;
-      var borderHeight =  G.tn.opt.Get('borderVertical') * 2;
-
-      var nbTn = G.GOM.items.length;
-      var row = 0;
-      var h = 0;
-      var n = 0;
-      
-      
-      // first loop: evaluate the gallery width based on the first row
-      var nbCols = 0;
-      var maxW = 0;
-      var mosaicPattern = G.tn.settings.getMosaic();
-      for( var i = 0; i < nbTn ; i++ ) {
-        var curPatternElt = mosaicPattern[n];
-
-        var cLeft = (curPatternElt.c - 1) * G.tn.defaultSize.getOuterWidth() + (curPatternElt.c - 1) * gutterWidth;
-        var cWidth = curPatternElt.w * G.tn.defaultSize.getOuterWidth() + (curPatternElt.w - 1) * gutterWidth;
-        
-        maxW = Math.max(maxW, cLeft + cWidth );
-        
-        nbCols = Math.max(nbCols, (curPatternElt.c - 1) + curPatternElt.w );
-
-        n++;
-        if( n >= mosaicPattern.length ) {
-          // end of pattern
-          break;
-        }
-      }
-      var totalGutterWidth = (nbCols - 1) * gutterWidth;
-      var scaleFactor = Math.min( (areaWidth - totalGutterWidth ) / ( maxW - totalGutterWidth ), 1);
-      
-      // second loop: position all the thumbnails based on the layout pattern
-      row = 0;
-      n = 0;
-      var mosaicPattern = G.tn.settings.getMosaic();
-      for( var i = 0; i < nbTn ; i++ ) {
-        var curTn = G.GOM.items[i];
-        var curPatternElt = mosaicPattern[n];
-        
-        curTn.top = Math.round((curPatternElt.r - 1) * G.tn.defaultSize.getOuterHeight()*scaleFactor) + (curPatternElt.r - 1) * gutterHeight + row * h + (G.tn.labelHeight.get()*(curPatternElt.r-1)) ;
-        if( row > 0 ) {
-          curTn.top += gutterHeight;
-        }
-
-        curTn.left = (curPatternElt.c - 1) * Math.round(G.tn.defaultSize.getOuterWidth()*scaleFactor) + (curPatternElt.c - 1) * gutterWidth;
-
-        curTn.height = Math.round(curPatternElt.h * G.tn.defaultSize.getOuterHeight() * scaleFactor) + (curPatternElt.h - 1) * gutterHeight + (G.tn.labelHeight.get() * curPatternElt.h);
-        curTn.resizedContentHeight = curTn.height - G.tn.labelHeight.get() - borderHeight;
-
-        curTn.width = Math.round(curPatternElt.w * G.tn.defaultSize.getOuterWidth()*scaleFactor) + (curPatternElt.w - 1) * gutterWidth;
-        curTn.resizedContentWidth = curTn.width - borderWidth ;
-
-        curTn.row = row;
-        if( row == 0 ) {
-          h=Math.max(h, curTn.top + curTn.height);
-        }
-
-        n++;
-        if( n >= mosaicPattern.length ) {
-          // end pattern -> new line
-          n = 0;
-          row++;
-        }
-      }
-      
-      G.GOM.displayArea.width = (maxW - totalGutterWidth) * scaleFactor + totalGutterWidth;
-      return true;
-    }
-    
-    
-    
-    // --- GRID LAYOUT
-    function GallerySetLayoutGrid() {
-      var curPosX=      0,
-      curPosY=          0,   
-      areaWidth=        G.GOM.cache.areaWidth,
-      gutterWidth=      0,
-      gutterHeight=     G.tn.settings.GetResponsive('gutterHeight'),
-      maxCol=           NbThumbnailsPerRow(areaWidth),
-      w=                0,
-      cols=             [],
-      curCol=           0,
-      newAreaWidth =    areaWidth,
-      tnWidth=          G.tn.defaultSize.getOuterWidth();
-      var scaleFactor = 1;
-      var nbTn=         G.GOM.items.length;
-      var borderWidth   = G.tn.opt.Get('borderHorizontal') * 2;
-      var borderHeight  = G.tn.opt.Get('borderVertical') * 2;
-      
-      // retrieve gutter width
-      if( G.O.thumbnailAlignment == 'justified' ) {
-        maxCol = Math.min( maxCol, nbTn);
-        gutterWidth = (maxCol==1 ? 0 : (areaWidth-(maxCol*tnWidth))/(maxCol-1));
-      }
-      else {
-        gutterWidth = G.tn.settings.GetResponsive('gutterWidth');
-      }
-
-      // first loop to retrieve the real used width of the area (the evaluation is based on the content of the first line)
-      // Retrieve the real used width of the area (the evaluation is based on the content of the first line)
-      if( G.O.RTL || G.O.thumbnailAlignment == 'fillWidth' ) {
-        // scaled --> evaluate scale factor and number of columns
-        var totalGutterWidth = (maxCol-1) * gutterWidth;
-        scaleFactor = (areaWidth - totalGutterWidth) / (maxCol*tnWidth);
-        if( scaleFactor > 1 ) {
-          maxCol++; // add one column and re-evaluate the scale factor
-        }
-        totalGutterWidth = (maxCol-1) * gutterWidth;
-        scaleFactor = Math.min( (areaWidth - totalGutterWidth) / (maxCol*tnWidth), 1);   // no upscale
-        newAreaWidth = (maxCol*tnWidth) + totalGutterWidth;
-      }
-
-      
-      G.GOM.lastFullRow = 0 ;    // display at leat 1 row (even if not full)
-      var lastPosY = 0;
-      var row = 0;
-      
-      tnWidth = Math.round(tnWidth * scaleFactor);
-      var contentWidth = tnWidth - borderWidth;
-      var tnHeight = Math.round(G.tn.defaultSize.getOuterHeight() * scaleFactor) + G.tn.labelHeight.get();
-      var contentHeight = Math.round( G.tn.defaultSize.getOuterHeight() * scaleFactor) - borderHeight;
-      
-      // loop to position and to set size of all thumbnails
-      for( var i = 0; i < nbTn ; i++ ) {
-        if( curPosY == 0 ) {
-          curPosX = curCol * (tnWidth + gutterWidth)
-          cols[curCol] = curPosX;
-          w = curPosX + tnWidth;
-        }
-        else {
-          curPosX = cols[curCol];
-        }
-
-        var x = curPosX;
-        if( G.O.RTL ) {
-          x = parseInt(newAreaWidth) - curPosX - tnWidth;
-        }
-        
-        // MANDATORY : set thumbnail position AND size
-        var curTn=G.GOM.items[i];
-        curTn.top = curPosY;
-        curTn.left = x;
-        curTn.height = tnHeight;
-        curTn.width = tnWidth;
-        // image size
-        if( G.O.thumbnailAlignment == 'fillWidth' ) {
-          curTn.resizedContentWidth = contentWidth;
-          curTn.resizedContentHeight = contentHeight;
-        }
-        curTn.row = row;
-        lastPosY = curPosY;
-
-        curCol++;
-        if( curCol >= maxCol ){
-          // new line
-          curCol = 0;
-          curPosY += tnHeight + gutterHeight;
-          G.GOM.lastFullRow = row;
-          row++;
-        }
-      }
-      G.GOM.displayArea.width = w;
-
-      return true;
-    }
-
-
-
-
-    //----- Display the thumbnails according to the calculated layout
-    function GalleryDisplayPart1( forceTransition ) {
-      if( G.CSStransformName == null ) {
-        G.$E.conTn.css( 'left' , '0px');
-      }
-      else {
-        G.$E.conTn.css( G.CSStransformName , 'none');
-      }
-      // CacheViewport();
-    }
-    
-    function CacheViewport() {
-      G.GOM.cache.viewport = getViewport();
-      // G.GOM.cache.areaWidth = G.$E.conTnParent.width();
-      G.GOM.cache.areaWidth = G.$E.base.width();
-			
-			// position of the gallery container
-			// we use the position of the loadingbar because :
-			//    - the gallery may be wrong positioned due to one display animation currently running
-			//    - the loadingbar is never animated and positioned just before the gallery container
-      //G.GOM.cache.containerOffset = G.$E.conTnParent.offset();
-      if( !G.O.lightboxStandalone ) {
-        G.GOM.cache.containerOffset = G.$E.conLoadingB.offset();
-      }
-    }
-
-
-    
-    function GalleryDisplayPart2( forceTransition ) {
-      CacheViewport();
-
-      var nbTn = G.GOM.items.length;
-      G.GOM.itemsDisplayed = 0;
-      var threshold = 50;
-      var cnt = 0;    // counter for delay between each thumbnail display
-      
-
-      GalleryRenderGetInterval();
-      
-      for( var i = 0; i < nbTn ; i++ ) {
-        var curTn = G.GOM.items[i];
-        if( i >= G.GOM.displayInterval.from && cnt < G.GOM.displayInterval.len ) {
-          curTn.inDisplayArea = true;
-          if( forceTransition ) {
-            curTn.neverDisplayed = true;
-          }
-          G.GOM.itemsDisplayed++;
-          cnt++;
-        }
-        else{
-          curTn.inDisplayArea = false;
-        }
-      }
-
-      // bottom of the gallery (pagination, more button...)
-      GalleryBottomManage();
-
-      var tnToDisplay = [];
-      var tnToReDisplay = [];
-      
-      CacheViewport();
-      G.GOM.clipArea.top = -1;
-      cnt = 0 ;
-      var lastTnIdx = -1;
-      G.GOM.clipArea.height = 0;
-      // NOTE: loop always the whole GOM.items --> in case an already displayed thumbnail needs to be removed
-      for( var i = 0; i < nbTn ; i++ ) {
-        var curTn = G.GOM.items[i];
-        if( curTn.inDisplayArea ) {
-          if( G.GOM.clipArea.top == -1 ) {
-            G.GOM.clipArea.top = curTn.top;
-          }
-          if( (curTn.top - G.GOM.clipArea.top) <= -1 ) {
-            // with mosaic layout, the first thumbnail may not give the top position
-            G.GOM.clipArea.top = curTn.top;
-          }
-
-          G.GOM.clipArea.height = Math.max( G.GOM.clipArea.height, curTn.top-G.GOM.clipArea.top + curTn.height);
-        
-          if( curTn.neverDisplayed ) {
-						// thumbnail is not displayed -> check if in viewport to display or not
-            var top = G.GOM.cache.containerOffset.top + (curTn.top - G.GOM.clipArea.top);
-            // var left=containerOffset.left+curTn.left;
-            if( (top + curTn.height) >= (G.GOM.cache.viewport.t - threshold) && top <= (G.GOM.cache.viewport.t + G.GOM.cache.viewport.h + threshold) ) {
-              // build thumbnail
-              var item = G.I[curTn.thumbnailIdx];
-              if( item.$elt == null ) {
-                ThumbnailBuild( item, curTn.thumbnailIdx, i, (i+1) == nbTn );
-              }
-              tnToDisplay.push({idx:i, delay:cnt});
-              cnt++;
-            }
-          }
-          else {
-            tnToReDisplay.push({idx: i, delay: 0});
-          }
-          // G.GOM.itemsDisplayed++;
-          lastTnIdx = i;
-        }
-        else {
-          curTn.displayed = false;
-          var item = G.I[curTn.thumbnailIdx];
-          if( item.$elt != null ){
-            item.$elt.css({ opacity: 0, display: 'none' });
-          }
-        }
-      }
-
-      var areaWidth = G.$E.conTnParent.width();
-
-      // set gallery area really used size
-      // if( G.GOM.displayArea.width != G.GOM.displayAreaLast.width || G.GOM.displayArea.height != G.GOM.displayAreaLast.height ) {
-      if( G.GOM.displayArea.width != G.GOM.displayAreaLast.width || G.GOM.clipArea.height != G.GOM.displayAreaLast.height ) {
-        G.$E.conTn.width( G.GOM.displayArea.width ).height( G.GOM.clipArea.height );
-        G.GOM.displayAreaLast.width = G.GOM.displayArea.width;
-        G.GOM.displayAreaLast.height = G.GOM.clipArea.height;
-        // G.GOM.displayAreaLast.height=G.GOM.displayArea.height-G.GOM.clipArea.top;
-      }
-
-      if( areaWidth != G.$E.conTnParent.width() ) {
-        // gallery area width changed since layout calculation (for example when a scrollbar appeared)
-        // so we need re-calculate the layout before displaying the thumbnails
-        G.GOM.cache.areaWidth = G.$E.conTnParent.width();
-        GallerySetLayout();
-        GalleryDisplayPart1( forceTransition );
-        GalleryDisplayPart2( forceTransition );
-        return;
-      }
-
-      // counter of not displayed images (is displayed on the last thumbnail)
-      if( G.layout.support.rows ) {
-        if( G.galleryDisplayMode.Get() == 'ROWS' || (G.galleryDisplayMode.Get() == 'FULLCONTENT' && G.galleryLastRowFull.Get() && G.GOM.lastFullRow != -1) ){
-          if( lastTnIdx < (nbTn - 1) ) {
-            G.GOM.lastDisplayedIdxNew = lastTnIdx;
-          }
-          else {
-            G.GOM.lastDisplayedIdxNew =- 1;
-          }
-          // remove last displayed counter
-          if( G.GOM.lastDisplayedIdx != -1 ) {
-            var item = G.I[G.GOM.items[G.GOM.lastDisplayedIdx].thumbnailIdx];
-            item.$getElt('.nGY2GThumbnailIconsFullThumbnail').html('');
-          }
-        }
-      }
-
-      
-      // batch set position (and display animation) to all thumbnails
-      // first display newly built thumbnails
-			if( G.tn.opt.Get('displayOrder') == 'random' ) {
-				NGY2Tools.AreaShuffle( tnToDisplay );
-			}
-      var nbBuild = tnToDisplay.length;
-      G.GOM.thumbnails2Display=[];
-      for( var i = 0; i < nbBuild ; i++ ) {
-        // ThumbnailSetPosition(tnToDisplay[i].idx, tnToDisplay[i].delay+10);
-        ThumbnailSetPosition(tnToDisplay[i].idx, i);
-      }
-      
-      // then re-position already displayed thumbnails
-			if( G.tn.opt.Get('displayOrder') == 'random' ) {
-				NGY2Tools.AreaShuffle( tnToReDisplay );
-			}
-      var n = tnToReDisplay.length;
-      for( var i = 0; i < n ; i++ ) {
-        // ThumbnailSetPosition(tnToReDisplay[i].idx, nbBuild+1);
-        ThumbnailSetPosition(tnToReDisplay[i].idx, i);
-      }
-      
-      ThumbnailDisplayAnimBatch();
-
-      if( G.tn.opt.Get('displayTransition') == 'NONE' ) {
-        G.galleryResizeEventEnabled = true;
-        // GalleryThumbnailSliderBuildAndStart();  // image slider on last displayed thumbnail
-        TriggerCustomEvent('galleryDisplayed');
-      }
-      else {
-        // setTimeout(function() {
-        requestTimeout( function() {
-          // change value after the end of the display transistion of the newly built thumbnails
-          G.galleryResizeEventEnabled = true;
-          // GalleryThumbnailSliderBuildAndStart();  // image slider on last displayed thumbnail
-          TriggerCustomEvent('galleryDisplayed');
-        }, nbBuild * G.tn.opt.Get('displayInterval'));
-      }
-      
-    }
-    
-    
-    // Thumbnail: set the new position
-    function ThumbnailSetPosition( GOMidx, cnt ) {
-      var newTop=   0;
-      var curTn=    G.GOM.items[GOMidx];
-      var idx=      G.GOM.items[GOMidx].thumbnailIdx;
-      var item=     G.I[idx];
-    
-      if( curTn.neverDisplayed ) {
-        // thumbnail is built but has never been displayed (=first display)
-        var top = curTn.top - G.GOM.clipArea.top;
-        if( G.tn.opt.Get('stacks') > 0 ) {
-          // we have stacks -> do not display them here. They will be displayed at the end of the display animation
-          item.$elt.last().css({ display: 'block'});
-          item.$elt.css({ top: top , left: curTn.left });
-        }
-        else {
-          item.$elt.css({ display: 'block', top: top , left: curTn.left });
-        }
-        newTop=top;
-        
-        // display the image of the thumbnail when fully loaded
-        if( G.O.thumbnailWaitImageLoaded === true ) {
-          var gi_imgLoad = ngimagesLoaded( item.$getElt('.nGY2TnImg2') );
-          gi_imgLoad.on( 'progress', function( instance, image ) {
-            if( image.isLoaded ) {
-              var albumIdx = image.img.getAttribute('data-albumidx');
-              if( albumIdx == G.GOM.albumIdx ) {
-                // ignore event if not on current album
-                var idx = image.img.getAttribute('data-idx');
-                G.I[idx].ThumbnailImageReveal();
-              }
-            }
-          });
-        }
-        // display the thumbnail
-        ThumbnailAppear(GOMidx, cnt);
-
-        curTn.displayed = true;
-        curTn.neverDisplayed = false;
-      }
-      else {
-        var topOld = G.GOM.cache.containerOffset.top + item.top;
-        var top = G.GOM.cache.containerOffset.top + (curTn.top - G.GOM.clipArea.top);
-        newTop = curTn.top - G.GOM.clipArea.top;
-        var vp = G.GOM.cache.viewport;
-        if( G.O.thumbnailDisplayOutsideScreen || ( ( (topOld + curTn.height) >= (vp.t - vp.h) && topOld <= (vp.t + vp.h * 4) ) ||
-              ( (top + curTn.height) >= (vp.t - vp.h) && top <= (vp.t + vp.h * 4) ) )  ) {
-          // thumbnail positioned in enlarged viewport (viewport + 4 x viewport height) (v1.5: changed from 2 to 4)
-          if( curTn.displayed ) {
-            // thumbnail is displayed
-            if( item.top != curTn.top || item.left != curTn.left ) {
-              // set position
-              if( G.O.galleryResizeAnimation == true ) {
-                // with transition
-                var tweenable = new NGTweenable();
-                tweenable.tween({
-                  from:       { top: item.top, left: item.left,  height: item.height,  width: item.width },
-                  to:         { top: newTop,   left: curTn.left, height: curTn.height, width: curTn.width },
-                  attachment: { $e: item.$elt },
-                  duration:   100,
-                  delay:      cnt * G.tn.opt.Get('displayInterval') / 5,
-                  // easing:     'easeInOutQuad',
-                  easing:     'easeOutQuart',
-                  step:       function (state, att) {
-                    // window.ng_draf( function() {
-                      att.$e.css(state);
-                    // });
-                  },
-                  finish:     function (state, att) {
-                    var _this=this;
-                    // window.ng_draf( function() {
-                      _this.dispose();
-                    // });
-                  }
-                });
-              }
-              else {
-                // set position without transition
-                // item.$elt.css({ top: curTn.top , left: curTn.left });
-                item.$elt.css({ top: newTop , left: curTn.left });
-              }
-            }
-          }
-          else {
-            // re-display thumbnail
-            curTn.displayed = true;
-            // item.$elt.css({ display: 'block', top: curTn.top , left: curTn.left, opacity:1 });
-            item.$elt.css({ display: 'block', top: newTop, left: curTn.left, opacity: 1 });
-            ThumbnailAppearFinish(item);
-          }
-        }
-        else {
-          // undisplay thumbnail if not in viewport+margin --> performance gain
-          curTn.displayed = false;
-          item.$elt.css({ display: 'none'});
-        }
-      }
-      item.left = curTn.left;
-      item.top = newTop;
-      
-      // set new size if changed
-      if( item.width != curTn.width || item.height != curTn.height ) {
-        item.$elt.css({ width: curTn.width , height: curTn.height });
-        item.width = curTn.width;
-        item.height = curTn.height;
-        
-        // if( curTn.resizedContentWidth > 0 ) {
-        // resize also the content (=image)
-        if( item.resizedContentWidth != curTn.resizedContentWidth || item.resizedContentHeight != curTn.resizedContentHeight ) {
-          if( item.kind == 'albumUp' ) {
-            // item.$getElt('.nGY2GThumbnailAlbumUp').css({'height': curTn.resizedContentHeight, 'width': curTn.resizedContentWidth});
-          }
-          else {
-            item.$getElt('.nGY2GThumbnailImage').css({'height': curTn.resizedContentHeight, 'width': curTn.resizedContentWidth});
-
-            if( G.layout.engine == 'JUSTIFIED'  ) {
-              item.$getElt('.nGY2GThumbnailImg').css({'height': curTn.resizedContentHeight, 'width': curTn.resizedContentWidth});
-            }
-          }
-          item.resizedContentWidth = curTn.resizedContentWidth;
-          item.resizedContentHeight = curTn.resizedContentHeight;
-        }
-      }
-      
-      
-      // add counter of remaining (not displayed) images 
-      if( G.GOM.lastDisplayedIdxNew == GOMidx &&  G.layout.support.rows ) {
-        if( (G.galleryDisplayMode.Get() == 'ROWS' && G.galleryMaxRows.Get() > 0) || (G.galleryDisplayMode.Get() == 'FULLCONTENT' && G.galleryLastRowFull.Get() && G.GOM.lastFullRow != -1) ){
-          // number of items
-          var nb = G.GOM.items.length - GOMidx - 1;
-          if( item.albumID != '0' && G.O.thumbnailLevelUp ) {
-            nb--;
-          }
-
-          if( nb > 0 ) {
-            // display counter
-            if( G.O.thumbnailOpenInLightox || G.O.thumbnailSliderDelay > 0  ) {
-              item.$getElt('.nGY2GThumbnailIconsFullThumbnail').html( '+' + nb);
-            }
-
-            // if( G.layout.engine == 'GRID' && G.GOM.slider.hostItem != G.GOM.NGY2Item(GOMidx) ) {
-            // image slider on last displayed thumbnail
-            if( G.O.thumbnailLabel.get('position') != 'right' && G.O.thumbnailLabel.get('position') != 'left' ) {
-              if( G.GOM.slider.hostItem != G.GOM.NGY2Item(GOMidx) ) {
-
-                // set current slider back to initial content
-                GalleryThumbnailSliderSetContent( G.GOM.slider.hostItem );
-                // new slider
-                G.GOM.slider.hostIdx = GOMidx;
-                G.GOM.slider.hostItem = G.GOM.NGY2Item(GOMidx);
-                G.GOM.slider.nextIdx = GOMidx;
-                G.GOM.slider.currentIdx = GOMidx;
-                GalleryThumbnailSliderBuildAndStart();  // image slider on last displayed thumbnail
-                // GalleryThumbnailSliderSetNextContent();
-              }
-            }
-          }
-          else {
-            // reset slider content to initial content because all thumbnails are displayed
-            GalleryThumbnailSliderSetContent( G.GOM.slider.hostItem );
-            G.GOM.slider.hostIdx = -1;
-          }
-          
-          G.GOM.lastDisplayedIdx = GOMidx;
-        }
-      }
-
-    }
-    
-    // ---------------------
-    // replace image on last thumbnails with not displayed ones (mode ROWS or FULLCONTENT with galleryLastRowFull enabled)
-    // function GalleryLastThumbnailSlideImage() {
-    function GalleryThumbnailSliderBuildAndStart() {
-
-      if( G.O.thumbnailSliderDelay == 0 || G.GOM.slider.hostIdx == -1 ) {
-        return;
-      }
-      clearTimeout(G.GOM.slider.timerID);
-      
-      var item = G.GOM.slider.hostItem;
-
-      // dupplicate image layer -> for the next image
-      if( item.$getElt('.nGY2TnImgNext').length == 0 ) {
-        item.$getElt('.nGY2TnImg').clone().removeClass('nGY2TnImg').addClass('nGY2TnImgNext').insertAfter(item.$getElt('.nGY2TnImg'));
-        item.$getElt('.nGY2TnImgBack').clone().removeClass('nGY2TnImgBack').addClass('nGY2TnImgBackNext').insertAfter(item.$getElt('.nGY2TnImg', true));
-        item.$getElt('.nGY2GThumbnailImage', true); // important -> refresh the cache
-        item.$getElt('.nGY2GThumbnailImg', true);   // important -> refresh the cache
-      }
-
-      item.CSSTransformSet('.nGY2TnImgNext', 'translateX', '100%', true);
-      item.CSSTransformApply( '.nGY2TnImgNext' );
-      item.CSSTransformSet('.nGY2TnImgBackNext', 'translateX', '100%', true);
-      item.CSSTransformApply( '.nGY2TnImgBackNext' );
-
-      GalleryThumbnailSliderSetNextContent();
-      
-      // clearTimeout(G.GOM.slider.timerID);
-      // G.GOM.slider.timerID = setTimeout(function(){ GalleryThumbnailSliderStartTransition() }, G.O.thumbnailSliderDelay);
-      G.GOM.slider.timerID = requestTimeout(function(){ GalleryThumbnailSliderStartTransition() }, G.O.thumbnailSliderDelay);
-    }
-
-    
-    function GalleryThumbnailSliderSetNextContent() {
-
-      G.GOM.slider.nextIdx++;
-      if( G.GOM.slider.nextIdx >= G.GOM.items.length ) {
-        G.GOM.slider.nextIdx = G.GOM.slider.hostIdx;
-      }
-      
-      // new image
-      var newItem = G.GOM.NGY2Item(G.GOM.slider.nextIdx);
-      var imgBlurred = G.emptyGif;
-      var bgImg = "url('" + G.emptyGif + "')";
-      if( newItem.imageDominantColors != null ) {
-        imgBlurred = newItem.imageDominantColors;
-        bgImg = "url('" + newItem.imageDominantColors + "')";
-      }
-      G.GOM.slider.hostItem.$getElt('.nGY2TnImgBackNext', true).css({'background-image': bgImg, opacity: 1 });
-      G.GOM.slider.hostItem.$getElt('.nGY2TnImgNext', true).css({ 'background-image': "url('" + newItem.thumbImg().src + "')", opacity: 1 });
-      G.GOM.slider.hostItem.$getElt('.nGY2TnImgNext .nGY2GThumbnailImg', true).attr('src', newItem.thumbImg().src );
-      
-
-    }
-    
-    // thumbnail slider - transition from one image to the next one
-    function GalleryThumbnailSliderStartTransition() {
-      
-      if( G.GOM.slider.hostItem.$getElt() != null ) {
-
-        // slider transition
-        var tweenable = new NGTweenable();
-        G.GOM.slider.tween = tweenable;
-        tweenable.tween({
-          from:         { 'left': 100 },
-          to:           { 'left': 0 },
-          duration:     800,
-          delay:        0,
-          // easing:       'easeInOutQuad',
-          easing:       'easeOutQuart',
-          
-          step: function (state) {
-            if( G.GOM.slider.hostItem.$getElt() == null ) {
-              // the thumbnail may have been destroyed since the start of the animation
-              G.GOM.slider.tween.stop(false);
-              return;
-            }
-
-            // window.ng_draf( function() {
-              // slide current content
-              G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgBack', 'translateX', -(100 - state.left) + '%');
-              G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgBack' );
-              G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImg', 'translateX', -(100 - state.left) + '%');
-              G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImg' );
-
-              // slide new content
-              G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgBackNext', 'translateX', state.left + '%');
-              G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgBackNext' );
-              G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgNext', 'translateX', state.left + '%');
-              G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgNext' );
-            // });
-
-            
-          },
-          finish: function (state) {
-            if( G.GOM.slider.hostItem.$getElt() == null ) {
-              // the thumbnail may be destroyed since the start of the animation
-              return;
-            }
-           
-            if( G.GOM.NGY2Item(G.GOM.slider.nextIdx) == null ) { return; } // item does not exist anymore
-            
-            // window.ng_draf( function() {
-              // set new content as current content
-              GalleryThumbnailSliderSetContent( G.GOM.NGY2Item(G.GOM.slider.nextIdx) );
-              G.GOM.slider.currentIdx = G.GOM.slider.nextIdx;
-              GalleryThumbnailSliderSetNextContent();
-              
-              clearTimeout(G.GOM.slider.timerID);
-              // G.GOM.slider.timerID=setTimeout(function(){ GalleryThumbnailSliderStartTransition() }, G.O.thumbnailSliderDelay);
-              G.GOM.slider.timerID = requestTimeout(function(){ GalleryThumbnailSliderStartTransition() }, G.O.thumbnailSliderDelay);
-            // });
-          }
-        });
-      }
-    }
-    
-    // set main content of the thumbnail hosting the slider
-    // hide the elements for the next content of the slider
-    function GalleryThumbnailSliderSetContent( ngy2itemContent ) {
-        if( G.GOM.slider.hostIdx == -1 ) { return; }
-        
-        if( G.GOM.slider.tween != null ) {
-          if( G.GOM.slider.tween._isTweening  == true ) {
-            G.GOM.slider.tween.stop(false);
-          }
-        }
-
-        var bgImg = "url('" + G.emptyGif + "')";
-        if( ngy2itemContent.imageDominantColors != null ) {
-          bgImg = "url('" + ngy2itemContent.imageDominantColors + "')";
-        }
-        G.GOM.slider.hostItem.$getElt('.nGY2TnImgBack').css('background-image', bgImg);
-        G.GOM.slider.hostItem.$getElt('.nGY2TnImg').css('background-image', "url('" + ngy2itemContent.thumbImg().src + "')" );
-        G.GOM.slider.hostItem.$getElt('.nGY2TnImg .nGY2GThumbnailImg').attr('src', ngy2itemContent.thumbImg().src );
-        
-        G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgBack', 'translateX', '0');
-        G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgBack' );
-        G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImg', 'translateX', '0');
-        G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImg' );
-
-        // place the containers for the next image slider outside of the thumbnail (=hidden)
-        G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgBackNext', 'translateX', '100%', true);
-        G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgBackNext' );
-        G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgNext', 'translateX', '100%', true);
-        G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgNext' );
-
-        // set new title and description
-        if( G.O.thumbnailLabel.get('display') == true ) {
-          var icons = G.O.icons.thumbnailAlbum;
-          if( ngy2itemContent.kind != 'album' ) {
-            icons = G.O.icons.thumbnailImage;
-          }
-          G.GOM.slider.hostItem.$getElt('.nGY2GThumbnailTitle').html(icons + getThumbnailTitle(ngy2itemContent));
-          G.GOM.slider.hostItem.$getElt('.nGY2GThumbnailDescription').html(icons + getTumbnailDescription(ngy2itemContent));
-        }
-      }
-    
-   
-    
-    // Compute the height of the label part of a thumbnail (title+description, both single line)
-    function ThumbnailGetLabelHeight() {
-      var newElt = [],
-      newEltIdx =  0;
-
-      // if( G.O.thumbnailLabel.get('display') == false && G.tn.toolbar.getWidth(item) <= 0 ) {
-      if( G.O.thumbnailLabel.get('display') == false  ) {
-        return 0;
-      }
-      
-      var desc='';
-      if( G.O.thumbnailLabel.get('displayDescription') == true ) {
-        desc = 'aAzZjJ';
-      }
-
-      // visibility set to hidden
-      newElt[newEltIdx++] = '<div class="nGY2GThumbnail ' + G.O.theme + '" style="display:block;visibility:hidden;position:absolute;top:-9999px;left:-9999px;" ><div class="nGY2GThumbnailSub">';
-      if( G.O.thumbnailLabel.get('display') == true ) {
-        // Labels: title and description
-        newElt[newEltIdx++] = '  <div class="nGY2GThumbnailLabel" '+ G.tn.style.getLabel() +'>';
-        newElt[newEltIdx++] = '    <div class="nGY2GThumbnailAlbumTitle" '+G.tn.style.getTitle()+'>aAzZjJ</div>';
-        if( G.O.thumbnailLabel.get('displayDescription') == true ) {
-          newElt[newEltIdx++] = '    <div class="nGY2GThumbnailDescription" '+G.tn.style.getDesc()+'>'+'aAzZjJ'+'</div>';
-        }
-        newElt[newEltIdx++] = '  </div>';
-      }
-      
-      newElt[newEltIdx++] = '</div></div>';
-    
-      var $newDiv = jQuery(newElt.join('')).appendTo(G.$E.conTn);
-      var h = $newDiv.find('.nGY2GThumbnailLabel').outerHeight(true);
-      $newDiv.remove();
-
-      return h;
-    }
-    
-    function ThumbnailBuildStacks( bgColor ) {
-      var ns=G.tn.opt.Get('stacks');
-      if( ns == 0 ) { return ''; }
-     
-      var s='';
-      for( var i=0; i<ns; i++ ) {
-        s='<div class="nGY2GThumbnailStack " style="display:none;'+bgColor+'"></div>'+s;
-      }
-      return s;
-    }
-    
-    //----- Build one UP thumbnail (=navigation thumbnail)
-    function ThumbnailBuildAlbumpUp( item, idx, GOMidx ) {
-      var newElt = [],
-      newEltIdx = 0;
-      
-      var mp = '';
-      if( G.O.thumbnailOpenInLightox === false ) {
-        mp = 'cursor:default;'
-      }
-      
-      newElt[newEltIdx++] = ThumbnailBuildStacks('') + '<div class="nGY2GThumbnail" style="display:none;opacity:0;' + mp + '" >';
-      newElt[newEltIdx++] = '  <div class="nGY2GThumbnailSub">';
-
-      var h=G.tn.defaultSize.getHeight(),
-      w=G.tn.defaultSize.getWidth();
-
-      newElt[newEltIdx++] = '    <div class="nGY2GThumbnailImage" style="width:'+w+'px;height:'+h+'px;"><img class="nGY2GThumbnailImg" src="'+G.emptyGif+'" alt="" style="max-width:'+w+'px;max-height:'+h+'px;" ></div>';
-      // newElt[newEltIdx++] = '    <div class="nGY2GThumbnailAlbumUp" style="width:'+w+'px;height:'+h+'px;">'+G.O.icons.thumbnailAlbumUp+'</div>';
-      newElt[newEltIdx++] = '    <div class="nGY2GThumbnailAlbumUp" >'+G.O.icons.thumbnailAlbumUp+'</div>';
-      newElt[newEltIdx++] = '  </div>';
-      newElt[newEltIdx++] = '</div>';
-      
-      var $newDiv = jQuery(newElt.join('')).appendTo(G.$E.conTn); //.animate({ opacity: 1},1000, 'swing');  //.show('slow'); //.fadeIn('slow').slideDown('slow');
-      
-      item.$elt = $newDiv;
-      $newDiv.data('index', GOMidx);
-      item.$getElt('.nGY2GThumbnailImg').data('index', GOMidx);
-      
-      return;
-    }
-
-    
-    //----- Build one thumbnail
-    function ThumbnailBuild( item, idx, GOMidx, lastOne ) {
-      item.eltTransform =  [];
-      item.eltFilter =     [];
-      item.hoverInitDone = false;
-      item.$Elts =         [];
-
-      if( item.kind == 'albumUp' ) {
-        ThumbnailBuildAlbumpUp( item, idx, GOMidx);
-        return;
-      }
-
-      var newElt = [],
-      newEltIdx = 0;
-
-      var mp = '';
-      if( G.O.thumbnailOpenInLightox === false ) {
-        mp = 'cursor:default;'
-      }
-
-      // var src = encodeURI(item.thumbImg().src),
-      var src = (item.thumbImg().src).replace(/'/g, "%27"),   // replace single quote with %27
-      sTitle = getThumbnailTitle(item);
-
-      // image background -> visible during image download
-      var bg = '';
-      var bgImg = "background-image: url('" + G.emptyGif + "');";
-      if( item.imageDominantColors != null ) {
-        // dominant colorS (blurred preview image)
-        bgImg = "background-image: url('" + item.imageDominantColors + "');";
-      }
-      else {
-        // dominant color -> background color
-        if( item.imageDominantColor != null ) {
-          bg = 'background-color:' + item.imageDominantColor + ';';
-        }
-        else {
-          bgImg = '';
-        }
-      }
-
-      var op = 'opacity:1;';
-      if( G.O.thumbnailWaitImageLoaded == true ) {
-        op = 'opacity:0;';
-      }
-
-      // ##### thumbnail containers  (with stacks)
-      newElt[newEltIdx++] = ThumbnailBuildStacks(bg) + '<div class="nGY2GThumbnail nGY2GThumbnail_'+G.GOM.curNavLevel+'" style="display:none;opacity:0;' + mp + '"><div class="nGY2GThumbnailSub ' + ( G.O.thumbnailSelectable && item.selected ? "nGY2GThumbnailSubSelected" : "" ) + '">';
-
-      
-      // image size
-      var w = G.tn.settings.getW();
-      var h = G.tn.settings.getH();
-      if( G.tn.settings.getMosaic() !== null ) {
-        // mosaic layout -> 
-        w = G.GOM.items[GOMidx].width;
-        h = G.GOM.items[GOMidx].height;
-      }
-
-      var bgSize = 'contain';
-      if( G.tn.opt.Get('crop') ) {
-        bgSize = 'cover';             // thumbnail image will be cropped to fit in the thumbnail (no black border)
-      }
-      
-      // ##### layer for image background (color, dominant color, blurred preview)
-      var s1 = "position: absolute; top: 0px; left: 0px; width:" + w + "px; height:" + h + "px;"+ bg + bgImg + " background-position: center center;  background-repeat: no-repeat; background-size:" + bgSize + "; overflow: hidden;";
-      newElt[newEltIdx++]='<div class="nGY2GThumbnailImage nGY2TnImgBack" style="' + s1 + '"></div>';
-      
-      // #### layer for image 
-      var s2 = op + "position: absolute; top: 0px; left: 0px; width:" + w + "px; height:" + h + "px; background-image: url('" + src + "'); background-position: center center; background-repeat: no-repeat; background-size:" + bgSize + "; overflow: hidden;";
-      newElt[newEltIdx++]='<div class="nGY2GThumbnailImage nGY2TnImg" style="' + s2 + '">';
-      newElt[newEltIdx++]='  <img class="nGY2GThumbnailImg nGY2TnImg2" src="' + src + '" alt="' + sTitle + '" style="opacity:0;" data-idx="' + idx + '" data-albumidx="' + G.GOM.albumIdx + '" >';
-      newElt[newEltIdx++]='</div>';
-      
-      // ##### layer for user customization purposes
-      newElt[newEltIdx++]='<div class="nGY2GThumbnailCustomLayer"></div>';
-
-      // ##### layer for labels (title + description and their icons)
-      if( G.O.thumbnailLabel.get('display') == true ) {
-        // Labels: title and description
-        newElt[newEltIdx++]= '  <div class="nGY2GThumbnailLabel" '+ G.tn.style.getLabel(item) + '>';
-        if( item.kind == 'album' ) {
-          // album kind
-          newElt[newEltIdx++]= '    <div class="nGY2GThumbnailTitle nGY2GThumbnailAlbumTitle" ' + G.tn.style.getTitle() + '>' + G.O.icons.thumbnailAlbum + sTitle + '</div>';
-        }
-        else {
-          // image/media kind
-          newElt[newEltIdx++]= '    <div class="nGY2GThumbnailTitle nGY2GThumbnailImageTitle" ' + G.tn.style.getTitle() + '>' + G.O.icons.thumbnailImage + sTitle + '</div>';
-        }
-        newElt[newEltIdx++]= '    <div class="nGY2GThumbnailDescription" ' + G.tn.style.getDesc() + '>' + getTumbnailDescription(item) + '</div>';
-        newElt[newEltIdx++]= '  </div>';
-      }
-
-      // ##### layer for tools
-      newElt[newEltIdx++] = ThumbnailBuildTools(item, lastOne);
-      
-      // close containers
-      newElt[newEltIdx++]='</div></div>';
-      
-      var $newDiv =jQuery(newElt.join('')).appendTo(G.$E.conTn);
-
-      item.$elt=$newDiv;
-      $newDiv.data('index',GOMidx);
-      item.$getElt('.nGY2GThumbnailImg').data('index',GOMidx);
-      
-      // Custom init function
-      var fu=G.O.fnThumbnailInit;
-      if( fu !== null ) {
-        typeof fu == 'function' ? fu($newDiv, item, GOMidx) : window[fu]($newDiv, item, GOMidx);
-      }
-
-      if( item.title != 'image gallery by nanogallery2 [build]' ) {
-        ThumbnailOverInit(GOMidx);
-      }
-      
-      return ;
-    }
-
-    
-    // Thumbnail layer for tools (toolbars and counter)
-    function ThumbnailBuildTools( item, lastThumbnail ) {
-    
-      // toolbars
-      var tb = ThumbnailBuildToolbarOne(item, 'topLeft') + ThumbnailBuildToolbarOne(item, 'topRight') + ThumbnailBuildToolbarOne(item, 'bottomLeft') + ThumbnailBuildToolbarOne(item, 'bottomRight');
-      
-      // counter of not displayed images
-      tb += '<div class="nGY2GThumbnailIconsFullThumbnail"></div>';
-
-      return tb;
-    }
-    
-    function ThumbnailBuildToolbarOne( item, position ) {
-      var toolbar = '';
-      var tb =      G.tn.toolbar.get(item);
-      var width =   { xs:0, sm:1, me:2, la:3, xl:4 };
-      var cnt =     0;
-      
-      if( tb[position] != '' ) {
-        var pos='top: 0; right: 0; text-align: right;';     // 'topRight' and default
-        switch( position ) {
-          case 'topLeft':
-            pos = 'top: 0; left: 0; text-align: left;';
-            break;
-          case 'bottomRight':
-            pos = 'bottom: 0; right: 0; text-align: right;';
-            break;
-          case 'bottomLeft':
-            pos = 'bottom: 0; left: 0; text-align: left;';
-            break;
-        }
-        
-        toolbar += '  <ul class="nGY2GThumbnailIcons" style="' + pos + '">';
-        
-        var icons = tb[position].split(',');
-        var nb = icons.length;
-        for( var i = 0; i < nb; i++ ) {
-          var icon = icons[i].replace(/^\s*|\s*$/, '');   //trim trailing/leading whitespace
-
-          var minWidth = icon.substring(0,2).toLowerCase();
-          var tIcon = icon;
-          var display = true;
-          if( /xs|sm|me|la|xl/i.test(minWidth) ) {
-            // check visbility (depending on screen width)
-            if( width[minWidth] > width[G.GOM.curWidth] ) {
-              display = false;
-            }
-            tIcon = icon.substring(2);
-          }
-          
-          if( display ) {
-            var sp=(i+1<nb ? '&nbsp;' :'');
-            switch( tIcon ) {
-              case 'COUNTER':
-                if( item.kind == 'album' ) {
-                  toolbar += '    <li class="nGY2GThumbnailIcon" data-ngy2action="">';
-                  toolbar += '      <div class="nGY2GThumbnailIconImageCounter"></div>';
-                  toolbar += '      <div class="nGY2GThumbnailIconText">' + G.O.icons.thumbnailCounter+Math.max((item.getContentLength(false)),item.numberItems) + sp + '</div>';
-                  toolbar += '    </li>';
-                  cnt++;
-                }
-                break;
-              case 'COUNTER2':
-                if( item.kind == 'album' ) {
-                  toolbar += '    <li class="nGY2GThumbnailIcon" data-ngy2action="">';
-                  toolbar += '      <div class="nGY2GThumbnailIconTextBadge">' + G.O.icons.thumbnailCounter+Math.max((item.getContentLength(false)),item.numberItems) + sp + '</div>';
-                  toolbar += '    </li>';
-                  cnt++;
-                }
-                break;
-              case 'SHARE':
-                toolbar += '    <li class="nGY2GThumbnailIcon" data-ngy2action="' + tIcon + '">';
-                toolbar += '      <div>' + G.O.icons.thumbnailShare + '</div>';
-                toolbar += '    </li>';
-                cnt++;
-                break;
-              case 'DOWNLOAD':
-                toolbar += '    <li class="nGY2GThumbnailIcon" data-ngy2action="' + tIcon + '">';
-                toolbar += '      <div>' + G.O.icons.thumbnailDownload + '</div>';
-                toolbar += '    </li>';
-                cnt++;
-                break;
-              case 'INFO':
-                toolbar += '    <li class="nGY2GThumbnailIcon" data-ngy2action="' + tIcon + '">';
-                toolbar += '      <div>' + G.O.icons.thumbnailInfo + '</div>';
-                toolbar += '    </li>';
-                cnt++;
-                break;
-              case 'SHOPPINGCART':
-                toolbar += '    <li class="nGY2GThumbnailIcon" data-ngy2action="' + tIcon + '">';
-                // toolbar += '      <div>' + G.O.icons.thumbnailShoppingcart + '</div>';
-                toolbar += ThumbnailBuildToolbarOneCart( item );
-                
-                toolbar += '    </li>';
-                cnt++;
-                break;
-              case 'DISPLAY':
-                toolbar += '    <li class="nGY2GThumbnailIcon" data-ngy2action="DISPLAY">';
-                toolbar += '      <div class="nGY2GThumbnailIconImageShare">' + G.O.icons.thumbnailDisplay + '</div>';
-                toolbar += '    </li>';
-                cnt++;
-                break;
-              case 'CUSTOM1':
-              case 'CUSTOM2':
-              case 'CUSTOM3':
-              case 'CUSTOM4':
-              case 'CUSTOM5':
-              case 'CUSTOM6':
-              case 'CUSTOM7':
-              case 'CUSTOM8':
-              case 'CUSTOM9':
-              case 'CUSTOM10':
-                var cust = tIcon.replace('CUSTOM', '');
-                toolbar += '    <li class="nGY2GThumbnailIcon" data-ngy2action="' + tIcon.toLowerCase() + '">';
-                toolbar += '      <div class="nGY2GThumbnailIconImageShare">' + G.O.icons['thumbnailCustomTool' + cust] + '</div>';
-                toolbar += '    </li>';
-                cnt++;
-                break;
-              case 'FEATURED':
-                if( item.featured === true ) {
-                  toolbar += '    <li class="nGY2GThumbnailIcon" data-ngy2action="">';
-                  toolbar += '      <div class="nGY2GThumbnailIconImageFeatured">' + G.O.icons.thumbnailFeatured + '</div>';
-                  toolbar += '    </li>';
-                  cnt++;
-                }
-                break;
-              case 'SELECT':
-                if( G.O.thumbnailSelectable == true ) {
-                  toolbar += '    <li class="nGY2GThumbnailIcon" data-ngy2action="TOGGLESELECT">';
-                  if( item.selected === true ) {
-                    toolbar += '      <div class="nGY2GThumbnailIconImageSelect nGY2ThumbnailSelected">' + G.O.icons.thumbnailSelected + '</div>';
-                  }
-                  else {
-                    toolbar += '      <div class="nGY2GThumbnailIconImageSelect nGY2ThumbnailUnselected">' + G.O.icons.thumbnailUnselected + '</div>';
-                  }
-                  toolbar += '    </li>';
-                  cnt++;
-                }
-                break;
-            }
-          }
-        }
-        toolbar += '  </ul>';
-      }
-      
-      if( cnt > 0 ) {
-        return toolbar;
-      }
-      else {
-        return '';
-      }
-    }
-    
-    // CART ICON AND COUNTER
-    function ThumbnailBuildToolbarOneCart( item ) {
-      var q = 0;
-      
-      var id = item.GetID()
-      for( var i=0; i<G.shoppingCart.length; i++ ) {
-        if( G.I[G.shoppingCart[i].idx].GetID() == id ) {
-          q = G.shoppingCart[i].qty;
-        }
-      }
-      if( q == 0 ) {
-        q = '';
-      }
-
-      return '      <div>' + G.O.icons.thumbnailShoppingcart + q + '</div>';
-    }
-    function ThumbnailBuildToolbarOneCartUpdate( item ) {
-      var $e = item.$elt;
-
-      if( $e != null ) {
-        var $q = $e.find('*[data-ngy2action="SHOPPINGCART"]');
-        if( $q !== undefined ) {
-          $q.html( ThumbnailBuildToolbarOneCart( item ) );
-        }
-      }
-    }
-    
-    function getThumbnailTitle( item ) {
-    
-      var sTitle = item.title;
-      if( G.O.thumbnailLabel.get('display') == true ) {
-        if( sTitle === undefined || sTitle.length == 0 ) { sTitle = '&nbsp;'; }
-
-        if( G.i18nTranslations.thumbnailImageTitle != '' ) {
-          sTitle = G.i18nTranslations.thumbnailImageTitle;
-        }
-        var ml = G.O.thumbnailLabel.get('titleMaxLength');
-        if( ml > 3 && sTitle.length > ml ){
-          sTitle = sTitle.substring(0, ml) + '...';
-        }
-      }
-      
-      return sTitle;
-    }
-
-    function getTumbnailDescription( item ) {
-      var sDesc = '';
-      if( G.O.thumbnailLabel.get('displayDescription') == true ) { 
-        if( item.kind == 'album' ) {
-          if( G.i18nTranslations.thumbnailImageDescription != '' ) {
-            sDesc = G.i18nTranslations.thumbnailAlbumDescription;
-          }
-          else {
-            sDesc = item.description;
-          }
-        }
-        else {
-          if( G.i18nTranslations.thumbnailImageDescription != '' ) {
-            sDesc = G.i18nTranslations.thumbnailImageDescription;
-          }
-          else {
-            sDesc = item.description;
-          }
-        }
-        var ml = G.O.thumbnailLabel.get('descriptionMaxLength');
-        if( ml > 3 && sDesc.length > ml ){
-          sDesc = sDesc.substring(0, ml) + '...';
-        }
-        if( sDesc.length == 0 ) {
-          sDesc = '&nbsp;';
-        }
-      }
-      
-      return sDesc;
-    }
-
-    
-    
-    // Retrieve the maximum number of thumbnails that fits in one row
-    function NbThumbnailsPerRow( areaWidth ) {
-      var tnW = G.tn.defaultSize.getOuterWidth();
-      
-      var nbMaxTn = 0;
-      if( G.O.thumbnailAlignment == 'justified' ) {
-        nbMaxTn = Math.floor((areaWidth)/(tnW));
-      }
-      else {
-        nbMaxTn = Math.floor((areaWidth + G.tn.settings.GetResponsive('gutterWidth'))/(tnW + G.tn.settings.GetResponsive('gutterWidth')));
-      }
-      
-      if(  G.O.maxItemsPerLine >0 && nbMaxTn >  G.O.maxItemsPerLine ) {
-        nbMaxTn = G.O.maxItemsPerLine;
-      }
-      
-      if( nbMaxTn < 1 ) { nbMaxTn = 1; }
-      
-      return nbMaxTn
-    }
-  
-    // Thumbnail display animation
-    function ThumbnailAppear( n, cnt ) {
-      var curTn = G.GOM.items[n];
-      var item = G.I[curTn.thumbnailIdx];
-
-    
-      if( G.tn.opt.Get('displayTransition') == 'NONE' ) {
-        item.$elt.css({ opacity: 1 });
-        ThumbnailAppearFinish( item );
-      }
-      else {
-        if( item.$elt == null ) { return; }
-        var top = G.GOM.cache.containerOffset.top + ( curTn.top - G.GOM.clipArea.top );
-        var vp = G.GOM.cache.viewport;
-        if( (top + (curTn.top - G.GOM.clipArea.top)) >= (vp.t - 50) && top <= (vp.t + vp.h + 50) ) {
-          // display animation only if in the current viewport
-          var delay = cnt * G.tn.opt.Get('displayInterval');
-          if( G.tn.opt.Get('displayTransition') == 'CUSTOM' ) {
-            if( G.GOM.curNavLevel == 'lN' ) {
-              G.O.fnThumbnailDisplayEffect(item.$elt, item, n, delay);
-            }
-            else {
-              G.O.fnThumbnailL1DisplayEffect(item.$elt, item, n, delay);
-            }
-          }
-          else {
-            G.GOM.thumbnails2Display.push({itm: item, d: delay});
-            // ThumbnailDisplayAnim2(item, delay);
-          }
-          return;
-        }
-        else {
-          item.$elt.css({ opacity: 1 });
-          ThumbnailAppearFinish(item);
-        }
-      }
-    }
-    
-    
-    // displays thumbnail stacks at the end of the display animation
-    function ThumbnailAppearFinish( item ) {
-    
-      // add stacks
-      var ns = G.tn.opt.Get('stacks');
-      if( ns > 0 ) {
-        // display stacks
-        item.$elt.css({ display: 'block'});
-        var o = 0.9;
-        // set stack opacity
-        for( var i = ns-1; i>=0; i-- ) {
-          item.$elt.eq(i).css('opacity', o);
-          o = o - 0.2;
-        }
-
-      }
-    }
-    
-
-    function ThumbnailDisplayAnim2( item, delay ) {
-      function randomIntFromInterval(min,max) {
-        return Math.floor(Math.random()*(max-min+1)+min);
-      }
-      var oFrom = {};
-      var oTo = {};
-    
-      switch (G.tn.opt.Get('displayTransition')) {
-        case 'RANDOMSCALE':
-          var scales = [0.95, 1, 1.05, 1.1];
-          var zi = [1, 2, 3, 4];
-          
-          var r = randomIntFromInterval(0,3);
-          while( r == G.GOM.lastRandomValue ) {
-            r = randomIntFromInterval(0,3);
-          }
-          G.GOM.lastRandomValue = r;
-          var f = scales[r];
-          // item.$elt.css({ 'z-index': G.GOM.lastZIndex+zi[r], 'box-shadow': '-1px 2px 5px 1px rgba(0, 0, 0, 0.7)' });
-          item.$elt.css({ 'z-index': G.GOM.lastZIndex+zi[r], 'box-shadow': '0px 0px 5px 3px rgba(0,0,0,0.74)' });
-          
-          oFrom = { scale: 0.5, opacity:0 };
-          oTo =   { scale: f,   opacity:1 };
-          break;
-
-        case 'SCALEUP':
-          var f = G.tn.opt.Get('displayTransitionStartVal');
-          if( f == 0 ) { f = 0.6; }   // default value
-          oFrom = { scale: f, opacity: 0 };
-          oTo =   { scale: 1, opacity: 1 };
-          break;
-
-        case 'SCALEDOWN':
-          var f = G.tn.opt.Get('displayTransitionStartVal');
-          if( f == 0 ) { f=1.3; }   // default value
-          oFrom = { scale: f, opacity: 0 };
-          oTo =   { scale: 1, opacity: 1 };
-          break;
-        case 'SLIDEUP':
-          var f = G.tn.opt.Get('displayTransitionStartVal');
-          if( f == 0 ) { f=50; }   // default value
-          oFrom = { opacity: 0, translateY: f };
-          oTo =   { opacity: 1, translateY: 0 };
-          break;
-        case 'SLIDEDOWN':
-          var f=G.tn.opt.Get('displayTransitionStartVal');
-          if( f == 0 ) { f=-50; }   // default value
-          oFrom = { opacity: 0, translateY: f };
-          oTo =   { opacity: 1, translateY: 0 };
-          break;
-        case 'FLIPUP':
-          var f=G.tn.opt.Get('displayTransitionStartVal');
-          if( f == 0 ) { f=100; }   // default value
-          oFrom = { opacity: 0, translateY: f, rotateX: 45 };
-          oTo =   { opacity: 1, translateY: 0, rotateX: 0  };
-          break;
-          
-        case 'FLIPDOWN':
-          var f=G.tn.opt.Get('displayTransitionStartVal');
-          if( f == 0 ) { f=-100; }   // default value
-          oFrom = { opacity: 0, translateY: f, rotateX: -45 };
-          oTo =   { opacity: 1, translateY: 0, rotateX: 0 };
-          break;
-        case 'SLIDEUP2':
-          var f = G.tn.opt.Get('displayTransitionStartVal');
-          if( f == 0 ) { f=100; }   // default value
-          oFrom = { opacity: 0, translateY: f, rotateY: 40 };
-          oTo =   { opacity: 1, translateY: 0, rotateY: 0  };
-          break;
-        case 'IMAGESLIDEUP':
-          var f = G.tn.opt.Get('displayTransitionStartVal');
-          if( f == 0 ) { f=100; }   // default value
-          oFrom = { opacity: 0, top: '100%' };
-          oTo =   { opacity: 1, top: '0%'  };
-          break;
-        case 'SLIDEDOWN2':
-          var f=G.tn.opt.Get('displayTransitionStartVal');
-          if( f == 0 ) { f=-100; }   // default value
-          oFrom = { opacity: 0, translateY: f, rotateY: 40 };
-          oTo =   { opacity: 1, translateY: 0, rotateY: 0  };
-          break;
-        case 'SLIDERIGHT':
-          var f=G.tn.opt.Get('displayTransitionStartVal');
-          if( f == 0 ) { f=-150; }   // default value
-          oFrom = { opacity: 0, translateX: f };
-          oTo =   { opacity: 1, translateX: 0 };
-          break;
-
-        case 'SLIDELEFT':
-          var f=G.tn.opt.Get('displayTransitionStartVal');
-          if( f == 0 ) { f=150; }   // default value
-          oFrom = { opacity: 0, translateX: f };
-          oTo =   { opacity: 1, translateX: 0 };
-          break;
-          
-        case 'FADEIN':
-          oFrom = { opacity: 0 };
-          oTo =   { opacity: 1 };
-          break;
-          
-          
-      }
-    
-      var tweenable = new NGTweenable();
-      tweenable.tween({
-        from:         oFrom,
-        to:           oTo,
-        attachment:   { $e:item.$elt, item: item, tw: tweenable },
-        delay:        delay,
-        duration:     G.tn.opt.Get('displayTransitionDuration'),
-        easing:       G.tn.opt.Get('displayTransitionEasing'),
-        step:         function (state, att) {
-          window.requestAnimationFrame( function() {
-            if( att.item.$elt === null ) {  // the thumbnail may have been destroyed since the start of the animation
-              att.tw.stop(false);
-              return;
-            }
-            switch (G.tn.opt.Get('displayTransition')) {
-              case 'RANDOMSCALE':
-                att.$e.css( G.CSStransformName , 'scale(' + state.scale + ')').css('opacity', state.opacity);
-                break;
-              case 'SCALEUP':
-                att.$e.css( G.CSStransformName , 'scale('+state.scale+')').css('opacity',state.opacity);
-                break;
-              case 'SCALEDOWN':
-                att.item.$elt.last().css('opacity', state.opacity);
-                att.item.CSSTransformSet('.nGY2GThumbnail', 'scale', state.scale);
-                att.item.CSSTransformApply('.nGY2GThumbnail');
-                break;
-              case 'SLIDEUP':
-                att.item.$elt.css('opacity', state.opacity);
-                att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px, '+state.translateY + 'px');
-                att.item.CSSTransformApply('.nGY2GThumbnail');
-                break;
-              case 'SLIDEDOWN':
-                att.item.$elt.css('opacity', state.opacity);
-                att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px,'+state.translateY+'px');
-                att.item.CSSTransformApply('.nGY2GThumbnail');
-                break;
-              case 'FLIPUP':
-                att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px,'+state.translateY+'px');
-                att.item.CSSTransformSet('.nGY2GThumbnail', 'rotateX', state.rotateX+'deg');
-                att.item.$elt.css('opacity', state.opacity);
-                att.item.CSSTransformApply('.nGY2GThumbnail');
-                break;
-              case 'FLIPDOWN':
-                att.item.$elt.css('opacity', state.opacity);
-                att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px,' + state.translateY + 'px');
-                att.item.CSSTransformSet('.nGY2GThumbnail', 'rotateX', state.rotateX + 'deg');
-                att.item.CSSTransformApply('.nGY2GThumbnail');
-                break;
-              case 'SLIDEUP2':
-                att.item.$elt.css('opacity', state.opacity);
-                att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px,' + state.translateY + 'px');
-                att.item.CSSTransformSet('.nGY2GThumbnail', 'rotateY', state.rotateY + 'deg');
-                att.item.CSSTransformApply('.nGY2GThumbnail');
-                break;
-              case 'IMAGESLIDEUP':
-                att.item.$elt.css('opacity', state.opacity);
-								att.item.$Elts['.nGY2GThumbnailImage'].css('top', state.top);
-                break;
-              case 'SLIDEDOWN2':
-                att.item.$elt.css('opacity', state.opacity);
-                att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px, ' + state.translateY + 'px');
-                att.item.CSSTransformSet('.nGY2GThumbnail', 'rotateY', state.rotateY + 'deg');
-                att.item.CSSTransformApply('.nGY2GThumbnail');
-                break;
-              case 'SLIDERIGHT':
-                att.item.$elt.css('opacity', state.opacity);
-                att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', state.translateX + 'px, 0px');
-                att.item.CSSTransformApply('.nGY2GThumbnail');
-                break;
-              case 'SLIDELEFT':
-                att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', state.translateX + 'px, 0px');
-                att.item.$elt.css('opacity', state.opacity);
-                att.item.CSSTransformApply('.nGY2GThumbnail');
-                break;
-              case 'FADEIN':
-                att.$e.css(state);
-                break;
-            }
-          });
-          // att.$e.css( G.CSStransformName , 'scale('+state.scale+')').css('opacity',state.opacity);
-        },
-        finish:       function (state, att) {
-          window.requestAnimationFrame( function() {
-            if( att.item.$elt === null ) { return; }
-            
-            switch (G.tn.opt.Get('displayTransition')) {
-              case 'RANDOMSCALE':
-                att.$e.css( G.CSStransformName , 'scale('+state.scale+')').css('opacity', '');
-                break;
-              case 'SCALEUP':
-                att.$e.css( G.CSStransformName , '').css('opacity', '');
-                break;
-              case 'SCALEDOWN':
-                att.item.$elt.last().css('opacity', '');
-                att.item.CSSTransformSet('.nGY2GThumbnail', 'scale', state.scale);
-                att.item.CSSTransformApply('.nGY2GThumbnail');
-                break;
-              case 'IMAGESLIDEUP':
-                att.item.$elt.css('opacity', '');
-								att.item.$Elts['.nGY2GThumbnailImage'].css('top', 0);
-                break;
-              case 'SLIDEDOWN2':
-                att.item.$elt.css('opacity', '');
-                att.item.CSSTransformApply('.nGY2GThumbnail');
-                break;
-							default :
-								// case 'SLIDEUP':
-								// case 'SLIDEDOWN':
-								// case 'FLIPUP':
-								// case 'FLIPDOWN':
-								// case 'SLIDEUP2':
-								// case 'SLIDERIGHT':
-								// case 'SLIDELEFT':
-								// case 'FADEIN':
-                att.item.$elt.css('opacity', '');
-            }
-            ThumbnailAppearFinish(att.item);
-          });
-
-        }
-      });
-    
-    }
-    
-    // batch display thumbnails with animation
-    function ThumbnailDisplayAnimBatch() {
-      
-      G.GOM.thumbnails2Display.forEach( function(one) {
-        ThumbnailDisplayAnim2(one.itm, one.d);
-      });
-      G.GOM.thumbnails2Display=[];
-    }
-    
-    
-
-    // ######################################
-    // Gallery display animation
-    function GalleryAppear() {
-      
-      var d=G.galleryDisplayTransitionDuration.Get();
-      switch( G.galleryDisplayTransition.Get() ){
-        case 'ROTATEX':
-          G.$E.base.css({ perspective: '1000px', 'perspective-origin': '50% 0%' });
-          var tweenable = new NGTweenable();
-          tweenable.tween({
-            from:         { r: 50 },
-            to:           { r: 0  },
-            attachment:   { orgIdx: G.GOM.albumIdx },
-            duration:     d,
-            easing:       'easeOutCirc',
-            step:         function (state, att) {
-              if( att.orgIdx == G.GOM.albumIdx ) {
-                // window.ng_draf( function() {
-                  G.$E.conTnParent.css( G.CSStransformName , 'rotateX(' + state.r + 'deg)');
-                // });
-              }
-            }
-          });
-          break;
-        case 'SLIDEUP':
-          G.$E.conTnParent.css({ opacity: 0 });
-          var tweenable = new NGTweenable();
-          tweenable.tween({
-            from:         { y: 200, o: 0 },
-            to:           { y: 0,   o: 1 },
-            attachment:   { orgIdx: G.GOM.albumIdx },
-            duration:     d,
-            easing:       'easeOutCirc',
-            step:         function (state, att) {
-              if( att.orgIdx == G.GOM.albumIdx ) {
-                // window.ng_draf( function() {
-                  G.$E.conTnParent.css( G.CSStransformName , 'translate( 0px, '+state.y + 'px)').css('opacity', state.o);
-                // });
-              }
-            }
-          });
-          break;
-        case 'NONE':
-        default:
-          break;
-      }
-
-
-    }
-    
-    // ######################################
-    // ##### THUMBNAIL HOVER MANAGEMENT #####
-    // ######################################
-
-    function ThumbnailOverInit( GOMidx ) {
-      // Over init in 2 step:
-      // 1) init with thumbnailBuildInit2 parameter
-      // 2) init with the hover effect parameter
-      
-      
-      var curTn = G.GOM.items[GOMidx];
-      var item = G.I[curTn.thumbnailIdx];
-
-      if( item.$elt == null ) { return; } // zombie
-      
-      var fu = G.O.fnThumbnailHoverInit;
-      if( fu !== null ) {
-        typeof fu == 'function' ? fu($e, item, GOMidx) : window[fu]($e, item, GOMidx);
-      }
-
-      // build initialization
-      var inits = G.tn.buildInit.get();
-      for( var j = 0; j < inits.length; j++) {
-        switch( inits[j].property ) {
-          // CSS Transform
-          case 'scale':
-          case 'rotateX':
-          case 'rotateY':
-          case 'rotateZ':
-          case 'translateX':
-          case 'translateY':
-          case 'translateZ':
-            item.CSSTransformSet(inits[j].element, inits[j].property, inits[j].value);
-            item.CSSTransformApply(inits[j].element);
-            break;
-          // CSS filter
-          case 'blur':
-          case 'brightness':
-          case 'grayscale':
-          case 'sepia':
-          case 'contrast':
-          case 'opacity':
-          case 'saturate':
-            item.CSSFilterSet(inits[j].element, inits[j].property, inits[j].value);
-            item.CSSFilterApply(inits[j].element);
-            break;
-          default:
-            var $t=item.$getElt(inits[j].element);
-            $t.css( inits[j].property, inits[j].value );
-            break;
-        }
-      }
-      
-      // hover
-      var effects = G.tn.hoverEffects.get();
-      for( var j = 0; j < effects.length; j++) {
-        if( effects[j].firstKeyframe === true ) {
-          switch( effects[j].type ) {
-            case 'scale':
-            case 'rotateX':
-            case 'rotateY':
-            case 'rotateZ':
-            case 'translateX':
-            case 'translateY':
-            case 'translateZ':
-              item.CSSTransformSet(effects[j].element, effects[j].type, effects[j].from);
-              item.CSSTransformApply(effects[j].element);
-              break;
-            case 'blur':
-            case 'brightness':
-            case 'grayscale':
-            case 'sepia':
-            case 'contrast':
-            case 'opacity':
-            case 'saturate':
-              item.CSSFilterSet(effects[j].element, effects[j].type, effects[j].from);
-              item.CSSFilterApply(effects[j].element);
-              break;
-            default:
-              var $t = item.$getElt(effects[j].element);
-              $t.css( effects[j].type, effects[j].from );
-              break;
-              
-          }
-        }
-      }
-      item.hoverInitDone=true;
-    }
-
-    function ThumbnailHoverReInitAll() {
-      if( G.GOM.albumIdx == -1 ) { return; };
-      var l = G.GOM.items.length;
-      for( var i = 0; i < l ; i++ ) {
-        ThumbnailOverInit(i);
-        // G.GOM.items[i].hovered=false;
-        G.I[G.GOM.items[i].thumbnailIdx].hovered = false;
-      }
-    }
-
-
-    function ThumbnailHover( GOMidx ) {
-      if( G.GOM.albumIdx == -1 || !G.galleryResizeEventEnabled ) { return; };
-      if( G.GOM.slider.hostIdx == GOMidx ) {
-        // slider hosted on thumbnail -> no hover effect
-        return;
-      }
-      var curTn = G.GOM.items[GOMidx];
-      var item = G.I[curTn.thumbnailIdx];
-      if( item.kind == 'albumUp' || item.$elt == null ) { return; }
-
-      item.hovered = true;
-
-      var fu = G.O.fnThumbnailHover;
-      if( fu !== null ) {
-        typeof fu == 'function' ? fu(item.$elt, item, GOMidx) : window[fu](item.$elt, item, GOMidx);
-      }
-      var effects = G.tn.hoverEffects.get();
-
-      try {
-        for( var j = 0; j < effects.length; j++) {
-          if( effects[j].hoverin === true ) {
-            //item.animate( effects[j], j*10,  true );
-            item.animate( effects[j], 0,  true );
-          }
-        }
-        // effects on whole layout
-        // GalleryResize( GOMidx );
-      }
-      catch (e) { 
-        NanoAlert(G, 'error on hover: ' + e.message );
-      }
-
-    }
-
-    function ThumbnailHoverOutAll() {
-      if( G.GOM.albumIdx == -1 ) { return; };
-      var l = G.GOM.items.length;
-      for( var i = 0; i < l ; i++ ) {
-        if( G.GOM.items[i].inDisplayArea ) {
-          ThumbnailHoverOut(i);
-        }
-        else {
-          G.I[G.GOM.items[i].thumbnailIdx].hovered = false;
-        }
-      }
-    }
-
-    
-    function ThumbnailHoverOut( GOMidx ) {
-      if( G.GOM.albumIdx == -1 || !G.galleryResizeEventEnabled ) { return; }
-
-      if( G.GOM.slider.hostIdx == GOMidx ) {
-        // slider on thumbnail -> no hover effect
-        return;
-      }
-
-      var curTn = G.GOM.items[GOMidx];
-      var item = G.I[curTn.thumbnailIdx];
-      if( item.kind == 'albumUp' || !item.hovered ) { return; }
-      item.hovered = false;
-      if( item.$elt == null ) { return; }
-
-      var fu = G.O.fnThumbnailHoverOut;
-      if( fu !== null ) {
-        typeof fu == 'function' ? fu(item.$elt, item, GOMidx) : window[fu](item.$elt, item, GOMidx);
-      }
-
-      var effects = G.tn.hoverEffects.get();
-      try {
-        for( var j = 0; j < effects.length; j++) {
-          if( effects[j].hoverout === true ) {
-            // item.animate( effects[j], j*10, false );
-            item.animate( effects[j], 0, false );
-          }
-        }
-        // effects on whole layout
-        // GalleryResize( );
-      }
-      catch (e) { 
-        NanoAlert(G, 'error on hoverOut: ' + e.message );
-      }
-      
-    }
-    
-
-    /** @function DisplayPhoto */
-    function DisplayPhoto( imageID, albumID ) {
-
-      if( G.O.debugMode ) { console.log('#DisplayPhoto : '+  albumID +'-'+ imageID); }
-      var albumIdx = NGY2Item.GetIdx(G, albumID);
-      if( albumIdx == 0 ) {
-        G.GOM.curNavLevel = 'l1';
-      }
-      else {
-        G.GOM.curNavLevel = 'lN';
-      }
-
-      if( albumIdx == -1 ) {
-        // get content of album on root level
-        if( G.O.kind != '' ) {
-          // do not add album if Markup or Javascript data
-          NGY2Item.New( G, '', '', albumID, '0', 'album' );    // create empty album
-          albumIdx = G.I.length - 1;
-        }
-      }
-
-      var ngy2ItemIdx = NGY2Item.GetIdx(G, imageID);
-      if( ngy2ItemIdx == -1 ) {
-        // get content of the album
-        AlbumGetContent( albumID, DisplayPhoto, imageID, albumID );
-        return;
-      }
-      
-      if( G.O.debugMode ) { console.log('#DisplayPhoto : '+  ngy2ItemIdx); }
-     
-      DisplayPhotoIdx(ngy2ItemIdx);
-    
-    }
-
-
-    // BETA -> NOT finished and not used at this time
-    // Retrieve the title+description of ONE album
-    function albumGetInfo( albumIdx, fnToCall ) {
-      var url =   '';
-      var kind =  'image';
-      
-      switch( G.O.kind ) {
-        case 'json':
-          // TODO
-        case 'flickr':
-          // TODO
-        case 'picasa':
-        case 'google':
-        case 'google2':
-        default:
-          url = G.Google.url() + 'user/'+G.O.userID+'/albumid/'+G.I[albumIdx].GetID()+'?alt=json&&max-results=1&fields=title';
-          break;
-      }
-
-      jQuery.ajaxSetup({ cache: false });
-      jQuery.support.cors = true;
-      
-      var tId = setTimeout( function() {
-        // workaround to handle JSONP (cross-domain) errors
-        //PreloaderHide();
-        NanoAlert(G, 'Could not retrieve AJAX data...');
-      }, 60000 );
-      jQuery.getJSON(url, function(data, status, xhr) {
-        clearTimeout(tId);
-        //PreloaderHide();
-        
-        fnToCall( G.I[albumIdx].GetID() );
-
-      })
-      .fail( function(jqxhr, textStatus, error) {
-        clearTimeout(tId);
-        //PreloaderHide();
-        var err = textStatus + ', ' + error;
-        NanoAlert('Could not retrieve ajax data: ' + err);
-      });      
-    
-    }
-
-    
-    // function AlbumGetContent( albumIdx, fnToCall ) {
-    function AlbumGetContent( albumID, fnToCall, fnParam1, fnParam2 ) {
-      // var url='';
-      // var kind='image';
-      // var albumIdx=NGY2Item.GetIdx(G, albumID);
-      // var photoIdx=NGY2Item.GetIdx(G, photoID);
-      
-      switch( G.O.kind ) {
-        // MARKUP / API
-        case '':
-          AlbumGetMarkupOrApi(fnToCall, fnParam1, fnParam2);
-          break;
-        // JSON, Flickr, Picasa, ...
-        default:
-          jQuery.nanogallery2['data_'+G.O.kind](G, 'AlbumGetContent', albumID, fnToCall, fnParam1, fnParam2 );
-      }
-      
-    }
-    
-    var mediaList = {
-      youtube : {
-        getID: function( url ) {
-          // https://stackoverflow.com/questions/10591547/how-to-get-youtube-video-id-from-url
-          var s = url.match( /(?:https?:\/{2})?(?:w{3}\.)?youtu(?:be)?\.(?:com|be)(?:\/watch\?v=|\/)([^\s&]+)/ );
-          return s != null ? s[1] : null;
-        },
-        thumbUrl: function( id ) {
-          return 'https://img.youtube.com/vi/' + id + '/hqdefault.jpg';
-        },
-        url: function( id ) {
-          return 'https://www.youtube.com/embed/' + id;
-        },
-        markup: function( id ) {
-          // return '<iframe class="nGY2ViewerMedia" src="https://www.youtube.com/embed/' + id + '?rel=0" frameborder="0" gesture="media" allowfullscreen></iframe>';
-          return '<iframe class="nGY2ViewerMedia" src="https://www.youtube.com/embed/' + id + '?rel=0" frameborder="0" allow="autoplay" allowfullscreen></iframe>';
-        },
-        kind: 'iframe'
-      },
-      vimeo : {
-        getID: function( url ) {
-          // https://stackoverflow.com/questions/2916544/parsing-a-vimeo-id-using-javascript
-          var s = url.match( /^.*(vimeo\.com\/)((channels\/[A-z]+\/)|(groups\/[A-z]+\/videos\/))?([0-9]+)/ );
-          return s != null ? s[5] : null;
-        },
-        url: function( id ) {
-          return 'https://player.vimeo.com/video/' + id;
-        },
-        markup: function( id ) {
-          // return '<iframe class="nGY2ViewerMedia" src="https://player.vimeo.com/video/' + id + '?rel=0" frameborder="0" gesture="media" allowfullscreen></iframe>';
-          return '<iframe class="nGY2ViewerMedia" src="https://player.vimeo.com/video/' + id + '?rel=0" frameborder="0" allow="autoplay" allowfullscreen></iframe>';
-        },
-        kind: 'iframe'
-      },
-      dailymotion : {
-        getID: function( url ) {
-          // https://stackoverflow.com/questions/12387389/how-to-parse-dailymotion-video-url-in-javascript
-          var m = url.match(/^.+dailymotion.com\/(video|hub)\/([^_]+)[^#]*(#video=([^_&]+))?/);
-          if (m !== null) {
-              if(m[4] !== undefined) {
-                  return m[4];
-              }
-              return m[2];
-          }
-          return null;
-        },
-        thumbUrl: function( id ) {
-          return 'https://www.dailymotion.com/thumbnail/video/' + id;
-        },
-        url: function( id ) {
-          return 'https://www.dailymotion.com/embed/video/' + id;
-        },
-        markup: function( id ) {
-          // return '<iframe class="nGY2ViewerMedia" src="https://www.dailymotion.com/embed/video/' + id + '?rel=0" frameborder="0" gesture="media" allowfullscreen></iframe>';
-          return '<iframe class="nGY2ViewerMedia" src="https://www.dailymotion.com/embed/video/' + id + '?rel=0" frameborder="0" allow="autoplay" allowfullscreen></iframe>';
-        },
-        kind: 'iframe'
-      },
-      selfhosted : {
-				// SELF-HOSTED VIDEOS
-        getID: function( url ) {
-          // In order to leave things as is, I used ID to identify the extension
-          // https://stackoverflow.com/questions/6997262/how-to-pull-url-file-extension-out-of-url-string-using-javascript
-          // Make sure the method used for verifying the extension matches the kind of url your selfhosted video has
-          var extension = url.split('.').pop();
-
-          // supported extensions
-          var s = ( extension === 'mp4' || extension === 'webm' || extension === 'ogv' || extension === '3gp' ) ? extension : null ;
-          return s;
-        },
-        markup: function( url ) {
-          // return '<video controls class="nGY2ViewerMedia"><source src="${id.src}" type="video/${id.type}" preload="auto">Your browser does not support the video tag (HTML 5).</video>';
-          var extension = url.split('.').pop();
-          return '<video controls class="nGY2ViewerMedia"><source src="'+ url +'" type="video/'+ extension +'" preload="auto">Your browser does not support the video tag (HTML 5).</video>';
-        },
-        kind: 'video',
-        selfhosted : true
-      }
-    };
-        
-    function AlbumGetMarkupOrApi ( fnToCall, fnParam1, fnParam2 ) {
-    
-      if( G.markupOrApiProcessed === true ) {
-        // already processed (maybe location hash to unknow reference) -> display root album
-        DisplayAlbum('-1', 0);
-        return;
-      }
-      
-      if( G.O.items !== undefined && G.O.items !== null ) {
-        // data defined as an object in an option parameter
-        GetContentApiObject();
-      }
-      else {
-				if( G.O.$markup.length > 0 ) {
-          // data defined as markup (href elements)
-          GetContentMarkup( G.O.$markup );
-          G.O.$markup=[]  ;
-        }
-        else {
-          NanoConsoleLog(G, 'error: no media to process.');
-          return;
-        }
-      }
-      
-      G.markupOrApiProcessed = true;
-      if( fnToCall !== null &&  fnToCall !== undefined) {
-        fnToCall( fnParam1, fnParam2, null );
-      }
-    }
-    
-    function StartsWithProtocol ( path ) {
-      if( path == null || path == undefined ) { return false; }
-      
-      var pattern = /^((http|https|ftp|ftps|file):\/\/)/;
-      if( !pattern.test(path) ) {
-        // not a full URL
-        return false;
-      }
-      return true;
-    }
-    
-    function GetContentApiObject() {
-      var foundAlbumID=false;
-      var nbTitles = 0;
-      var AlbumPostProcess = NGY2Tools.AlbumPostProcess.bind(G);
-
-      G.I[0].contentIsLoaded=true;
-
-      jQuery.each(G.O.items, function(i,item){
-        
-        var title = '';
-        title=GetI18nItem(item, 'title');
-        if( title === undefined ) { title=''; }
-        
-        var src='';
-        if( item['src'+RetrieveCurWidth().toUpperCase()] !== undefined ) {
-          src = item['src'+RetrieveCurWidth().toUpperCase()];
-        }
-        else {
-          src = item.src;
-        }
-        if( !StartsWithProtocol(src) ) {
-          src = G.O.itemsBaseURL + src;
-        }
-
-        var thumbsrc = '';
-        if( item.srct !== undefined && item.srct.length > 0 ) {
-          thumbsrc = item.srct;
-          if( !StartsWithProtocol(thumbsrc) ) {
-            thumbsrc = G.O.itemsBaseURL + thumbsrc;
-          }
-        }
-        else {
-          thumbsrc = src;
-        }
-        
-        if( G.O.thumbnailLabel.get('title') != '' ) {
-          title = GetImageTitle(src);
-        }
-
-        var description='';     //'&nbsp;';
-        description=GetI18nItem(item,'description');
-        if( description === undefined ) { description=''; }
-        //if( toType(item.description) == 'string' ) {
-        //  description=item.description;
-        //}
-
-        var tags = GetI18nItem(item, 'tags');
-        if( tags === undefined ) { tags=''; }
-
-        var albumID = 0;
-        if( item.albumID !== undefined  ) {
-          albumID=item.albumID;
-          foundAlbumID = true;
-        }
-        var ID = null;
-        if( item.ID !== undefined ) {
-          ID = item.ID;
-        }
-        var kind = 'image';
-        if( item.kind !== undefined && item.kind.length > 0 ) {
-          kind = item.kind;
-        }
-        
-        var newItem=NGY2Item.New( G, title, description, ID, albumID, kind, tags );
-        if( title != '' ) {
-          nbTitles++;
-        }
-        
-        // media source url - img is the default media kind
-        newItem.setMediaURL( src, 'img');
-
-        // manage media kinds other than IMG
-        jQuery.each(mediaList, function ( n, media ) {
-          var id = media.getID(src);
-          if( id != null ) {
-            if( typeof media.url == 'function' ) { src = media.url(id);  }
-            if( typeof media.thumbUrl == 'function' ) { thumbsrc = media.thumbUrl(id);  }
-            newItem.mediaKind = media.kind;
-            newItem.mediaMarkup = ( media.selfhosted ) ? media.markup( src ) : media.markup(id);
-            return false;
-          }
-        });
-
-        // image size
-        if( item.imageWidth !== undefined ) { newItem.imageWidth = item.width; }
-        if( item.imageHeight !== undefined ) { newItem.imageHeight = item.height; }
-
-        // THUMBNAILS
-        
-        // thumbnail image size
-        var tw = item.imgtWidth !== undefined ? item.imgtWidth : 0;
-        var th = item.imgtHeight !== undefined ? item.imgtHeight : 0;
-
-        // default thumbnail URL and size
-        newItem.thumbs = {
-          url:    { l1 : { xs: thumbsrc, sm: thumbsrc, me: thumbsrc, la: thumbsrc, xl: thumbsrc }, lN : { xs: thumbsrc, sm: thumbsrc, me: thumbsrc, la: thumbsrc, xl: thumbsrc } },
-          width:  { l1 : { xs: tw, sm: tw, me: tw, la: tw, xl: tw }, lN : { xs: tw, sm: tw, me: tw, la: tw, xl: tw } },
-          height: { l1 : { xs: th, sm: th, me: th, la: th, xl: th }, lN : { xs: th, sm: th, me: th, la: th, xl: th } }
-        };
-        
-        // default media type -> IMG
-        if( newItem.mediaKind == 'img' ) {
-        
-          // responsive thumbnails URL and size
-          var lst=['xs', 'sm', 'me', 'la', 'xl'];
-          for( var i=0; i< lst.length; i++ ) {
-            // url
-            var turl = item['srct' + lst[i].toUpperCase()];
-            if( turl !== undefined ) {
-              if( !StartsWithProtocol(turl) ) {
-                turl = G.O.itemsBaseURL + turl;
-              }
-              newItem.url.l1[lst[i]] = turl;
-              newItem.url.lN[lst[i]] = turl;
-            }
-            // width
-            var tw = item['imgt' + lst[i].toUpperCase() + 'Width'];
-            if( tw != undefined ) {
-              newItem.width.l1[lst[i]] = parseInt(tw);
-              newItem.width.lN[lst[i]] = parseInt(tw);
-            }
-            // height
-            var th = item['imgt' + lst[i].toUpperCase() + 'Height'];
-            if( th != undefined ) {
-              newItem.height.l1[lst[i]] = parseInt(th);
-              newItem.height.lN[lst[i]] = parseInt(th);
-            }
-          }
-        }
-        
-        // dominant colors (needs to be a base64 gif)
-        if( item.imageDominantColors !== undefined ) {
-          newItem.imageDominantColors = item.imageDominantColors;
-        }
-        // dominant color (rgb hex)
-        if( item.imageDominantColor !== undefined ) {
-          newItem.imageDominantColor = item.imageDominantColor;
-        }
-        
-        // dest url
-        if( item.destURL !== undefined && item.destURL.length>0 ) {
-          newItem.destinationURL = item.destURL;
-        }
-        
-        // download image url
-        if( item.downloadURL !== undefined && item.downloadURL.length>0 ) {
-          newItem.downloadURL = item.downloadURL;
-        }
-        
-        // EXIF DATA
-        // Exif - model
-        if( item.exifModel !== undefined ) { newItem.exif.model = item.exifModel; }
-        // Exif - flash
-        if( item.exifFlash !== undefined ) { newItem.exif.flash = item.exifFlash; }
-        // Exif - focallength
-        if( item.exifFocalLength !== undefined ) { newItem.exif.focallength = item.exifFocalLength; }
-        // Exif - fstop
-        if( item.exifFStop !== undefined ) { newItem.exif.fstop = item.exifFStop; }
-        // Exif - exposure
-        if( item.exifExposure !== undefined ) { newItem.exif.exposure = item.exifExposure; }
-        // Exif - time
-        if( item.exifIso !== undefined ) { newItem.exif.iso = item.exifIso; }
-        // Exif - iso
-        if( item.exifTime !== undefined ) { newItem.exif.time = item.exifTime; }
-        // Exif - location
-        if( item.exifLocation !== undefined ) { newItem.exif.location  = item.exifLocation; }
-        
-        
-        // custom data
-        if( item.customData !== null ) {
-          newItem.customData = cloneJSObject( item.customData );
-        }
-
-        newItem.contentIsLoaded = true;
-        
-        var fu = G.O.fnProcessData;
-        if( fu !== null ) {
-          typeof fu == 'function' ? fu(newItem, 'api', item) : window[fu](newItem, 'api', item);
-        }
-        
-        AlbumPostProcess(albumID);
-      });
-      
-      // if( foundAlbumID ) { G.O.displayBreadcrumb=true; }
-      if( nbTitles == 0 ) { G.O.thumbnailLabel.display=false; }
-
-    }
-    
-
-    // Returns the text of the DOM element (without children)
-    // Because jQuery().text() returns the text of all children
-    function ElementGetText( element ) {
-      
-      var text = '';
-      if( element.childNodes[0] !== undefined ) {
-        if( element.childNodes[0].nodeValue !== null && element.childNodes[0].nodeValue !== undefined ) {
-          text = element.childNodes[0].nodeValue.trim();
-        }
-      }
-      return text;
-    }
-    
-    // Extract items from the jQuery elements
-    function GetContentMarkup( $elements, group ) {
-      var foundAlbumID = false;
-      var nbTitles = 0;
-      var AlbumPostProcess = NGY2Tools.AlbumPostProcess.bind(G);
-      var GetImageTitleFromURL = NGY2Tools.GetImageTitleFromURL.bind(G);
-
-      G.I[0].contentIsLoaded = true;
-
-      jQuery.each($elements, function(i, item){
-        
-        // compare to group defined on the element that has been clicked (lightbox standalone)
-        if( item.dataset.nanogallery2Lgroup != group ) { return; }
-
-        // create dictionnary with all data attribute name in lowercase (to be case unsensitive)
-        var data = {
-          // all possible data attributes with some default values
-          'data-ngdesc':                  '',         // item description
-          'data-ngid':                    null,       // ID
-          'data-ngkind':                  'image',    // kind (image, album, albumup)
-          'data-ngtags':                  null,       // tags
-          'data-ngdest':                  '',         // destination URL
-          'data-ngthumbimgwidth':         0,          // thumbnail width
-          'data-ngthumbimgheight':        0,          // thumbnail height
-          'data-ngimagewidth':            0,          // image width
-          'data-ngimageheight':           0,          // image height
-          'data-ngimagedominantcolors':   null,       // image dominant colors
-          'data-ngimagedominantcolor':    null,       // image dominant colors
-          'data-ngexifmodel':             '',         // EXIF data
-          'data-ngexifflash':             '',
-          'data-ngexiffocallength':       '',
-          'data-ngexiffstop':             '',
-          'data-ngexifexposure':          '',
-          'data-ngexifiso':               '',
-          'data-ngexiftime':              '',
-          'data-ngexiflocation':          '',
-          'data-ngsrc':    					      '',
-					'alt':													''
-        };
-
-        // Extract data attributes from main item
-        [].forEach.call( item.attributes, function(attr) {
-          data[attr.name.toLowerCase()] = attr.value.trim();
-        });
-
-        var title = ElementGetText(item);
-				if( title == '' && data.alt != '') {
-					// no title -> check ALT attribute of main element
-					title = data['alt'];
-				}
-        
-        // Complete with data attributes from all children
-        jQuery.each($(item).children(), function(i, sub_item){
-          
-          // title may be set on a child element
-          if( title == '' ) {
-            title = ElementGetText(sub_item);
-          }
-          
-          [].forEach.call( sub_item.attributes, function(attr) {
-            data[attr.name.toLowerCase()] = attr.value.trim();
-          });
-
-					if( title == '' && data.alt != '') {
-						// no title -> check ALT attribute of sub element
-						title = data['alt'];
-					}
-
-				});
-
-				// BIG IMAGE URL
-        // responsive image URL
-        var src = '',
-        st = RetrieveCurWidth().toUpperCase();
-        if( data.hasOwnProperty('data-ngsrc'+st) ) {
-          src = data['data-ngsrc'+st];
-        }
-				// image URL from data-ngsrc attribute 
-        // if( src == '' ) {
-          // src = data['data-ngsrc'];
-        // }
-				// image URL from href attribute (a element)
-        // if( src == '' ) {
-          // src = data['href'];
-        // }
-        src = src || data['data-ngsrc'] || data['href'];
-        if( !StartsWithProtocol(src) ) {          // do not add the base URL if src starts with a protocol (http, https...)
-          src = G.O.itemsBaseURL + src;
-        }
-
-
-        // THUMBNAIL IMAGE
-        var thumbsrc = '';
-				// src attribute (img element)
-        if( data.hasOwnProperty('src') ) {
-          thumbsrc = data['src'];
-        }
-				// data-ngthumb attribute
-				if( thumbsrc == '' && data.hasOwnProperty('data-ngthumb') ) {
-          thumbsrc = data['data-ngthumb'];
-        }
-        if( thumbsrc == '' ) {
-          thumbsrc = src;       // no thumbnail image URL -> use big image URL
-        }
-				if( !StartsWithProtocol(thumbsrc) ) {
-					thumbsrc = G.O.itemsBaseURL + thumbsrc;
-				}
-
-        //newObj.description=jQuery(item).attr('data-ngdesc');
-        var description = data['data-ngdesc'];
-        var ID = data['id'] || data['data-ngid'];
-        // if( ID == undefined ) {
-          // ID = data['data-ngid'];
-        // }
-        var kind = data['data-ngkind'];
-        var tags = data['data-ngtags'];
-
-        var albumID = '0';
-        if( data.hasOwnProperty('data-ngalbumid') ) {
-          albumID = data['data-ngalbumid'];
-          foundAlbumID = true;
-        }
-
-        // var title = jQuery(item).text();
-        var title_from_url = GetImageTitleFromURL( src );
-        if( title_from_url != '' ) {
-          title = title_from_url;
-        }
-
-
-        var newItem = NGY2Item.New( G, title, description, ID, albumID, kind, tags );
-        if( title != '' ) {
-          nbTitles++;
-        }
-
-        // media source url - img is the default media kind
-        newItem.setMediaURL( src, 'img');
-
-        // manage media kinds other than IMG
-        // newItem.mediaKind = 'img';
-        jQuery.each(mediaList, function ( n, media ) {
-          var id = media.getID(src);
-          if( id != null ) {
-            if( typeof media.url == 'function' ) { src = media.url(id);  }
-            if( typeof media.thumbUrl == 'function' ) { thumbsrc = media.thumbUrl(id);  }
-            newItem.mediaKind = media.kind;
-            newItem.mediaMarkup = ( media.selfhosted ) ? media.markup( src ) : media.markup(id);
-            return false;
-          }
-        });
-        
-        
-        // Big image size
-        newItem.imageWidth = parseInt( data['data-ngimagewidth'] );
-        newItem.imageHeight = parseInt( data['data-ngimageheight'] );
-        
-        // default thumbnail image URL and size
-        var tw = parseInt(data['data-ngthumbimgwidth']);
-        var th = parseInt(data['data-ngthumbimgheight']);
-        newItem.thumbs = {
-          url:    { l1 : { xs: thumbsrc, sm: thumbsrc, me: thumbsrc, la: thumbsrc, xl: thumbsrc }, lN : { xs: thumbsrc, sm: thumbsrc, me: thumbsrc, la: thumbsrc, xl: thumbsrc } },
-          width:  { l1 : { xs: tw, sm: tw, me: tw, la: tw, xl: tw }, lN : { xs: tw, sm: tw, me: tw, la: tw, xl: tw } },
-          height: { l1 : { xs: th, sm: th, me: th, la: th, xl: th }, lN : { xs: th, sm: th, me: th, la: th, xl: th } }
-        };
-
-        // Media type -> IMG
-        if( newItem.mediaKind == 'img' ) {
-        
-          // responsive thumbnails URL and size
-          var lst = ['xs', 'sm', 'me', 'la', 'xl'];
-          for( var i = 0; i < lst.length; i++ ) {
-            // url
-            if( data.hasOwnProperty('data-ngthumb' + lst[i]) ) {
-              var turl=data['data-ngthumb' + lst[i]];
-              if( !StartsWithProtocol(turl) ) {
-                turl = G.O.itemsBaseURL + turl;
-              }
-              newItem.url.l1[lst[i]] = turl;
-              newItem.url.lN[lst[i]] = turl;
-            }
-
-            // width
-            if( data.hasOwnProperty('data-ngthumb' + lst[i] + 'width') ) {
-              var tw=parseInt(data['data-ngthumb' + lst[i] + 'width']);
-              newItem.width.l1[lst[i]] = tw;
-              newItem.width.lN[lst[i]] = tw;
-            }
-            // height
-            if( data.hasOwnProperty('data-ngthumb' + lst[i] + 'height') ) {
-              var th=parseInt('data-ngthumb' + lst[i] + 'height');
-              newItem.height.l1[lst[i]] = th;
-              newItem.height.lN[lst[i]] = th;
-            }
-          }
-        }
-        
-        
-        // dominant colorS (needs to be a base64 gif)
-        newItem.imageDominantColors = data['data-ngimagedominantcolors'];
-        // dominant color (rgb hex)
-        newItem.imageDominantColor = data['data-ngimagedominantcolors'];
-
-        newItem.destinationURL = data['data-ngdest'];
-        newItem.downloadURL = data['data-ngdownloadurl'];
-
-        // Exif - model
-        newItem.exif.model=data['data-ngexifmodel'];
-        // Exif - flash
-        newItem.exif.flash=data['data-ngexifflash'];
-        // Exif - focallength
-        newItem.exif.focallength=data['data-ngexiffocallength'];
-        // Exif - fstop
-        newItem.exif.fstop=data['data-ngexiffstop'];
-        // Exif - exposure
-        newItem.exif.exposure=data['data-ngexifexposure'];
-        // Exif - iso
-        newItem.exif.iso=data['data-ngexifiso'];
-        // Exif - time
-        newItem.exif.time=data['data-ngexiftime'];
-        // Exif - location
-        newItem.exif.location=data['data-ngexiflocation'];
-        
-        newItem.contentIsLoaded = true;
-
-        // custom data
-        if( jQuery(item).data('customdata') !== undefined ) {
-          newItem.customData = cloneJSObject(jQuery(item).data('customdata'));
-        }
-        // custom data
-        if( jQuery(item).data('ngcustomdata') !== undefined ) {
-          newItem.customData = cloneJSObject(jQuery(item).data('ngcustomdata'));
-        }
-
-        var fu=G.O.fnProcessData;
-        if( fu !== null ) {
-          typeof fu == 'function' ? fu(newItem, 'markup', item) : window[fu](newItem, 'markup', item);
-        }
-        
-        AlbumPostProcess(albumID);
-
-      });
-      
-      // if( foundAlbumID ) { G.O.displayBreadcrumb=true; }
-      if( nbTitles == 0 ) { G.O.thumbnailLabel.display = false; }
-			
-    }
-
-    
-    // ################################
-    // ##### DEFINE VARIABLES     #####
-    // ################################
-
-    
-    /** @function DefineVariables */
-    function DefineVariables() {
-    
-      // change 'picasa' to 'google' for compatibility reason
-      if( G.O.kind.toUpperCase() == 'PICASA'  || G.O.kind.toUpperCase() == 'GOOGLE') {
-        G.O.kind='google2';
-      }
-    
-      // management of screen width
-      G.GOM.cache.viewport = getViewport();
-      G.GOM.curWidth = RetrieveCurWidth();
-      
-      // tumbnail toolbar
-      jQuery.extend(true, G.tn.toolbar.image, G.O.thumbnailToolbarImage );
-      jQuery.extend(true, G.tn.toolbar.album, G.O.thumbnailToolbarAlbum );
-      var t = ['image', 'album'];
-      var pos= ['topLeft', 'topRight', 'bottomLeft', 'bottomRight']
-      for( var i=0; i < t.length ; i++ ) {
-        for( var j=0; j < pos.length ; j++ ) {
-          G.tn.toolbar[t[i]][pos[j]] = G.tn.toolbar[t[i]][pos[j]].toUpperCase();
-        }
-      }
-
-      // convert label settings
-      if( G.O.thumbnailLabel.position == 'overImageOnBottom' ) {
-        G.O.thumbnailLabel.valign = 'bottom';
-        G.O.thumbnailLabel.position == 'overImage';
-      }
-      if( G.O.thumbnailLabel.position == 'overImageOnMiddle' ) {
-        G.O.thumbnailLabel.valign = 'middle';
-        G.O.thumbnailLabel.position == 'overImage';
-      }
-      if( G.O.thumbnailLabel.position == 'overImageOnTop' ) {
-        G.O.thumbnailLabel.valign = 'top';
-        G.O.thumbnailLabel.position == 'overImage';
-      }
-      if( G.O.thumbnailL1Label !== undefined && G.O.thumbnailL1Label.position !== undefined ) {
-        if( G.O.thumbnailL1Label.position == 'overImageOnBottom' ) {
-          G.O.thumbnailL1Label.valign = 'bottom';
-          G.O.thumbnailL1Label.position == 'overImage';
-        }
-        if( G.O.thumbnailL1Label.position == 'overImageOnMiddle' ) {
-          G.O.thumbnailL1Label.valign = 'middle';
-          G.O.thumbnailL1Label.position == 'overImage';
-        }
-        if( G.O.thumbnailL1Label.position == 'overImageOnTop' ) {
-          G.O.thumbnailL1Label.valign = 'top';
-          G.O.thumbnailL1Label.position == 'overImage';
-        }
-      }
-
-      // thumbnails label - level dependant settings
-      G.O.thumbnailLabel.get = function( opt ) {
-        if( G.GOM.curNavLevel == 'l1' && G.O.thumbnailL1Label !== undefined && G.O.thumbnailL1Label[opt] !== undefined ) {
-          return G.O.thumbnailL1Label[opt];
-        }
-        else {
-          return G.O.thumbnailLabel[opt];
-        }
-      };
-      G.O.thumbnailLabel.set = function( opt, value ) {
-        if( G.GOM.curNavLevel == 'l1' && G.O.thumbnailL1Label !== undefined && G.O.thumbnailL1Label[opt] !== undefined ) {
-          G.O.thumbnailL1Label[opt]=value;
-        }
-        else {
-          G.O.thumbnailLabel[opt]=value;
-        }
-      };
-
-      if( G.O.blockList != '' ) { G.blockList = G.O.blockList.toUpperCase().split('|'); }
-      if( G.O.allowList != '' ) { G.allowList = G.O.allowList.toUpperCase().split('|'); }
-
-      if( G.O.albumList2 !== undefined && G.O.albumList2 !== null && G.O.albumList2.constructor === Array  ) {
-        var l=G.O.albumList2.length;
-        for(var i=0; i< l; i++ ) {
-          G.albumList.push(G.O.albumList2[i]);
-        }
-        // G.albumList=G.O.albumList.toUpperCase().split('|');
-      }
-      if( G.O.albumList2 !== undefined && typeof G.O.albumList2 == 'string'   ) {
-        G.albumList.push(G.O.albumList2);
-      }
-      
-      
-      // thumbnail image crop
-      G.tn.opt.lN.crop = G.O.thumbnailCrop;
-      G.tn.opt.l1.crop = G.O.thumbnailL1Crop != null ? G.O.thumbnailL1Crop : G.O.thumbnailCrop;
-
-
-      function ThumbnailOpt( lN, l1, opt) {
-        G.tn.opt.lN[opt] = G.O[lN];
-        G.tn.opt.l1[opt] = G.O[lN];
-        if( toType(G.O[l1]) == 'number' ) {
-          G.tn.opt.l1[opt] = G.O[l1];
-        }
-      }
-      // thumbnail stacks
-      ThumbnailOpt('thumbnailStacks', 'thumbnailL1Stacks', 'stacks');
-      // thumbnail stacks translate X
-      ThumbnailOpt('thumbnailStacksTranslateX', 'thumbnailL1StacksTranslateX', 'stacksTranslateX');
-      // thumbnail stacks translate Y
-      ThumbnailOpt('thumbnailStacksTranslateY', 'thumbnailL1StacksTranslateY', 'stacksTranslateY');
-      // thumbnail stacks translate Z
-      ThumbnailOpt('thumbnailStacksTranslateZ', 'thumbnailL1StacksTranslateZ', 'stacksTranslateZ');
-      // thumbnail stacks rotate X
-      ThumbnailOpt('thumbnailStacksRotateX', 'thumbnailL1StacksRotateX', 'stacksRotateX');
-      // thumbnail stacks rotate Y
-      ThumbnailOpt('thumbnailStacksRotateY', 'thumbnailL1StacksRotateY', 'stacksRotateY');
-      // thumbnail stacks rotate Z
-      ThumbnailOpt('thumbnailStacksRotateZ', 'thumbnailL1StacksRotateZ', 'stacksRotateZ');
-      // thumbnail stacks scale
-      ThumbnailOpt('thumbnailStacksScale', 'thumbnailL1StacksScale', 'stacksScale');
-      // thumbnail gutter width
-      // ThumbnailOpt('thumbnailGutterWidth', 'thumbnailL1GutterWidth', 'gutterWidth');
-      // thumbnail gutter height
-      // ThumbnailOpt('thumbnailGutterHeight', 'thumbnailL1GutterHeight', 'gutterHeight');
-      // thumbnail border horizontal
-      ThumbnailOpt('thumbnailBorderHorizontal', 'thumbnailL1BorderHorizontal', 'borderHorizontal');
-      // thumbnail border vertical
-      ThumbnailOpt('thumbnailBorderVertical', 'thumbnailL1BorderVertical', 'borderVertical');
-      // thumbnail grid base height (for cascading layout)
-      ThumbnailOpt('thumbnailBaseGridHeight', 'thumbnailL1BaseGridHeight', 'baseGridHeight');
-      
-      
-      // Set same value to all widths
-      function ResponsiveSetSize( setting, level, v ) {
-        G.tn.settings[setting][level]['xs'] = v;
-        G.tn.settings[setting][level]['sm'] = v;
-        G.tn.settings[setting][level]['me'] = v;
-        G.tn.settings[setting][level]['la'] = v;
-        G.tn.settings[setting][level]['xl'] = v;
-      }
-
-      // Get and evaluate responsive values from one option
-      // Responsive is with syntax: n XSn1 SMn2 MEn3 LAn4 XLn5 (where n is the default value)
-      // Value 'auto' is accepted for all options, but is handeld only for thumbnail width/height
-      function ResponsiveOption( option, setting, level ) {
-        var v = G.O[option];
-
-        if( v === undefined || v === null ) { return; }
-        
-        // if( toType(v) == 'number' ) {
-        if( toType(v) == 'number' || v.indexOf(' ') == -1 ) {
-          // set value for all widths
-          var vn = 'auto';                              
-          if( v != 'auto' ) { vn = parseInt(v); }
-          ResponsiveSetSize( setting, level, vn );
-        }
-        else {
-          var sp = v.split(' ');
-          if( sp.length > 0 && +sp[0] === +sp[0] ) {          // check if sp[0] is a number
-            // first value is the default size for all widths
-            var vn = 'auto';
-            if( sp[0] != 'auto' ) { vn = parseInt(sp[0]); }
-            ResponsiveSetSize( setting, level, vn );
-          }
-          for( var i = 1; i < sp.length; i++ ) {
-            if( /^xs|sm|me|la|xl/i.test( sp[i] ) ) {        // regex: i ignores the case and ^ means "starts with"
-              var wi = sp[i].substring(0, 2).toLowerCase();
-              var va = sp[i].substring(2);
-              var vn = 'auto';
-              if( va != 'auto' ) { vn = parseInt(va); }
-              G.tn.settings[setting][level][wi] = vn;
-            }
-          }
-        }
-      }
-      
-      ResponsiveOption('thumbnailGutterWidth', 'gutterWidth', 'lN');
-      ResponsiveOption('thumbnailGutterWidth', 'gutterWidth', 'l1');        // set default values for first level
-      ResponsiveOption('thumbnailL1GutterWidth', 'gutterWidth', 'l1');
-      ResponsiveOption('thumbnailGutterHeight', 'gutterHeight', 'lN');
-      ResponsiveOption('thumbnailGutterHeight', 'gutterHeight', 'l1');      // set default values for first level
-      ResponsiveOption('thumbnailL1GutterHeight', 'gutterHeight', 'l1');
-      
-      // gallery display mode
-      G.galleryDisplayMode.lN = G.O.galleryDisplayMode.toUpperCase();
-      G.galleryDisplayMode.l1 = G.O.galleryL1DisplayMode != null ? G.O.galleryL1DisplayMode.toUpperCase() : G.O.galleryDisplayMode.toUpperCase();
-      
-      // gallery maximum number of lines of thumbnails
-      G.galleryMaxRows.lN = G.O.galleryMaxRows;
-      G.galleryMaxRows.l1 = toType(G.O.galleryL1MaxRows) == 'number' ? G.O.galleryL1MaxRows : G.O.galleryMaxRows;
-
-      // gallery last row full
-      G.galleryLastRowFull.lN = G.O.galleryLastRowFull;
-      G.galleryLastRowFull.l1 = G.O.galleryL1LastRowFull != null ? G.O.galleryL1LastRowFull : G.O.galleryLastRowFull;
-      
-      // gallery sorting
-      G.gallerySorting.lN = G.O.gallerySorting.toUpperCase();
-      G.gallerySorting.l1 = G.O.galleryL1Sorting != null ? G.O.galleryL1Sorting.toUpperCase() : G.gallerySorting.lN;
-      
-      // gallery display transition
-      G.galleryDisplayTransition.lN = G.O.galleryDisplayTransition.toUpperCase();
-      G.galleryDisplayTransition.l1 = G.O.galleryL1DisplayTransition != null ? G.O.galleryL1DisplayTransition.toUpperCase() : G.galleryDisplayTransition.lN;
-
-      // gallery display transition duration
-      G.galleryDisplayTransitionDuration.lN = G.O.galleryDisplayTransitionDuration;
-      G.galleryDisplayTransitionDuration.l1 = G.O.galleryL1DisplayTransitionDuration != null ? G.O.galleryL1DisplayTransitionDuration : G.galleryDisplayTransitionDuration.lN;
-      
-      // gallery max items per album (not for inline/api defined items)
-      G.galleryMaxItems.lN = G.O.galleryMaxItems;
-      G.galleryMaxItems.l1 = toType(G.O.galleryL1MaxItems) == 'number' ? G.O.galleryL1MaxItems : G.O.galleryMaxItems;
-
-      // gallery filter tags
-      G.galleryFilterTags.lN = G.O.galleryFilterTags;
-      G.galleryFilterTags.l1 = G.O.galleryL1FilterTags != null ? G.O.galleryL1FilterTags : G.O.galleryFilterTags;
-
-      // gallery filter tags mode
-      G.galleryFilterTagsMode.lN = G.O.galleryFilterTagsMode;
-      G.galleryFilterTagsMode.l1 = G.O.galleryL1FilterTagsMode != null ? G.O.galleryL1FilterTagsMode : G.O.galleryFilterTagsMode;
-      
-      // gallery pagination
-      G.O.galleryPaginationMode = G.O.galleryPaginationMode.toUpperCase();
-
-      if( toType(G.O.slideshowDelay) == 'number' && G.O.slideshowDelay >= 2000 ) {
-        G.VOM.slideshowDelay = G.O.slideshowDelay;
-      }
-      else {
-        NanoConsoleLog(G, 'Parameter "slideshowDelay" must be an integer >= 2000 ms.');
-      }
-
-      // gallery display transition
-      if( typeof G.O.thumbnailDisplayTransition == 'boolean' ) {
-        if( G.O.thumbnailDisplayTransition === true ) {
-          G.tn.opt.lN.displayTransition = 'FADEIN';
-          G.tn.opt.l1.displayTransition = 'FADEIN';
-        }
-        else {
-          G.tn.opt.lN.displayTransition = 'NONE';
-          G.tn.opt.l1.displayTransition = 'NONE';
-        }
-      }
-
-      if( G.O.fnThumbnailDisplayEffect !== '' ) {
-        G.tn.opt.lN.displayTransition = 'CUSTOM';
-        G.tn.opt.l1.displayTransition = 'CUSTOM';
-      }
-      if( G.O.fnThumbnailL1DisplayEffect !== '' ) {
-        G.tn.opt.l1.displayTransition = 'CUSTOM';
-      }
-      
-
-      // thumbnail display transition easing
-			// set default easing
-      ThumbnailOpt('thumbnailDisplayTransitionEasing', 'thumbnailL1DisplayTransitionEasing', 'displayTransitionEasing');
-      // parse thumbnail display transition
-      function thumbnailDisplayTransitionParse( cfg, level ) {
-        if( typeof cfg == 'string' ) {
-          var st=cfg.split('_');
-          if( st.length == 1 ) {
-            G.tn.opt[level]['displayTransition'] = cfg.toUpperCase();
-          }
-          if( st.length == 2 ) {
-            G.tn.opt[level]['displayTransition'] = st[0].toUpperCase();
-            G.tn.opt[level]['displayTransitionStartVal'] = Number(st[1]);
-          }
-          if( st.length == 3 ) {
-            G.tn.opt[level]['displayTransition'] = st[0].toUpperCase();
-            G.tn.opt[level]['displayTransitionStartVal'] = Number(st[1]);
-            G.tn.opt[level]['displayTransitionEasing'] = st[2];
-          }
-        }
-      }
-      thumbnailDisplayTransitionParse( G.O.thumbnailDisplayTransition, 'lN');
-      thumbnailDisplayTransitionParse( G.O.thumbnailDisplayTransition, 'l1');
-      thumbnailDisplayTransitionParse( G.O.thumbnailL1DisplayTransition, 'l1');
-
-      
-      // thumbnail display transition duration
-      ThumbnailOpt('thumbnailDisplayTransitionDuration', 'thumbnailL1DisplayTransitionDuration', 'displayTransitionDuration');
-      // thumbnail display transition interval duration
-      ThumbnailOpt('thumbnailDisplayInterval', 'thumbnailL1DisplayInterval', 'displayInterval');
-      // thumbnail display order
-      ThumbnailOpt('thumbnailDisplayOrder', 'thumbnailL1DisplayOrder', 'displayOrder');
-
-      
-      // resolution breakpoints --> convert old syntax to new one
-      if( G.O.thumbnailSizeSM !== undefined ) { G.O.breakpointSizeSM = G.O.thumbnailSizeSM; }
-      if( G.O.thumbnailSizeME !== undefined ) { G.O.breakpointSizeME = G.O.thumbnailSizeME; }
-      if( G.O.thumbnailSizeLA !== undefined ) { G.O.breakpointSizeLA = G.O.thumbnailSizeLA; }
-      if( G.O.thumbnailSizeXL !== undefined ) { G.O.breakpointSizeXL = G.O.thumbnailSizeXL; }
-
-      // THUMBNAIL BUILD INIT
-      //level 1
-      if( G.O.thumbnailL1BuildInit2 !== undefined ) {
-        var t1 = G.O.thumbnailL1BuildInit2.split('|');
-        for( var i = 0; i < t1.length; i++ ) {
-          var o1 = t1[i].trim().split('_');
-          if( o1.length == 3 ) {
-            var i1 = NewTBuildInit();
-            i1.element = ThumbnailOverEffectsGetCSSElement(o1[0], '');
-            i1.property = o1[1];
-            i1.value = o1[2];
-            G.tn.buildInit.level1.push(i1);
-          }
-        }
-      }
-      //level N
-      if( G.O.thumbnailBuildInit2 !== undefined ) {
-        var t1 = G.O.thumbnailBuildInit2.split('|');
-        for( var i = 0; i < t1.length; i++ ) {
-          var o1 = t1[i].trim().split('_');
-          if( o1.length == 3 ) {
-            var i1 = NewTBuildInit();
-            i1.element = ThumbnailOverEffectsGetCSSElement(o1[0], '');
-            i1.property = o1[1];
-            i1.value = o1[2];
-            G.tn.buildInit.std.push(i1);
-          }
-        }
-      }
-
-      
-      // THUMBNAIL HOVER EFFETCS
-      
-      // thumbnails hover effects - Level1
-      var tL1HE = G.O.thumbnailL1HoverEffect2;
-      if( tL1HE !== undefined ) {
-        switch( toType(tL1HE) ) {
-          case 'string':
-            var tmp = tL1HE.split('|');
-            for(var i = 0; i < tmp.length; i++) {
-              var oDef = NewTHoverEffect();
-              oDef = ThumbnailHoverEffectExtract( tmp[i].trim(), oDef );
-              if(  oDef != null ) {
-                G.tn.hoverEffects.level1.push(oDef);
-              }
-            }
-            break;
-          case 'object':
-            var oDef = NewTHoverEffect();
-            oDef = jQuery.extend(oDef,tL1HE);
-            oDef = ThumbnailHoverEffectExtract( oDef.name, oDef );
-            if(  oDef != null ) {
-              G.tn.hoverEffects.level1.push(oDef);
-            }
-            break;
-          case 'array':
-            for(var i = 0; i < tL1HE.length; i++) {
-              var oDef = NewTHoverEffect();
-              oDef = jQuery.extend(oDef,tL1HE[i]);
-              oDef = ThumbnailHoverEffectExtract( oDef.name, oDef );
-              if(  oDef != null ) {
-                G.tn.hoverEffects.level1.push(oDef);
-              }
-            }
-            break;
-          case 'null':
-            break;
-          default:
-            NanoAlert(G, 'incorrect parameter for "thumbnailL1HoverEffect2".');
-        }
-      }
-      G.tn.hoverEffects.level1 = ThumbnailOverEffectsPreset(G.tn.hoverEffects.level1);
-  
-      // thumbnails hover effects - other levels
-      var tHE = G.O.thumbnailHoverEffect2;
-      switch( toType(tHE) ) {
-        case 'string':
-          var tmp = tHE.split('|');
-          for(var i = 0; i < tmp.length; i++) {
-            var oDef = NewTHoverEffect();
-            oDef = ThumbnailHoverEffectExtract( tmp[i].trim(), oDef );
-            if(  oDef != null ) {
-              G.tn.hoverEffects.std.push(oDef);
-            }
-          }
-          break;
-        case 'object':
-          var oDef = NewTHoverEffect();
-          oDef = jQuery.extend(oDef, tHE);
-          oDef = ThumbnailHoverEffectExtract( oDef.name, oDef );
-          if(  oDef != null ) {
-            G.tn.hoverEffects.std.push(oDef);
-          }
-          break;
-        case 'array':
-          for(var i = 0; i < tHE.length; i++) {
-            var oDef = NewTHoverEffect();
-            oDef = jQuery.extend(oDef,tHE[i]);
-            oDef = ThumbnailHoverEffectExtract( oDef.name, oDef );
-            if(  oDef!= null ) {
-              G.tn.hoverEffects.std.push(oDef);
-            }
-          }
-          break;
-        case 'null':
-          break;
-        default:
-          NanoAlert(G, 'incorrect parameter for "thumbnailHoverEffect2".');
-      }
-      G.tn.hoverEffects.std = ThumbnailOverEffectsPreset(G.tn.hoverEffects.std);
-
-      
-      if( G.O.touchAnimationL1 == undefined ) {
-        G.O.touchAnimationL1 = G.O.touchAnimation;
-      }
-      
-      // disable thumbnail touch animation when no hover effect defined
-      if( G.tn.hoverEffects.std.length == 0 ) {
-        if( G.tn.hoverEffects.level1.length == 0 ) {
-          G.O.touchAnimationL1 = false;
-        }
-        G.O.touchAnimation = false;
-      }      
-      
-      
-      // thumbnail sizes
-      if( G.O.thumbnailHeight == 0 || G.O.thumbnailHeight == ''  ) { G.O.thumbnailHeight = 'auto'; }
-      if( G.O.thumbnailWidth == 0 || G.O.thumbnailWidth == '' ) { G.O.thumbnailWidth = 'auto'; }
-      if( G.O.thumbnailL1Height == 0 || G.O.thumbnailL1Height == '' ) { G.O.thumbnailL1Height = 'auto'; }
-      if( G.O.thumbnailL1Width == 0 || G.O.thumbnailL1Width == '' ) { G.O.thumbnailL1Width = 'auto'; }
-
-      // RETRIEVE ALL THUMBNAIL SIZES
-      // ThumbnailSizes( 'thumbnailWidth', false, 'width');
-      // ThumbnailSizes( 'thumbnailL1Width', true, 'width');
-      // ThumbnailSizes( 'thumbnailHeight', false, 'height');
-      // ThumbnailSizes( 'thumbnailL1Height', true, 'height');
-      ResponsiveOption('thumbnailWidth', 'width', 'lN');
-      ResponsiveOption('thumbnailWidth', 'width', 'l1');
-      ResponsiveOption('thumbnailL1Width', 'width', 'l1');
-      ResponsiveOption('thumbnailHeight', 'height', 'lN');
-      ResponsiveOption('thumbnailHeight', 'height', 'l1');
-      ResponsiveOption('thumbnailL1Height', 'height', 'l1');
-
-      
-      G.O.thumbnailLabelHeight = parseInt(G.O.thumbnailLabelHeight);
-
-      
-      // retrieve all mosaic layout patterns
-      // default pattern
-      if( G.O.galleryMosaic != undefined ) {
-        // clone object
-        G.tn.settings.mosaic.l1.xs = JSON.parse(JSON.stringify(G.O.galleryMosaic));
-        G.tn.settings.mosaic.l1.sm = JSON.parse(JSON.stringify(G.O.galleryMosaic));
-        G.tn.settings.mosaic.l1.me = JSON.parse(JSON.stringify(G.O.galleryMosaic));
-        G.tn.settings.mosaic.l1.la = JSON.parse(JSON.stringify(G.O.galleryMosaic));
-        G.tn.settings.mosaic.l1.xl = JSON.parse(JSON.stringify(G.O.galleryMosaic));
-        G.tn.settings.mosaic.lN.xs = JSON.parse(JSON.stringify(G.O.galleryMosaic));
-        G.tn.settings.mosaic.lN.sm = JSON.parse(JSON.stringify(G.O.galleryMosaic));
-        G.tn.settings.mosaic.lN.me = JSON.parse(JSON.stringify(G.O.galleryMosaic));
-        G.tn.settings.mosaic.lN.la = JSON.parse(JSON.stringify(G.O.galleryMosaic));
-        G.tn.settings.mosaic.lN.xl = JSON.parse(JSON.stringify(G.O.galleryMosaic));
-        G.tn.settings.mosaicCalcFactor('l1', 'xs');
-        G.tn.settings.mosaicCalcFactor('l1', 'sm');
-        G.tn.settings.mosaicCalcFactor('l1', 'me');
-        G.tn.settings.mosaicCalcFactor('l1', 'la');
-        G.tn.settings.mosaicCalcFactor('l1', 'xl');
-        G.tn.settings.mosaicCalcFactor('lN', 'xs');
-        G.tn.settings.mosaicCalcFactor('lN', 'sm');
-        G.tn.settings.mosaicCalcFactor('lN', 'me');
-        G.tn.settings.mosaicCalcFactor('lN', 'la');
-        G.tn.settings.mosaicCalcFactor('lN', 'xl');
-      }
-      if( G.O.galleryL1Mosaic != undefined ) {
-        // default L1 pattern
-        G.tn.settings.mosaic.l1.xs = JSON.parse(JSON.stringify(G.O.galleryL1Mosaic));
-        G.tn.settings.mosaic.l1.sm = JSON.parse(JSON.stringify(G.O.galleryL1Mosaic));
-        G.tn.settings.mosaic.l1.me = JSON.parse(JSON.stringify(G.O.galleryL1Mosaic));
-        G.tn.settings.mosaic.l1.la = JSON.parse(JSON.stringify(G.O.galleryL1Mosaic));
-        G.tn.settings.mosaic.l1.xl = JSON.parse(JSON.stringify(G.O.galleryL1Mosaic));
-        G.tn.settings.mosaicCalcFactor('l1', 'xs');
-        G.tn.settings.mosaicCalcFactor('l1', 'sm');
-        G.tn.settings.mosaicCalcFactor('l1', 'me');
-        G.tn.settings.mosaicCalcFactor('l1', 'la');
-        G.tn.settings.mosaicCalcFactor('l1', 'xl');
-      }
-
-			var lst=['xs','sm','me','la','xl'];
-			// retrieve responsive mosaic definition for levels l1 & lN
-      for( var w = 0; w < lst.length; w++ ) {
-				if( G.O['galleryMosaic' + lst[w].toUpperCase()] != undefined ) {
-          G.tn.settings.mosaic.lN[lst[w]] = JSON.parse(JSON.stringify( G.O['galleryMosaic' + lst[w].toUpperCase()] ));
-          G.tn.settings.mosaic.l1[lst[w]] = JSON.parse(JSON.stringify( G.O['galleryMosaic' + lst[w].toUpperCase()] ));
-          G.tn.settings.mosaicCalcFactor('lN',lst[w]);
-          G.tn.settings.mosaicCalcFactor('l1', lst[w]);
-        }
-      }
-			// retrieve responsive mosaic definition for level l1
-      for( var w = 0; w < lst.length; w++ ) {
-				if( G.O['galleryL1Mosaic' + lst[w].toUpperCase()] != undefined ) {
-          G.tn.settings.mosaic.l1[lst[w]] = JSON.parse(JSON.stringify( G.O['galleryL1Mosaic' + lst[w].toUpperCase()] ));
-          G.tn.settings.mosaicCalcFactor('l1', lst[w]);
-        }
-      }
-      
-      G.O.imageTransition = G.O.imageTransition.toUpperCase();
-
-      G.layout.SetEngine();
-      
-      // init plugins
-      switch( G.O.kind ) {
-        // MARKUP / API
-        case '':
-          break;
-        // JSON, Flickr, Picasa, ...
-        default:
-        jQuery.nanogallery2['data_' + G.O.kind](G, 'Init' );
-      }
-
-    }
-
-    // HOVER EFFECTS
-    function ThumbnailHoverEffectExtract( name, effect) {
-      var easings = [ 'easeInQuad', 'easeOutQuad', 'easeInOutQuad', 'easeInCubic', 'easeOutCubic', 'easeInOutCubic', 'easeInQuart', 'easeOutQuart', 'easeInOutQuart', 'easeInQuint', 'easeOutQuint', 'easeInOutQuint', 'easeInSine', 'easeOutSine', 'easeInOutSine', 'easeInExpo', 'easeOutExpo', 'easeInOutExpo', 'easeInCirc', 'easeOutCirc', 'easeInOutCirc', 'easeOutBounce', 'easeInBack', 'easeOutBack', 'easeInOutBack', 'elastic', 'bounce'];
-    
-      var sp = name.split('_');
-      if( sp.length >= 4 ) {
-        // var oDef=NewTHoverEffect();
-        effect.name = '';
-        effect.type = sp[1];
-        effect.from = sp[2];
-        effect.to = sp[3];
-        if( sp.length >= 5 ) {
-          // effect.duration=sp[4];
-
-          for( var n = 4; n < sp.length; n++ ) {
-            var v = sp[n];
-            
-            // check if an easing name
-            var foundEasing = false;
-            for( var e = 0; e < easings.length; e++) {
-              if( v == easings[e] ) {
-                foundEasing = true;
-                effect.easing = v;
-                break;
-              }
-            }
-            if( foundEasing === true ) {
-              continue;
-            }
-            
-            v = v.toUpperCase();
-            
-            if( v == 'HOVERIN' ) {
-              effect.hoverout = false;
-              continue;
-            }
-            if( v == 'HOVEROUT' ) {
-              effect.hoverin = false;
-              continue;
-            }
-            
-            if( v == 'KEYFRAME' ) {
-              effect.firstKeyframe = false;
-              continue;
-            }
-            
-            var num = parseInt(v.replace(/[^0-9\.]/g, ''), 10);   // extract a number if one exists
-
-            if( num > 0 ) {
-              // the string contains a numbers > 0
-              if( v.indexOf('DURATION') >= 0 ) {
-                effect.duration = num;
-                continue;
-              }
-              if( v.indexOf('DURATIONBACK') >= 0 ) {
-                effect.durationBack = num;
-                continue;
-              }
-              if( v.indexOf('DELAY') >= 0 ) {
-                effect.delay = num;
-                continue;
-              }
-              if( v.indexOf('DELAYBACK') >= 0 ) {
-                effect.delayBack = num;
-                continue;
-              }
-              
-              // no parameter name found -> default is duration
-              effect.duration = num;
-            }
-          }
-        }        
-        effect.element=ThumbnailOverEffectsGetCSSElement(sp[0], effect.type);
-        
-      }
-      else {
-        effect.name = name;
-        // NanoAlert(G, 'incorrect parameter for "thumbnailHoverEffect": ' + name);
-        // return null;
-      }
-      return effect;
-    }
-    
-    
-    function ThumbnailOverEffectsGetCSSElement( element, property ) {
-        
-         var elts = {
-          'image':        '.nGY2GThumbnailImage',
-          'thumbnail':    '.nGY2GThumbnail',
-          'label':        '.nGY2GThumbnailLabel',
-          'title':        '.nGY2GThumbnailTitle',
-          'description':  '.nGY2GThumbnailDescription',
-          'tools':        '.nGY2GThumbnailIcons',
-          'customlayer':  '.nGY2GThumbnailCustomLayer',
-          'default':      'nGY2GThumbnailImage'
-        };
-        return (elts[element] || elts['default']);
-        
-
-    }
-    
-    // convert preset hover effects (nanoGALLERY) to new ones (nanogallery2)
-    function ThumbnailOverEffectsPreset( effects ) {
-
-      // COMPATIBILITY WITH nanoGALLERY
-      // OK:
-      //  'borderLighter', 'borderDarker', 'scale120', 'labelAppear', 'labelAppear75', 'labelOpacity50', 'scaleLabelOverImage'
-      //  'overScale', 'overScaleOutside', 'descriptionAppear'
-      //  'slideUp', 'slideDown', 'slideRight', 'slideLeft'
-      //  'imageScale150', 'imageScaleIn80', 'imageScale150Outside', 'imageSlideUp', 'imageSlideDown', 'imageSlideRight', 'imageSlideLeft'
-      //  'labelSlideUpTop', 'labelSlideUp', 'labelSlideDown', 'descriptionSlideUp'
-      // KO:
-      //  'labelSplit4', 'labelSplitVert', 'labelAppearSplit4', 'labelAppearSplitVert' 
-      // TODO:
-      //  'rotateCornerBL', 'rotateCornerBR', 'imageSplit4', 'imageSplitVert', 'imageRotateCornerBL', 'imageRotateCornerBR', 'imageFlipHorizontal', 'imageFlipVertical'
-
-    
-   
-      var newEffects=[];
-      for( var i=0; i< effects.length; i++ ) {
-        switch( effects[i].name.toUpperCase() ) {
-          case 'BORDERLIGHTER':
-            var rgb = ColorHelperToRGB(GalleryThemeGetCurrent().thumbnail.borderColor);
-            name = 'thumbnail_borderColor_'+rgb+'_'+ShadeBlendConvert(0.5, rgb );
-            newEffects.push(ThumbnailHoverEffectExtract(name, effects[i]));
-            break;
-
-          case 'BORDERDARKER':
-            var rgb = ColorHelperToRGB(GalleryThemeGetCurrent().thumbnail.borderColor);
-            name = 'thumbnail_borderColor_'+rgb+'_'+ShadeBlendConvert(-0.5, rgb );
-            newEffects.push(ThumbnailHoverEffectExtract(name, effects[i]));
-            break;
-
-          case 'SCALE120':
-            newEffects.push(ThumbnailHoverEffectExtract('thumbnail_scale_1.00_1.20', effects[i]));
-            break;
-          case 'LABELAPPEAR':
-          case 'LABELAPPEAR75':
-            newEffects.push(ThumbnailHoverEffectExtract('label_opacity_0.00_1.00', effects[i]));
-            break;
-          case 'TOOLSAPPEAR':
-            newEffects.push(ThumbnailHoverEffectExtract('tools_opacity_0_1', effects[i]));
-            break;
-          case 'TOOLSSLIDEDOWN':
-            newEffects.push(ThumbnailHoverEffectExtract('tools_translateY_-100%_0%', effects[i]));
-            break;
-          case 'TOOLSSLIDEUP':
-            newEffects.push(ThumbnailHoverEffectExtract('tools_translateY_100%_0%', effects[i]));
-            break;
-          case 'LABELOPACITY50':
-            newEffects.push(ThumbnailHoverEffectExtract('label_opacity_1.00_0.50', effects[i]));
-            break;
-          case 'LABELSLIDEUPTOP':
-          case 'LABELSLIDEUP':
-            newEffects.push(ThumbnailHoverEffectExtract('label_translateY_100%_0%', effects[i]));
-            break;
-          case 'LABELSLIDEDOWN':
-            newEffects.push(ThumbnailHoverEffectExtract('label_translateY_-100%_0%', effects[i]));
-            break;
-          case 'SCALELABELOVERIMAGE':
-            newEffects.push(ThumbnailHoverEffectExtract('label_scale_0.00_1.00', effects[i]));
-            var n = cloneJSObject(effects[i]);
-            newEffects.push(ThumbnailHoverEffectExtract('image_scale_1.00_0.00', n));
-            break;
-          case 'OVERSCALE':
-          case 'OVERSCALEOUTSIDE':
-            name = 'label_scale_0_100';
-            newEffects.push(ThumbnailHoverEffectExtract('label_scale_2.00_1.00', effects[i]));
-            var n = cloneJSObject(effects[i]);
-            newEffects.push(ThumbnailHoverEffectExtract('label_opacity_0.00_1.00', n));
-            n = cloneJSObject(effects[i]);
-            newEffects.push(ThumbnailHoverEffectExtract('image_scale_1.00_0.00', n));
-            n = cloneJSObject(effects[i]);
-            newEffects.push(ThumbnailHoverEffectExtract('image_opacity_1.00_0.00', n));
-            break;
-          case 'DESCRIPTIONAPPEAR':
-            newEffects.push(ThumbnailHoverEffectExtract('description_opacity_0_1', effects[i]));
-            break;
-          case 'SLIDERIGHT':
-            newEffects.push(ThumbnailHoverEffectExtract('image_translateX_0%_100%', effects[i]));
-            newEffects.push(ThumbnailHoverEffectExtract('label_translateX_-100%_0%', cloneJSObject(effects[i])));
-            break;
-          case 'SLIDELEFT':
-            newEffects.push(ThumbnailHoverEffectExtract('image_translateX_0%_-100%', effects[i]));
-            newEffects.push(ThumbnailHoverEffectExtract('label_translateX_100%_0%', cloneJSObject(effects[i])));
-            break;
-          case 'SLIDEUP':
-            newEffects.push(ThumbnailHoverEffectExtract('image_translateY_0%_-100%', effects[i]));
-            newEffects.push(ThumbnailHoverEffectExtract('label_translateY_100%_0%', cloneJSObject(effects[i])));
-            break;
-          case 'SLIDEDOWN':
-            newEffects.push(ThumbnailHoverEffectExtract('image_translateY_0%_100%', effects[i]));
-            newEffects.push(ThumbnailHoverEffectExtract('label_translateY_-100%_0%', cloneJSObject(effects[i])));
-            break;
-          case 'IMAGESCALE150':
-          case 'IMAGESCALE150OUTSIDE':
-            newEffects.push(ThumbnailHoverEffectExtract('image_scale_1.00_1.50', effects[i]));
-            break;
-          case 'IMAGESCALEIN80':
-            newEffects.push(ThumbnailHoverEffectExtract('image_scale_1.20_1.00', effects[i]));
-            break;
-          case 'IMAGESLIDERIGHT':
-            newEffects.push(ThumbnailHoverEffectExtract('image_translateX_0%_100%', effects[i]));
-            break;
-          case 'IMAGESLIDELEFT':
-            newEffects.push(ThumbnailHoverEffectExtract('image_translateX_0%_-100%', effects[i]));
-            break;
-          case 'IMAGESLIDEUP':
-            newEffects.push(ThumbnailHoverEffectExtract('image_translateY_0%_-100%', effects[i]));
-            break;
-          case 'IMAGESLIDEDOWN':
-            newEffects.push(ThumbnailHoverEffectExtract('image_translateY_0%_100%', effects[i]));
-            break;
-          case 'LABELSLIDEUP':
-          case 'LABELSLIDEUPTOP':
-            newEffects.push(ThumbnailHoverEffectExtract('label_translateY_100%_0%', effects[i]));
-            break;
-          case 'LABELSLIDEUPDOWN':
-            newEffects.push(ThumbnailHoverEffectExtract('label_translateY_0%_100%', effects[i]));
-            break;
-          case 'DESCRIPTIONSLIDEUP':
-            newEffects.push(ThumbnailHoverEffectExtract('description_translateY_110%_0%', effects[i]));
-            break;
-
-          case 'IMAGEBLURON':
-            newEffects.push(ThumbnailHoverEffectExtract('image_blur_2.00px_0.00px', effects[i]));
-            break;
-          case 'IMAGEBLUROFF':
-            newEffects.push(ThumbnailHoverEffectExtract('image_blur_0.00px_2.00px', effects[i]));
-            break;
-          case 'IMAGEGRAYON':
-            newEffects.push(ThumbnailHoverEffectExtract('image_grayscale_0%_100%', effects[i]));
-            break;
-          case 'IMAGEGRAYOFF':
-            newEffects.push(ThumbnailHoverEffectExtract('image_grayscale_100%_0%', effects[i]));
-            break;
-          case 'IMAGESEPIAON':
-            newEffects.push(ThumbnailHoverEffectExtract('image_sepia_100%_1%', effects[i]));
-            break;
-          case 'IMAGESEPIAOFF':
-            newEffects.push(ThumbnailHoverEffectExtract('image_sepia_1%_100%', effects[i]));
-            break;
-            
-          default:
-            newEffects.push(effects[i]);
-            break;
-        }
-      }
-      
-      return newEffects;
-    }
-
-
-    // Thumbnail hover effect definition
-    function NewTHoverEffect() {
-      var oDef={ 
-          name:           '',
-          element:        '',               // element class
-          type:           '',               
-          from:           '',               // start value
-          to:             '',               // end value
-          hoverin:        true,
-          hoverout:       true,
-          firstKeyframe:  true,
-          delay:          0,
-          delayBack:      0,
-          duration:       400,
-          durationBack:   300,
-          easing:         'easeOutQuart',
-          easingBack:     'easeOutQuart',
-          animParam:      null
-        };
-      return oDef;
-    }
-
-    function NewTBuildInit() {
-      // to set CSS properties
-      var oDef={ element: '', property: '', value: '' };
-      return oDef;
-    }
-
-    
-    function ThumbnailStyle( cfg, level) {
-
-      switch( cfg.position ){
-        case 'onBottom' :
-          G.tn.style[level]['label'] = 'bottom:0; ';
-          break;
-        case 'right' :
-          switch( cfg.valign ) {
-              case 'top':
-                G.tn.style[level]['label'] = 'top:0; position:absolute; left: 50%;';
-                break;
-              case 'middle':
-                G.tn.style[level]['label'] = 'top:0; bottom:0; left: 50%;';
-                G.tn.style[level]['title'] = 'position:absolute; bottom:50%;';
-                G.tn.style[level]['desc'] = 'position:absolute; top:50%;';
-                break;
-              case 'bottom':
-              default:
-                G.tn.style[level].label = 'bottom:0; position:absolute; left: 50%;';
-                G.tn.style[level].title = 'position:absolute;bottom:0;';
-                break;
-          }
-          break;
-        case 'custom':
-          break;
-        default:
-        case 'overImage' :
-          switch( cfg.valign ) {
-              case 'top':
-                G.tn.style[level]['label'] = 'top:0; position:absolute;';
-                break;
-              case 'middle':
-                G.tn.style[level]['label'] = 'top:0; bottom:0;';
-                G.tn.style[level]['title'] = 'position:absolute; bottom:50%;';
-                G.tn.style[level]['desc'] = 'position:absolute; top:50%;';
-                break;
-              case 'bottom':
-              default:
-                // G.O.thumbnailLabel.position = 'overImageOnBottom';
-                G.tn.style[level].label = 'bottom:0; position:absolute;';
-                break;
-          }
-        
-        // case 'overImageOnTop' :
-          // G.tn.style[level]['label'] = 'top:0; position:absolute;';
-          // break;
-        // case 'overImageOnMiddle' :
-          // G.tn.style[level]['label'] = 'top:0; bottom:0;';
-          // G.tn.style[level]['title'] = 'position:absolute; bottom:50%;';
-          // G.tn.style[level]['desc'] = 'position:absolute; top:50%;';
-          // break;
-        // case 'right' :
-        // case 'custom' :
-          // break;
-        // case 'overImageOnBottom' :
-        // default :
-          // G.O.thumbnailLabel.position = 'overImageOnBottom';
-          // G.tn.style[level].label = 'bottom:0; position:absolute;';
-          // break;
-      }
-      
-      // if( G.layout.engine != 'CASCADING' ) {
-      if( cfg.position != 'onBottom' ) {
-        // multi-line
-        if( cfg.titleMultiLine ) {
-          G.tn.style[level]['title'] += 'white-space:normal;';
-        }
-        if( cfg.descriptionMultiLine ) {
-          G.tn.style[level]['desc'] += 'white-space:normal;';
-        }
-      }
-      
-      // horizontal alignement
-      switch( cfg.align ) {
-        case 'right':
-            G.tn.style[level].label += 'text-align:right;';
-          break;
-        case 'left':
-            G.tn.style[level].label += 'text-align:left;';
-          break;
-        default:
-            G.tn.style[level].label += 'text-align:center;';
-          break;
-      }
-      
-      
-      if( cfg.titleFontSize != undefined && cfg.titleFontSize != '' ) {
-        G.tn.style[level].title += 'font-size:' + cfg.titleFontSize + ';';
-      }
-      if( cfg.descriptionFontSize != undefined && cfg.descriptionFontSize != '' ) {
-        G.tn.style[level].desc += 'font-size:' + cfg.descriptionFontSize + ';';
-      }
-      
-      if( cfg.displayDescription == false ) {
-        G.tn.style[level].desc += 'display:none;';
-      }
-    }
-
-    
-    // cache some thumbnail settings
-    function ThumbnailDefCaches() {
-      // thumbnail content CSS styles
-
-      // settings for level L1 and LN
-      ThumbnailStyle( G.O.thumbnailLabel, 'lN');
-      if( G.O.thumbnailL1Label !== undefined ) {
-        ThumbnailStyle( G.O.thumbnailL1Label, 'l1');
-      }
-      else {
-        ThumbnailStyle( G.O.thumbnailLabel, 'l1');
-      }
-
-      if( G.O.thumbnailL1Label && G.O.thumbnailL1Label.display ) {
-        // settings for level L1
-        ThumbnailStyle( G.O.thumbnailL1Label, 'l1');
-      }
-      
-
-      // default thumbnail sizes levels l1 and lN
-      var lst=['xs','sm','me','la','xl'];
-      for( var i = 0; i < lst.length; i++ ) {
-        var w = G.tn.settings.width.lN[lst[i]];
-        if( w != 'auto' ) {
-          G.tn.defaultSize.width.lN[lst[i]] = w;
-          G.tn.defaultSize.width.l1[lst[i]] = w;
-        }
-        else {
-          var h = G.tn.settings.height.lN[lst[i]];
-          G.tn.defaultSize.width.lN[lst[i]] = h;      // dynamic width --> set height value as default for the width
-          G.tn.defaultSize.width.l1[lst[i]] = h;      // dynamic width --> set height value as default
-        }
-      }
-      for( var i = 0; i < lst.length; i++ ) {
-        var h = G.tn.settings.height.lN[lst[i]];
-        if( h != 'auto' ) {
-          // grid or justified layout
-          G.tn.defaultSize.height.lN[lst[i]] = h;  //+G.tn.labelHeight.get();
-          G.tn.defaultSize.height.l1[lst[i]] = h;  //+G.tn.labelHeight.get();
-        }
-        else {
-          var w = G.tn.settings.width.lN[lst[i]];
-          G.tn.defaultSize.height.lN[lst[i]] = w;      // dynamic height --> set width value as default for the height
-          G.tn.defaultSize.height.l1[lst[i]] = w;      // dynamic height --> set width value as default
-        }
-      }
-
-      // default thumbnail sizes levels l1
-      for( var i = 0; i < lst.length; i++ ) {
-        var w = G.tn.settings.width.l1[lst[i]];
-        if( w != 'auto' ) {
-          G.tn.defaultSize.width.l1[lst[i]] = w;
-        }
-        else {
-          var h = G.tn.settings.height.l1[lst[i]];
-          G.tn.defaultSize.width.l1[lst[i]] = h;      // dynamic width --> set height value as default
-        }
-      }
-      for( var i = 0; i < lst.length; i++ ) {
-        var h = G.tn.settings.height.l1[lst[i]];
-        if( h != 'auto' ) {
-          // grid or justified layout
-          G.tn.defaultSize.height.l1[lst[i]] = h;  //+G.tn.labelHeight.get();
-        }
-        else {
-          var w = G.tn.settings.width.l1[lst[i]];
-          G.tn.defaultSize.height.l1[lst[i]]= w ;      // dynamic height --> set width value as default
-        }
-      }
-      
-    }
-    
-
-    //
-    function GalleryThemeGetCurrent() {
-
-      var cs=null;
-      switch(toType(G.O.galleryTheme)) {
-        case 'object':    // user custom color scheme object 
-          cs = G.galleryTheme_dark;  // default color scheme
-          jQuery.extend(true,cs,G.O.galleryTheme);
-          break;
-        case 'string':    // name of an internal defined color scheme
-          switch( G.O.galleryTheme ) {
-            case 'light':
-              cs = G.galleryTheme_light;
-              break;
-            case 'default':
-            case 'dark':
-            case 'none':
-            default:
-              cs = G.galleryTheme_dark;
-          }
-          break;
-        default:
-          cs = G.galleryTheme_dark;
-      }
-      return cs;
-    }
-    
-    // ##### BREADCRUMB/THUMBNAIL COLOR SCHEME #####
-    function SetGalleryTheme() {
-    
-      if( typeof G.O.colorScheme  !== 'undefined' ) {
-        G.O.galleryTheme = G.O.colorScheme;
-      }
-
-      var cs = null;
-      var galleryTheme = '';
-      switch(toType(G.O.galleryTheme)) {
-        case 'object':    // user custom color scheme object 
-          cs = G.galleryTheme_dark;  // default color scheme
-          jQuery.extend(true,cs,G.O.galleryTheme);
-          galleryTheme='nanogallery_gallerytheme_custom_' + G.baseEltID;
-          break;
-        case 'string':    // name of an internal defined color scheme
-          switch( G.O.galleryTheme ) {
-            case 'light':
-              cs = G.galleryTheme_light;
-              galleryTheme='nanogallery_gallerytheme_light_' + G.baseEltID;
-              break;
-            case 'default':
-            case 'dark':
-            case 'none':
-            default:
-              cs = G.galleryTheme_dark;
-              galleryTheme='nanogallery_gallerytheme_dark_' + G.baseEltID;
-          }
-          break;
-        default:
-          NanoAlert(G, 'Error in galleryTheme parameter.');
-          return;
-      }
-
-      //var s1='.nanogallery_theme_'+G.O.theme+' ';
-      var s1='.' + galleryTheme + ' ';
-    
-      // navigation bar
-      var c = cs.navigationBar;
-      var s=s1+'.nGY2Navigationbar { background:'+c.background+'; }'+'\n';
-      if( c.border !== undefined && c.border !== '' ) { s+=s1+'.nGY2Navigationbar { border:'+c.border+'; }'+'\n'; }
-      if( c.borderTop !== undefined && c.borderTop !== '' ) { s+=s1+'.nGY2Navigationbar { border-top:'+c.borderTop+'; }'+'\n'; }
-      if( c.borderBottom !== undefined && c.borderBottom !== '' ) { s+=s1+'.nGY2Navigationbar { border-bottom:'+c.borderBottom+'; }'+'\n'; }
-      if( c.borderRight !== undefined && c.borderRight !== '' ) { s+=s1+'.nGY2Navigationbar { border-right:'+c.borderRight+'; }'+'\n'; }
-      if( c.borderLeft !== undefined && c.borderLeft !== '' ) { s+=s1+'.nGY2Navigationbar { border-left:'+c.borderLeft+'; }'+'\n'; }
-      
-      // navigation bar - breadcrumb
-      var c = cs.navigationBreadcrumb;
-      s+=s1+'.nGY2Breadcrumb { background:'+c.background+'; border-radius:'+c.borderRadius+'; }'+'\n';
-      s+=s1+'.nGY2Breadcrumb .oneItem  { color:'+c.color+'; }'+'\n';
-      s+=s1+'.nGY2Breadcrumb .oneItem:hover { color:'+c.colorHover+'; }'+'\n';
-
-      // navigation bar - tag filter
-      var c = cs.navigationFilter;
-      s+=s1+'.nGY2NavFilterUnselected { color:'+c.color+'; background:'+c.background+'; border-radius:'+c.borderRadius+'; }'+'\n';
-      s+=s1+'.nGY2NavFilterSelected { color:'+c.colorSelected+'; background:'+c.backgroundSelected+'; border-radius:'+c.borderRadius+'; }'+'\n';
-      s+=s1+'.nGY2NavFilterSelectAll { color:'+c.colorSelected+'; background:'+c.background+'; border-radius:'+c.borderRadius+'; }'+'\n';
-
-      // navigation bar - pagination next/previous
-      var c = cs.navigationPagination;
-      s+=s1+'.nGY2NavPagination { color:'+c.color+'; background:'+c.background+'; border-radius:'+c.borderRadius+'; }'+'\n';
-      s+=s1+'.nGY2NavPagination:hover { color:'+c.colorHover+'; }'+'\n';
-      
-      // thumbnails
-      var c = cs.thumbnail;
-      // s+=s1+'.nGY2GThumbnail { border-radius: '+c.borderRadius+'; background:'+c.background+'; border-color:'+c.borderColor+'; border-top-width:'+G.tn.opt.Get('borderVertical')+'px; border-right-width:'+G.tn.opt.Get('borderHorizontal')+'px; border-bottom-width:'+G.tn.opt.Get('borderVertical')+'px; border-left-width:'+G.tn.opt.Get('borderHorizontal')+'px;}'+'\n';
-      s+=s1+'.nGY2GThumbnail { border-radius: '+c.borderRadius+'; background:'+c.background+'; border-color:'+c.borderColor+'; }'+'\n';
-      s+=s1+'.nGY2GThumbnail_l1 { border-top-width:'+G.tn.opt.l1.borderVertical+'px; border-right-width:'+G.tn.opt.l1.borderHorizontal+'px; border-bottom-width:'+G.tn.opt.l1.borderVertical+'px; border-left-width:'+G.tn.opt.l1.borderHorizontal+'px;}'+'\n';
-      s+=s1+'.nGY2GThumbnail_lN { border-top-width:'+G.tn.opt.lN.borderVertical+'px; border-right-width:'+G.tn.opt.lN.borderHorizontal+'px; border-bottom-width:'+G.tn.opt.lN.borderVertical+'px; border-left-width:'+G.tn.opt.lN.borderHorizontal+'px;}'+'\n';
-      s+=s1+'.nGY2GThumbnailStack { background:'+c.stackBackground+'; }'+'\n';
-      // s+=s1+'.nGY2GThumbnailImage { background:'+cs.thumbnail.background+'; background-image:'+cs.thumbnail.backgroundImage+'; }'+'\n';
-      s+=s1+'.nGY2TnImgBack { background:'+c.background+'; background-image:'+c.backgroundImage+'; }'+'\n';
-      s+=s1+'.nGY2GThumbnailAlbumUp { background:'+c.background+'; background-image:'+c.backgroundImage+'; color:'+cs.thumbnail.titleColor+'; }'+'\n';
-      s+=s1+'.nGY2GThumbnailIconsFullThumbnail { color:'+c.titleColor+'; }\n';
-      s+=s1+'.nGY2GThumbnailLabel { background:'+c.labelBackground+'; opacity:'+c.labelOpacity+'; }'+'\n';
-      s+=s1+'.nGY2GThumbnailImageTitle  { color:'+c.titleColor+'; background-color:'+c.titleBgColor+'; '+(c.titleShadow =='' ? '': 'Text-Shadow:'+c.titleShadow+';')+' }'+'\n';
-      s+=s1+'.nGY2GThumbnailAlbumTitle { color:'+c.titleColor+'; background-color:'+c.titleBgColor+'; '+(c.titleShadow =='' ? '': 'Text-Shadow:'+c.titleShadow+';')+' }'+'\n';
-      s+=s1+'.nGY2GThumbnailDescription { color:'+c.descriptionColor+'; background-color:'+c.descriptionBgColor+'; '+(c.descriptionShadow =='' ? '': 'Text-Shadow:'+c.descriptionShadow+';')+' }'+'\n';
-
-      // thumbnails - icons
-      var c = cs.thumbnailIcon;
-      s+=s1+'.nGY2GThumbnailIcons { padding:'+c.padding+'; }\n';
-      s+=s1+'.nGY2GThumbnailIcon { color:'+c.color+'; '+(c.shadow =='' ? '': 'Text-Shadow:'+c.shadow+';')+' }\n';
-      s+=s1+'.nGY2GThumbnailIconTextBadge { background-color:'+c.color+'; }\n';
-      
-      // gallery pagination -> dot/rectangle based
-      var c = cs.pagination;
-      if( G.O.galleryPaginationMode != 'NUMBERS' ) {
-        s+=s1+'.nGY2paginationDot { border:'+c.shapeBorder+'; background:'+c.shapeColor+';}\n';
-        s+=s1+'.nGY2paginationDotCurrentPage { border:'+c.shapeBorder+'; background:'+c.shapeSelectedColor+';}\n';
-        s+=s1+'.nGY2paginationRectangle { border:'+c.shapeBorder+'; background:'+c.shapeColor+';}\n';
-        s+=s1+'.nGY2paginationRectangleCurrentPage { border:'+c.shapeBorder+'; background:'+c.shapeSelectedColor+';}\n';
-      } else {
-        s+=s1+'.nGY2paginationItem { background:'+c.background+'; color:'+c.color+'; border-radius:'+c.borderRadius+'; }\n';
-        s+=s1+'.nGY2paginationItemCurrentPage { background:'+c.background+'; color:'+c.color+'; border-radius:'+c.borderRadius+'; }\n';
-        s+=s1+'.nGY2PaginationPrev { background:'+c.background+'; color:'+c.color+'; border-radius:'+c.borderRadius+'; }\n';
-        s+=s1+'.nGY2PaginationNext { background:'+c.background+'; color:'+c.color+'; border-radius:'+c.borderRadius+'; }\n';
-        s+=s1+'.nGY2paginationItemCurrentPage { background:'+c.backgroundSelected+'; }\n';
-      }
-      
-      // gallery more button
-      var c = cs.thumbnail;
-      // s+=s1+'.nGY2GalleryMoreButtonAnnotation { background:'+c.background+'; border-color:'+c.borderColor+'; border-top-width:'+G.O.thumbnailBorderVertical+'px; border-right-width:'+G.O.thumbnailBorderHorizontal+'px; border-bottom-width:'+G.O.thumbnailBorderVertical+'px; border-left-width:'+G.O.thumbnailBorderHorizontal+'px;}\n';
-      s+=s1+'.nGY2GalleryMoreButtonAnnotation { background:'+c.background+'; border-color:'+c.borderColor+'; border-top-width: 1px; border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px;}\n';
-      s+=s1+'.nGY2GalleryMoreButtonAnnotation  { color:'+c.titleColor+'; '+(c.titleShadow =='' ? '': 'Text-Shadow:'+c.titleShadow)+'; }\n';
-      
-      jQuery('head').append('<style id="ngycs_'+G.baseEltID+'">'+s+'</style>');
-      G.$E.base.addClass(galleryTheme);
-
-    };
-    
-    // ##### VIEWER COLOR SCHEME #####
-    function SetViewerTheme( ) {
-
-      if( G.VOM.viewerTheme != '' ) {
-        G.VOM.$baseCont.addClass(G.VOM.viewerTheme);
-        return;
-      }
-
-      if( typeof G.O.colorSchemeViewer  !== 'undefined' ) {
-        G.O.viewerTheme = G.O.colorSchemeViewer;
-      }
-
-      var cs=null;
-      switch(toType(G.O.viewerTheme)) {
-        case 'object':    // user custom color scheme object 
-          cs = G.viewerTheme_dark;
-          jQuery.extend(true, cs, G.O.viewerTheme);
-          G.VOM.viewerTheme = 'nanogallery_viewertheme_custom_' + G.baseEltID;
-          break;
-        case 'string':    // name of an internal defined color scheme
-          switch( G.O.viewerTheme ) {
-            case 'none':
-              return;
-              break;
-            case 'light':
-              cs = G.viewerTheme_light;
-              G.VOM.viewerTheme = 'nanogallery_viewertheme_light_' + G.baseEltID;
-              break;
-            case 'dark':
-            case 'default':
-              cs = G.viewerTheme_dark;
-              G.VOM.viewerTheme = 'nanogallery_viewertheme_dark_' + G.baseEltID;
-              break;
-          }
-          break;
-        default:
-          NanoAlert(G, 'Error in viewerTheme parameter.');
-          return;
-      }
-
-      var s1 = '.' + G.VOM.viewerTheme + ' ';
-      var s = s1 + '.nGY2Viewer { background:' + cs.background + '; }'+'\n';
-      s += s1 + '.nGY2Viewer .toolbarBackground { background:' + cs.barBackground + '; }'+'\n';
-      s += s1 + '.nGY2Viewer .toolbar { border:' + cs.barBorder + '; color:' + cs.barColor + '; }'+'\n';
-      s += s1 + '.nGY2Viewer .toolbar .previousButton:after { color:' + cs.barColor + '; }'+'\n';
-      s += s1 + '.nGY2Viewer .toolbar .nextButton:after { color:' + cs.barColor + '; }'+'\n';
-      s += s1 + '.nGY2Viewer .toolbar .closeButton:after { color:' + cs.barColor + '; }'+'\n';
-      s += s1 + '.nGY2Viewer .toolbar .label .title { color:' + cs.barColor + '; }'+'\n';
-      s += s1 + '.nGY2Viewer .toolbar .label .description { color:' + cs.barDescriptionColor + '; }'+'\n';
-      jQuery('head').append('<style>' + s + '</style>');
-      G.VOM.$baseCont.addClass(G.VOM.viewerTheme);
-    };
-
-    
-    
-    /** @function SetPolyFills */
-    function SetPolyFills() {
-
-      // POLYFILL FOR BIND function --> for older Safari mobile
-      // found on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind#Compatibility
-      if (!Function.prototype.bind) {
-        Function.prototype.bind = function (oThis) {
-          if (typeof this !== "function") {
-            // closest thing possible to the ECMAScript 5
-            // internal IsCallable function
-            throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
-          }
-
-          var aArgs = Array.prototype.slice.call(arguments, 1), 
-              fToBind = this, 
-              fNOP = function () {},
-              fBound = function () {
-                return fToBind.apply(this instanceof fNOP && oThis
-                       ? this
-                       : oThis,
-                       aArgs.concat(Array.prototype.slice.call(arguments)));
-              };
-
-          fNOP.prototype = this.prototype;
-          fBound.prototype = new fNOP();
-
-          return fBound;
-        };
-      }
-
-      // requestAnimationFrame polyfill by Erik M�ller. fixes from Paul Irish and Tino Zijdel
-      // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
-      // http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
-      // MIT license
-      (function() {
-        var lastTime = 0;
-        var vendors = ['ms', 'moz', 'webkit', 'o'];
-        for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
-          window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
-          window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
-        }
-        if (!window.requestAnimationFrame)
-          window.requestAnimationFrame = function(callback, element) {
-            var currTime = new Date().getTime();
-            var timeToCall = Math.max(0, 16 - (currTime - lastTime));
-            var id = window.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall);
-            lastTime = currTime + timeToCall;
-            return id;
-          };
-       
-        if (!window.cancelAnimationFrame)
-          window.cancelAnimationFrame = function(id) {
-            clearTimeout(id);
-          };
-      }());
-      
-      // array.removeIf -> removes items from array base on a function's result
-      Array.prototype.removeIf = function(callback) {
-        var i = this.length;
-        while (i--) {
-          if (callback(this[i], i)) {
-            this.splice(i, 1);
-          }
-        }
-      };  
+  jQuery.fn.nanogallery2 = function (args, option, value) {
 
-      // IE11 for startsWith 
-      // thanks to @lichtamberg - https://github.com/lichtamberg
-      if (!String.prototype.startsWith) {
-        String.prototype.startsWith = function(searchString, position) {
-          position = position || 0;
-          return this.indexOf(searchString, position) === position;
-        };
-      }
-      
-    }
-    
-    
-    // Gallery clicked or toolbar touched -> retrieve & execute action
-    function GalleryClicked(e) {
-    
-      var r = GalleryEventRetrieveElementl(e, false);
-
-      if( r.GOMidx == -1 ) { return 'exit'; }
-      
-      var idx = G.GOM.items[r.GOMidx].thumbnailIdx;
-      if( G.GOM.slider.hostIdx == r.GOMidx ) {
-        idx = G.GOM.items[G.GOM.slider.currentIdx].thumbnailIdx;
-      }
-      switch( r.action ) {
-        case 'OPEN':
-          ThumbnailOpen(idx, false);
-          return 'exit';
-          break;
-        case 'DISPLAY':
-          // used the display icon (ignore if selection mode)
-          ThumbnailOpen(idx, true);
-          return 'exit';
-          break;
-        case 'TOGGLESELECT':
-          ThumbnailSelectionToggle(idx);
-          return 'exit';
-          break;
-        case 'SHARE':
-          PopupShare(idx);
-          return 'exit';
-          break;
-        case 'DOWNLOAD':
-          DownloadImage(idx);
-          return 'exit';
-          break;
-        case 'INFO':
-          ItemDisplayInfo(G.I[idx]);
-          return 'exit';
-          break;
-        case 'SHOPPINGCART':
-          AddToCart(idx, 'gallery');
-          return 'exit';
-          break;
-        default:
-          // all other actions (custom1..10, or anything else)
-          var fu = G.O.fnThumbnailToolCustAction;
-          if( fu !== null ) {
-            typeof fu == 'function' ? fu(r.action, G.I[idx]) : window[fu](r.action, G.I[idx]);
-          }
-          break;
-      }
-    }
-
-    // Download an image
-    function DownloadImage(idx) {
-      if( G.I[idx].mediaKind != 'img' ) { return; }
-
-      
-      var url = G.I[idx].src;
-
-      if( G.I[idx].downloadURL != undefined && G.I[idx].downloadURL != '' ) {
-        url = G.I[idx].downloadURL;
-      }
-      
-      var a = document.createElement('a');
-      a.href = url;
-      // a.download = url.split('.').pop();
-      a.download = url.split('/').pop();
-      a.target = '_blank';
-      a.style.display = 'none';
-      document.body.appendChild(a);
-      a.click();
-      document.body.removeChild(a);      
-      
-    }
-    
-    // add one image to the shopping cart
-    function AddToCart( idx, source ) {
-      // increment quantity if already in shopping cart
-      var found=false;
-      for( var i=0; i<G.shoppingCart.length; i++ ) {
-        if( G.shoppingCart[i].idx == idx ) {
-          G.shoppingCart[i].qty++;
-          ThumbnailBuildToolbarOneCartUpdate( G.I[idx] );
-          
-          var fu = G.O.fnShoppingCartUpdated;
-          if( fu !== null ) {
-            typeof fu == 'function' ? fu(G.shoppingCart, G.I[idx], source) : window[fu](G.shoppingCart, G.I[idx], source);
-          }
-          TriggerCustomEvent('shoppingCartUpdated');
-          return;
-        }
-      }
-      
-      // add to shopping cart
-      if( !found) {
-        G.shoppingCart.push( { idx:idx, ID:G.I[idx].GetID(), qty:1} );
-        ThumbnailBuildToolbarOneCartUpdate(G.I[idx]);
-
-        var fu=G.O.fnShoppingCartUpdated;
-        if( fu !== null ) {
-          typeof fu == 'function' ? fu(G.shoppingCart, G.I[idx], source) : window[fu](G.shoppingCart, G.I[idx], source);
-        }
-        TriggerCustomEvent('shoppingCartUpdated');
-      }
-    }
-    
-
-    // All thumbnails are set to unselected
-    function ThumbnailSelectionClear() {
-      G.GOM.nbSelected = 0;
-      for( var i = 0, nbTn = G.GOM.items.length; i < nbTn ; i++ ) {
-        var item = G .I[G.GOM.items[i].thumbnailIdx];
-        if( item.selected ) {
-          item.selected = false;
-          var fu = G.O.fnThumbnailSelection;
-          if( fu !== null ) {
-            typeof fu == 'function' ? fu(item.$elt, item, G.I) : window[fu](item.$elt, item, G.I);
-          }
-        }
-        item.selected = false;
-      }
-    }
-    
-    function ThumbnailSelectionToggle( idx ){
-      var item = G.I[idx];
-      if( item.selected === true ) {
-        ThumbnailSelectionSet(item, false);
-        G.GOM.nbSelected--;
-        TriggerCustomEvent('itemUnSelected');
-      }
-      else {
-        ThumbnailSelectionSet(item, true);
-        G.GOM.nbSelected++;
-        TriggerCustomEvent('itemSelected');
-      }
-    }
-    
-    
-    // this replaces ThumbnailSelection()
-    function ThumbnailSelectionSet(item, selected ){
-      
-      item.selected = selected;
-      
-      ThumbnailSelectionSetIcon( item );
-      
-      // called when the selection status of an item changed
-      var fu=G.O.fnThumbnailSelection;
-      if( fu !== null ) {
-        typeof fu == 'function' ? fu(item.$elt, item, G.I) : window[fu](item.$elt, item, G.I);
-      }
-    
-    }
-    
-    function ThumbnailSelectionSetIcon( item ) {
-      if( item.$elt == null ) {
-        // thumbnail is not built
-        return;
-      }
-      var $sub = item.$getElt('.nGY2GThumbnail');
-      var $icon = item.$getElt('.nGY2GThumbnailIconImageSelect');
-      if( item.selected === true) {
-        $sub.addClass('nGY2GThumbnailSubSelected');
-        $icon.addClass('nGY2ThumbnailSelected');
-        $icon.removeClass('nGY2ThumbnailUnselected');
-        $icon.html(G.O.icons.thumbnailSelected);
-      }
-      else {
-        $sub.removeClass('nGY2GThumbnailSubSelected');
-        $icon.removeClass('nGY2ThumbnailSelected');
-        $icon.addClass('nGY2ThumbnailUnselected');
-        $icon.html(G.O.icons.thumbnailUnselected);
-      }
-    }
-    
-    
-    // display a modal popup for sharing image/album
-    function PopupShare(idx) {
-    
-      // SEE SAMPLES: https://gist.github.com/chrisjlee/5196139
-      // https://github.com/Julienh/Sharrre
-    
-      var item=G.I[idx];
-
-      var currentURL=document.location.protocol + '//' + document.location.hostname + document.location.pathname;
-      var newLocationHash = '#nanogallery/' + G.baseEltID + '/';
-      if( item.kind == 'image' ) {
-        newLocationHash += item.albumID + '/' + item.GetID();
-      }
-      else {
-        newLocationHash += item.GetID();
-      }
-    
-      var content = '<br><br>';
-      content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="facebook">'   + G.O.icons.shareFacebook +   '</div>';
-      content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="pinterest">'  + G.O.icons.sharePinterest +  '</div>';
-      content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="tumblr">'     + G.O.icons.shareTumblr +     '</div>';
-      content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="twitter">'    + G.O.icons.shareTwitter +    '</div>';
-      // content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="googleplus">' + G.O.icons.shareGooglePlus + '</div>';
-      content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="vk">'         + G.O.icons.shareVK +         '</div>';
-      content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="mail">'       + G.O.icons.shareMail +       '</div>';
-      content += '<div class="nGY2PopupOneItem" style="text-align:center;"></div>';
-      content += '<input class="nGY2PopupOneItemText" readonly type="text" value="' + currentURL+newLocationHash + '" style="width:100%;text-align:center;">';
-      content += '<br>';
-
-      currentURL = encodeURIComponent(document.location.protocol + '//' + document.location.hostname + document.location.pathname + newLocationHash);
-
-      var currentTitle = item.title;
-      var currentTn = item.thumbImg().src;
-      
-      
-      Popup('nanogallery2 - share to:', content, 'Center');
-      
-      G.popup.$elt.find('.nGY2PopupOneItem').on('click', function(e) {
-        e.stopPropagation();
-        
-        var shareURL = '';
-        var found = true;
-        switch(jQuery(this).attr('data-share').toUpperCase()) {
-          case 'FACEBOOK':
-            // <a name="fb_share" type="button" href="http://www.facebook.com/sharer.php?u={$url}&media={$imgPath}&description={$desc}" class="joinFB">Share Your Advertise</a>
-            //window.open("https://www.facebook.com/sharer.php?u="+currentURL,"","height=368,width=600,left=100,top=100,menubar=0");
-            shareURL = 'https://www.facebook.com/sharer.php?u=' + currentURL;
-            break;
-          case 'VK':
-            shareURL = 'http://vk.com/share.php?url=' + currentURL;
-            break;
-          case 'GOOGLEPLUS':
-            shareURL = "https://plus.google.com/share?url=" + currentURL;
-            break;
-          case 'TWITTER':
-            // shareURL="https://twitter.com/share?url="+currentURL+"&text="+currentTitle;
-            shareURL = 'https://twitter.com/intent/tweet?text=' + currentTitle + 'url=' + currentURL;
-            break;
-          case 'PINTEREST':
-            // shareURL='https://pinterest.com/pin/create/bookmarklet/?media='+currentTn+'&url='+currentURL+'&description='+currentTitle;
-            shareURL = 'https://pinterest.com/pin/create/button/?media=' + currentTn + '&url=' + currentURL + '&description=' + currentTitle;
-            break;
-          case 'TUMBLR':
-            //shareURL='https://www.tumblr.com/widgets/share/tool/preview?caption=<strong>'+currentTitle+'</strong>&tags=nanogallery2&url='+currentURL+'&shareSource=legacy&posttype=photo&content='+currentTn+'&clickthroughUrl='+currentURL;
-            shareURL = 'http://www.tumblr.com/share/link?url=' + currentURL + '&name=' + currentTitle;
-            break;
-          case 'MAIL':
-            shareURL = 'mailto:?subject=' + currentTitle + '&body=' + currentURL;
-            break;
-          default:
-            found = false;
-            break;
-        }
-        
-        if( found ) {
-          window.open(shareURL, "" , "height=550,width=500,left=100,top=100,menubar=0" );
-          G.popup.close();
-          // $popup.remove();
-        }
-        
-      });
-    }
-    
-    // build a modal popup
-    function Popup(title, content, align) {
-      var pp =  '<div class="nGY2Popup" style="opacity:0;"><div class="nGY2PopupContent' + align + '">';
-      pp    +=  '<div class="nGY2PopupCloseButton" style="font-size:0.9em;">' + G.O.icons.buttonClose + '</div>';
-      pp    +=  '<div class="nGY2PopupTitle">' + title + '</div>';
-      pp    +=  content;
-      pp    +=  '</div></div>';
-      
-      G.popup.$elt = jQuery(pp).appendTo('body');
-      setElementOnTop( G.VOM.$viewer, G.popup.$elt);
-      
-      G.popup.isDisplayed = true;
-      
-      var tweenable = new NGTweenable();
-      tweenable.tween({
-        from:       { o: 0, y: 100 },
-        to:         { o: 1, y: 0 },
-        easing:     'easeInOutSine',
-        duration:   250,
-        step:       function (state, att) {
-          G.popup.$elt[0].style.opacity = state.o;
-          G.popup.$elt[0].style[G.CSStransformName] = 'translateY(' + (state.y) + 'px)';
-        }
-      });
-      
-      G.popup.$elt.find('.nGY2PopupCloseButton').on('click', function(e) {
-        e.stopPropagation();
-        G.popup.close();
-      });
-      
-    }
-
-
-    function GalleryMouseEnter(e) {
-      if( !G.VOM.viewerDisplayed && G.GOM.albumIdx != -1 ) {
-        var r = GalleryEventRetrieveElementl(e, true);
-        // if( r.action == 'OPEN' && r.GOMidx != -1 ) {
-        if( r.GOMidx != -1 ) {
-          var target = e.target || e.srcElement;
-          // if( target.getAttribute('class') != 'nGY2GThumbnail' ) { return; }
-          ThumbnailHover(r.GOMidx);
-        }
-      }
-    }
-    
-    function GalleryMouseLeave(e) {
-      if( !G.VOM.viewerDisplayed && G.GOM.albumIdx != -1 ) {
-        var r = GalleryEventRetrieveElementl(e, true);
-        if( r.GOMidx != -1 ) {
-          var target = e.target || e.srcElement;
-          // if( target.getAttribute('class') != 'nGY2GThumbnail' ) { return; }
-          ThumbnailHoverOut(r.GOMidx);
-        }
-      }
-    }
-    
-    function GalleryEventRetrieveElementl( e, ignoreSubItems ) {
-      var r = { action: 'NONE', GOMidx: -1 };
-      
-      if( e == undefined ) {
-        return r;
-      }
-      var target = e.target || e.srcElement;
-      while( target != G.$E.conTnParent[0] ) {       // loop element parent up to find the thumbnail element
-        if( jQuery(target).hasClass('nGY2GThumbnail') ) {
-          if( r.action == 'NONE' ) {
-            r.action = 'OPEN';
-          }
-          r.GOMidx = jQuery(target).data('index');
-          return r;
-        }
-        // if( !ignoreSubItems && jQuery(target).hasClass('nGY2GThumbnailIcon') ) {
-        if( !ignoreSubItems ) {
-          var a = jQuery(target).data('ngy2action');
-          if( a != '' && a != undefined ) {
-            r.action = a;
-          }
-        }
-        if( target.parentNode == null ) {
-          return r;
-        }
-        target = target.parentNode;
-      }
-      return r;
-    }
-    
-
-    // OPEN ONE THUMBNAIL
-    function ThumbnailOpen( idx, ignoreSelected ) {
-      var item = G.I[idx];
-      
-      G.GOM.albumIdxLoading = idx;      // store idx -> may be used to display loader on album thumbnail
-
-      var fu = G.O.fnThumbnailClicked;
-      if( fu !== null ) {
-        typeof fu == 'function' ? fu(item.$elt, item) : window[fu](item.$elt, item);
-      }
-      
-      // open URL
-      if( item.destinationURL !== undefined && item.destinationURL.length > 0 ) {
-        window.location = item.destinationURL;
-        return;
-      }
-
-      switch( item.kind ) {
-        case 'image':
-          if( ignoreSelected === false && G.GOM.nbSelected > 0 ) {
-            ThumbnailSelectionToggle(idx);
-          }
-          else {
-            // display image
-            DisplayPhotoIdx( idx );
-          }
-          break;
-        case 'album':
-          if( ignoreSelected === false && G.GOM.nbSelected > 0 ) {
-            ThumbnailSelectionToggle( idx );
-          }
-          else {
-            if( G.O.thumbnailAlbumDisplayImage && idx != 0 ) {
-              // display album content in lightbox
-              DisplayFirstMediaInAlbum( idx );
-              return;
-            }
-            else {
-              // display album content in gallery
-              DisplayAlbum('-1', item.GetID());
-            }
-          }
-          break;
-        case 'albumUp':
-          var parent = NGY2Item.Get(G, item.albumID);
-          DisplayAlbum('-1', parent.albumID);
-          break;
-      }
-    }
-    
-    function DisplayFirstMediaInAlbum( albumIdx ) {
-      if( G.O.debugMode ) { console.log('#DisplayFirstPhotoInAlbum : '+  albumIdx); }
-
-      var item = G.I[albumIdx];
-      
-      var l = G.I.length;
-      for( var i = 0; i < l; i++ ) {
-        if( G.I[i].albumID == item.GetID() ) {
-          DisplayPhotoIdx( i );
-          return;
-        }
-      }
-      
-      // load album content
-      AlbumGetContent( item.GetID(), DisplayFirstMediaInAlbum, albumIdx, null );
-      
-    }
-    
-
-    // Open link to original image (new window)
-    function OpenOriginal( item ) {
-      switch( G.O.kind ) {
-        case 'flickr':
-          var sU = 'https://www.flickr.com/photos/' + G.O.userID + '/' + item.GetID();
-          if( item.albumID != '0' ) {
-            sU += '/in/album-' + item.albumID + '/';
-          }
-          window.open(sU, '_blank');
-          break;
-        case 'picasa':
-        case 'google':
-        case 'google2':
-          // no more working since Google changed the access to Google Photos in 2017
-          // var sU='https://plus.google.com/photos/'+G.O.userID+'/albums/'+item.albumID+'/'+item.GetID();
-          // window.open(sU,'_blank');
-          // break;
-        default:
-          var sU = item.responsiveURL();
-          window.open(sU, '_blank');
-          break;
-      }
-    }
-    
-    // ########################################################
-    // DISPLAY ONE MEDIA
-    // with internal or external viewer
-    // ########################################################
-    function DisplayPhotoIdx( ngy2ItemIdx ) {
-
-      if( !G.O.thumbnailOpenInLightox ) { return; }
-
-      if( G.O.thumbnailOpenOriginal ) {
-        // Open link to original image
-        OpenOriginal( G.I[ngy2ItemIdx] );
-        return;
-      }
-        
-      var items = [];
-//      G.VOM.currItemIdx = 0;
-      G.VOM.content.current.vIdx = 0;
-      G.VOM.items = [];
-      G.VOM.albumID = G.I[ngy2ItemIdx].albumID;
-      
-      var vimg = new VImg(ngy2ItemIdx);
-      G.VOM.items.push(vimg);
-      items.push(G.I[ngy2ItemIdx]);
-      //TODO -> danger? -> pourquoi reconstruire la liste si d�j� ouvert (back/forward)     
-      var l = G.I.length;
-      for( var idx = ngy2ItemIdx+1; idx < l ; idx++) {
-        var item = G.I[idx];
-        if( item.kind == 'image' && item.isToDisplay(G.VOM.albumID) && item.destinationURL == '' ) {
-          var vimg = new VImg(idx);
-          G.VOM.items.push(vimg);
-          items.push(item);
-        }
-      }
-      var last = G.VOM.items.length;
-      var cnt = 1;
-      for( var idx = 0; idx < ngy2ItemIdx ; idx++) {
-        var item = G.I[idx];
-        if( item.kind == 'image' && item.isToDisplay(G.VOM.albumID) && item.destinationURL == '' ) {
-          var vimg = new VImg(idx);
-          vimg.mediaNumber = cnt;
-          G.VOM.items.push(vimg);
-          items.push(item);
-          cnt++;
-        }
-      }
-      for( var i = 0; i < last; i++ ) {
-        G.VOM.items[i].mediaNumber = cnt;
-        cnt++;
-      }
-    
-      // opens media with external viewer
-      var fu = G.O.fnThumbnailOpen;
-      if( fu !== null ) {
-        typeof fu == 'function' ? fu(items) : window[fu](items);
-        return;
-      }
-    
-      // use internal viewer
-      if( !G.VOM.viewerDisplayed ) {
-        // build viewer and display
-        LightboxOpen();
-      }
-      else {
-        // viewer already displayed -> display new media in current viewer
-        G.VOM.content.current.$media.empty();
-        var item = G.VOM.content.current.NGY2Item();
-        var spreloader = '<div class="nGY2ViewerMediaLoaderDisplayed"></div>';
-        if( item.mediaKind == 'img' && item.imageWidth != 0 && item.imageHeight != 0 ) {
-          spreloader = '<div class="nGY2ViewerMediaLoaderHidden"></div>';
-        }
-        G.VOM.content.current.$media.append( spreloader + item.mediaMarkup);
-        ViewerSetMediaVisibility(G.VOM.content.next, 0);
-        ViewerSetMediaVisibility(G.VOM.content.previous, 0);
-        if( item.mediaKind == 'img' ) {
-          G.VOM.ImageLoader.loadImage(VieweImgSizeRetrieved, item);
-        }
-        // G.VOM.$mediaCurrent.css({ opacity:0 }).attr('src','');
-        // G.VOM.ImageLoader.loadImage(VieweImgSizeRetrieved, G.VOM.NGY2Item(0));
-        // G.VOM.$mediaCurrent.children().eq(0).attr('src',G.emptyGif).attr('src', G.VOM.NGY2Item(0).responsiveURL());
-        // LightboxDisplay(0, '');
-        LightboxDisplay('');
-      }
-    }
-    
-    function ViewerZoomStart() {
-      if( G.O.viewerZoom && !G.VOM.viewerMediaIsChanged ) {
-        var item = G.VOM.content.current.NGY2Item();
-        if( item.imageHeight > 0 && item.imageWidth > 0 ) {
-          if( G.VOM.zoom.isZooming === false ) {
-            // default zoom
-            G.VOM.zoom.userFactor = 1;
-            G.VOM.zoom.isZooming = true;
-          }
-          return true;
-        }
-      }
-    }
-          
-    function ViewerZoomIn( zoomIn ) {
-    if( zoomIn ) {
-        // zoom in
-        G.VOM.zoom.userFactor += 0.1;
-        ViewerZoomMax();
-      }
-      else {
-        // zoom out
-        G.VOM.zoom.userFactor -= 0.1;
-        ViewerZoomMin();
-      }
-      ViewerMediaSetPosAndZoom();
-    }
-    
-    function ViewerZoomMax() {
-      if( G.VOM.zoom.userFactor > 3 ) {
-        G.VOM.zoom.userFactor = 3;
-      }
-    }
-    function ViewerZoomMin() {
-    
-      if( G.VOM.zoom.userFactor < 0.2 ) {
-        G.VOM.zoom.userFactor = 0.2;
-      }
-    }
-    
-    
-    
-    // Set position and size of all 3 media containers
-    function ViewerMediaSetPosAndZoom() {
-    
-      if( !G.VOM.zoom.isZooming ) {
-        G.VOM.zoom.userFactor = 1;
-      }
-      // window.ng_draf( function() {
-        ViewerMediaSetPosAndZoomOne( G.VOM.content.current, true );
-        ViewerMediaSetPosAndZoomOne( G.VOM.content.previous, false );
-        ViewerMediaSetPosAndZoomOne( G.VOM.content.next, false );
-      // });
-    }
-    
-
-    
-    // Media which is not IMG -> center and set size
-    function ViewerMediaCenterNotImg( $mediaContainer ) {
-      var $media = $mediaContainer.children().eq(1);
-      $media.css( {'height': '80%' });
-      $media.css( {'width':  '90%' });
-      $media[0].style[G.CSStransformName] = 'translate(0px, "50%") ';
-    }
-    
-    // Set position and size of ONE media container
-    function ViewerMediaSetPosAndZoomOne(content_item, isCurrent ) {
-
-      var item = content_item.NGY2Item();
-      var $img = content_item.$media;
-      
-
-      if( item.mediaKind != 'img' ) {
-        ViewerMediaCenterNotImg( $img );
-        return;
-      }
-      
-      if( item.imageHeight == 0 || item.imageWidth == 0 ) { 
-        // ViewerSetMediaVisibility( item, $img, 0 );
-        ViewerSetMediaVisibility( content_item, 0 );
-        return;
-      }
-
-      // part 1: set the image size
-      var zoomUserFactor = isCurrent == true ? G.VOM.zoom.userFactor : 1;
-      
-      var dpr = 1;
-      if( G.O.viewerImageDisplay == 'bestImageQuality' ) {
-        dpr = window.devicePixelRatio;
-      }
-      
-      // retrieve the base zoom factor (image fill screen)
-      var zoomBaseFactorW = (G.VOM.window.lastWidth  - G.VOM.padding.V) / (item.imageWidth  / dpr);
-      var zoomBaseFactorH = (G.VOM.window.lastHeight - G.VOM.padding.H) / (item.imageHeight / dpr);
-      var zoomBaseFactor = Math.min(zoomBaseFactorW, zoomBaseFactorH);
-      if( zoomBaseFactor > 1 && G.O.viewerImageDisplay != 'upscale' ) {
-        // no upscale
-        zoomBaseFactor = 1;
-      }
-
-      var imageCurrentHeight = (item.imageHeight / dpr) * zoomUserFactor * zoomBaseFactor;
-      var imageCurrentWidth  = (item.imageWidth / dpr)  * zoomUserFactor * zoomBaseFactor;
-      $img.children().eq(1).css( {'height': imageCurrentHeight });
-      $img.children().eq(1).css( {'width':  imageCurrentWidth  });
-
-      // retrieve posX/Y to center image
-      var posX = 0;
-      if( imageCurrentWidth > G.VOM.window.lastWidth ) {
-        posX = -(imageCurrentWidth - G.VOM.window.lastWidth)/2;
-      }
-      var posY = 0;
-      if( imageCurrentHeight > G.VOM.window.lastHeight ) {
-        posY = ( imageCurrentHeight - G.VOM.window.lastHeight ) / 2;
-      }
-      posY = 0;   // actually, it seems that the image is always centered vertically -> so no need to to anything
-      
-      // Part 2: set the X/Y position (for zoom/pan)
-      if( isCurrent ) {
-        if( !G.VOM.zoom.isZooming ) {
-          G.VOM.panPosX = 0;
-          G.VOM.panPosY = 0;
-        }
-        G.VOM.zoom.posX = posX;
-        G.VOM.zoom.posY = posY;
-        ViewerImagePanSetPosition(G.VOM.panPosX, G.VOM.panPosY, $img, false);
-      }
-      // else {
-        //$img[0].style[G.CSStransformName]= 'translate3D('+ posX+'px, '+ posY+'px, 0) ';
-      // }
-      else {
-        // set the pan position of each media container
-        ViewerMediaPanX( G.VOM.swipePosX );
-        $img.children().eq(1)[0].style[G.CSStransformName]= 'translate(0px, 0px) rotate('+ item.rotationAngle +'deg)';
-      }
-      
-    }
-
-    // position the image depending on the zoom factor and the pan X/Y position
-    // IMG is the only media supporting zoom/pan
-    function ViewerImagePanSetPosition(posX, posY, imageContainer, savePosition ) {
-      if( savePosition ) {
-        G.VOM.panPosX = posX;
-        G.VOM.panPosY = posY;
-      }
-
-      posX += G.VOM.zoom.posX;
-      posY += G.VOM.zoom.posY;
-      
-      // imageContainer.children().eq(1)[0].style[G.CSStransformName]= 'translate('+ posX + 'px, '+ posY + 'px)';
-      imageContainer.children().eq(1)[0].style[G.CSStransformName]= 'translate('+ posX + 'px, '+ posY + 'px) rotate('+ G.VOM.content.current.NGY2Item().rotationAngle +'deg)';
-
-
-    }
-    
-
-    // LIGHTBOX
-    // display media with internal viewer
-    function LightboxOpen( idx ) {
-
-      // G.VOM.viewerDisplayed = true;
-      G.GOM.firstDisplay = false;
-      
-      // remove scrollbar and add right margin with same width as the scrollbar to avoid page reflow
-      jQuery('head').append('<style id="nGY2_body_scrollbar_style" type="text/css">.nGY2_body_scrollbar{margin-right: ' + (window.innerWidth - document.documentElement.clientWidth) + 'px;}</style>');
-      jQuery("body").addClass("nGY2_body_scrollbar");
-        
-
-      G.VOM.$baseCont = jQuery('<div  class="nGY2 nGY2ViewerContainer" style="opacity:1"></div>').appendTo('body');
-      
-      SetViewerTheme();
-
-      G.VOM.$viewer = jQuery('<div class="nGY2Viewer" style="opacity:0" itemscope itemtype="http://schema.org/ImageObject"></div>').appendTo( G.VOM.$baseCont );
-      G.VOM.$viewer.css({ msTouchAction: 'none', touchAction: 'none' });            // avoid pinch zoom
-
-      if( idx == undefined ) {
-        G.VOM.content.current.vIdx = 0;
-      }
-      else {
-        G.VOM.content.current.vIdx = idx;
-      }
-      G.VOM.content.previous.vIdx = G.VOM.IdxNext();
-      G.VOM.content.next.vIdx = G.VOM.IdxPrevious();   
- 
-      var sMedia = '<div class="nGY2ViewerMediaPan"><div class="nGY2ViewerMediaLoaderDisplayed"></div>' + G.VOM.content.previous.NGY2Item().mediaMarkup + '</div>';    // previous media
-      sMedia    += '<div class="nGY2ViewerMediaPan"><div class="nGY2ViewerMediaLoaderDisplayed"></div>' + G.VOM.content.current.NGY2Item().mediaMarkup  + '</div>';    // current media
-      sMedia    += '<div class="nGY2ViewerMediaPan"><div class="nGY2ViewerMediaLoaderDisplayed"></div>' + G.VOM.content.next.NGY2Item().mediaMarkup  + '</div>';    // next media
-
-      var sNav = '';
-      var iconP = G.O.icons.viewerImgPrevious;
-      if( iconP != undefined && iconP != '') {
-        sNav += '<div class="nGY2ViewerAreaPrevious ngy2viewerToolAction" data-ngy2action="previous">' + iconP + '</div>';
-      }
-      var iconN = G.O.icons.viewerImgNext;
-      if( iconN != undefined && iconN != '') {
-        sNav += '<div class="nGY2ViewerAreaNext ngy2viewerToolAction" data-ngy2action="next">' + iconN + '</div>';
-      }
-
-      G.VOM.$content = jQuery('<div class="nGY2ViewerContent">' + sMedia + sNav + '</div>').appendTo( G.VOM.$viewer );
-
-      G.VOM.$buttonLeft = G.VOM.$content.find('.nGY2ViewerAreaPrevious');
-      G.VOM.$buttonRight = G.VOM.$content.find('.nGY2ViewerAreaNext');
-
-      var $mediaPan = G.VOM.$content.find('.nGY2ViewerMediaPan');
-      G.VOM.content.previous.$media = $mediaPan.eq(0);    // pointer to previous media container
-      G.VOM.content.current.$media = $mediaPan.eq(1);     // pointer to current media container
-      G.VOM.content.next.$media = $mediaPan.eq(2);        // pointer to next media container
-
-      G.VOM.ImageLoader.loadImage( VieweImgSizeRetrieved, G.VOM.content.current.NGY2Item()  );
-      G.VOM.ImageLoader.loadImage( VieweImgSizeRetrieved, G.VOM.content.previous.NGY2Item() );
-      G.VOM.ImageLoader.loadImage( VieweImgSizeRetrieved, G.VOM.content.next.NGY2Item()  );
-      
-      G.VOM.padding.H = parseInt(G.VOM.$content.css("padding-left")) + parseInt(G.VOM.$content.css("padding-right"));
-      G.VOM.padding.V = parseInt(G.VOM.$content.css("padding-top")) + parseInt(G.VOM.$content.css("padding-bottom"));
-      
-      // build media toolbar container
-      var vtbBg1 = '';
-      var vtbBg2 = ' toolbarBackground';
-      if( G.O.viewerToolbar.fullWidth ) {
-        vtbBg1 = ' toolbarBackground';
-        vtbBg2 = '';
-      }
-      var vtbAlign = 'text-align:center;';
-      switch ( G.O.viewerToolbar.align ) {
-        case 'left':
-          vtbAlign = 'text-align:left;';
-          break;
-        case 'right':
-          vtbAlign = 'text-align:right;';
-          break;
-      }
-      var sTB = '<div class="toolbarContainer nGEvent' + vtbBg1 + '" style="visibility:' +(G.O.viewerToolbar.display ? "visible" : "hidden")+';'+vtbAlign+'"><div class="toolbar nGEvent' + vtbBg2 + '"></div></div>';
-      G.VOM.$toolbar = jQuery(sTB).appendTo(G.VOM.$viewer);
-
-      if( G.VOM.toolbarMode == 'min' || (G.O.viewerToolbar.autoMinimize > 0 && G.O.viewerToolbar.autoMinimize >= G.GOM.cache.viewport.w) ) {
-        ViewerToolbarForVisibilityMin();
-      }
-      else {
-        ViewerToolbarForVisibilityStd();
-      }
-      
-      // top-left toolbar
-      var sTopLeft = '<div class="nGY2ViewerToolsTopLeft nGEvent"><div class="toolbar nGEvent">';
-      var sTL = G.O.viewerTools.topLeft.split(',');
-      for( var i = 0, sTLL = sTL.length; i < sTLL; i++) {
-        sTopLeft += ToolbarAddElt( sTL[i] );
-      }
-      sTopLeft += '</div></div>';
-      G.VOM.$toolbarTL = jQuery(sTopLeft).appendTo(G.VOM.$viewer);
-
-      // top-right toolbar
-      var sTopRight = '<div class="nGY2ViewerToolsTopRight nGEvent"><div class="toolbar nGEvent">';
-      var sTR = G.O.viewerTools.topRight.split(',');
-      for( var i = 0, sTRL = sTR.length; i < sTRL; i++) {
-        sTopRight += ToolbarAddElt( sTR[i] );
-      }
-      sTopRight += '</div></div>';
-      G.VOM.$toolbarTR = jQuery(sTopRight).appendTo(G.VOM.$viewer);
-
-      // set the events handler on the toolbars
-      ViewerToolsOn();
-
-      // Go to fullscreen mode
-      if( ngscreenfull.enabled && G.O.viewerFullscreen ) { ngscreenfull.request(); }
-
-      // Gallery
-      LightboxGalleryBuild();
-
-      setElementOnTop('', G.VOM.$viewer);
-      ResizeLightbox(true);
-      G.VOM.gallery.Resize();
-      G.VOM.timeImgChanged = new Date().getTime();
-      
-      // viewer display transition
-      G.VOM.$toolbarTL.css('opacity', 0);
-      G.VOM.$toolbarTR.css('opacity', 0);
-      G.VOM.$buttonLeft.css('opacity', 0);
-      G.VOM.$buttonRight.css('opacity', 0);
-      G.VOM.gallery.$elt.css('opacity', 0);
-      G.VOM.$content.css('opacity', 0);
-      G.VOM.$toolbarTR[0].style[G.CSStransformName] = 'translateY(-40px) ';
-      G.VOM.$toolbarTL[0].style[G.CSStransformName] = 'translateY(-40px) ';
-      G.VOM.$buttonLeft[0].style[G.CSStransformName] = 'translateX(-40px) ';
-      G.VOM.$buttonRight[0].style[G.CSStransformName] = 'translateX(40px) ';
-
-      // STEP 1: display main container, including media
-      var tweenable = new NGTweenable();
-      tweenable.tween({
-        from:         { opacity: 0, posY: G.VOM.window.lastHeight*.5 },
-        to:           { opacity: 1, posY: 0 },
-        delay:        10,
-        duration:     450,
-        easing:       'easeInOutQuint',
-        step:         function (state) {
-          // lightbox
-          G.VOM.$viewer.css('opacity', state.opacity);
-          G.VOM.$viewer[0].style[G.CSStransformName] = 'translateY(' + (state.posY) + 'px) ';
-          
-          // media in lightbox
-          G.VOM.$content.css('opacity', state.opacity);
-        }
-      });
-      
-      
-      // STEP 2: display tools, left/right navigation buttons, gallery
-      var tweenable = new NGTweenable();
-      tweenable.tween({
-        from:         { posY: -40, opacity: 0, scale: 3 },
-        to:           { posY: 0, opacity: 1, scale: 1 },
-        delay:        300,
-        duration:     400,
-        easing:       'easeInOutQuint',
-        step:         function (state) {
-          
-          // tools
-          G.VOM.$toolbarTR[0].style[G.CSStransformName] = 'translateY(' + (state.posY) + 'px) ';
-          G.VOM.$toolbarTL[0].style[G.CSStransformName] = 'translateY(' + (state.posY) + 'px) ';
-          G.VOM.$buttonLeft[0].style[G.CSStransformName] = 'translateX(' + (state.posY) + 'px) ';
-          G.VOM.$buttonRight[0].style[G.CSStransformName] = 'translateX(' + (-state.posY) + 'px) ';
-          
-          // gallery
-          G.VOM.gallery.$elt.css({ opacity: state.opacity });
-          G.VOM.gallery.$elt[0].style[G.CSStransformName] = 'scale('+state.scale+')';
-        },
-        finish: function() {
-          G.VOM.viewerDisplayed = true;
-          ViewerMediaPanX(0);
-          ViewerSetEvents();
-
-          LightboxDisplay('');
-          
-          if( G.O.slideshowAutoStart ) {
-            G.VOM.playSlideshow = false;
-            SlideshowToggle();
-          }
-          
-          ViewerToolsUnHide();
-          LightboxDisplayFinalize('');
-        }
-      });
-
-
-      
-
-      // stop click propagation on media ==> if the user clicks outside of an media, the viewer is closed
-      // --> no more supported since v2.0.0
-      // G.VOM.$viewer.find('img').on('click', function (e) { e.stopPropagation(); });
-      
-      
-      // ViewerMediaPanX(0);
-      // ViewerSetEvents();
-
-      // LightboxDisplay('');
-      
-      // if( G.O.slideshowAutoStart ) {
-        // G.VOM.playSlideshow = false;
-        // SlideshowToggle();
-      // }
-    }
-    
-    function ViewerEvents() {
-      if( !G.VOM.viewerDisplayed || G.VOM.viewerMediaIsChanged ) {
-      // if( !G.VOM.viewerDisplayed || G.VOM.viewerMediaIsChanged || G.VOM.content.current.NGY2Item().mediaKind != 'img') {
-        // ignore fired event if viewer not displayed or if currently changed (or if current media not an image)
-        return false;
-      }
-      return true;
-    }
-    
-    // VIEWER - BUILD THE THUMBNAILS GALLERY
-    function LightboxGalleryBuild() {
-
-			G.VOM.gallery.firstDisplay = true;
-	
-			if( G.O.viewerGallery != 'none' ) {
-	
-				var tw = G.O.viewerGalleryTWidth;
-				var th = G.O.viewerGalleryTHeight;
-				var gutter = 2;
-				
-				var t = '';
-				for( var i=0; i< G.VOM.items.length; i++) {
-					var idx = G.VOM.items[i].ngy2ItemIdx;
-					var o = G.I[idx];
-					var src = (o.thumbImg().src).replace(/'/g, "%27");   // replace single quote with %27
-					t += '<div class="nGY2VThumbnail" style="width:'+tw+'px;height:'+th+'px;left:'+i*(tw+gutter*2)+'px;background-image: url(&apos;'+src+'&apos;);" data-ngy2_lightbox_thumbnail="true" data-ngy2_idx="' + idx + '" data-ngy2_vidx="' + i + '" ></div>';
-				}
-				G.VOM.gallery.gwidth = (tw+2*gutter) * G.VOM.items.length;
-				G.VOM.gallery.oneTmbWidth = tw+2*gutter;
-				var tc = "<div class='nGY2VThumbnailContainer' style='height:"+(th+gutter*2)+"px;left:0;width:"+G.VOM.gallery.gwidth+"px;' data-ngy2_lightbox_gallery='true'>" + t + "</div>";
-				G.VOM.gallery.$elt = jQuery('<div class="nGY2viewerGallery" style="display: inline-block;height:'+(th+gutter*2)+'px;left:0;right:0;">'+ tc +'</div>').appendTo(G.VOM.$viewer);
-				G.VOM.gallery.$tmbCont = G.VOM.gallery.$elt.find('.nGY2VThumbnailContainer')
-				
-				G.VOM.gallery.Resize();
-				G.VOM.gallery.SetThumbnailActive();
-      
-			}
-    }
-    
-
-    // Lightbox gesture handling
-    function ViewerSetEvents() {
-
-      if( G.VOM.hammertime == null ) {
-      
-        G.VOM.hammertime =  new NGHammer.Manager(G.VOM.$baseCont[0],  {
-          // domEvents: true,
-          recognizers: [
-            [NGHammer.Pinch, { enable: true }],
-            [NGHammer.Pan, { direction: NGHammer.DIRECTION_ALL }]
-          ]
-        });
-     
-        // PAN
-        G.VOM.hammertime.on('pan', function(ev) {
-          if( !ViewerEvents() ) { return; }
-
-          
-          if( G.VOM.panMode == 'off' ) {
-            // PAN START -> determine the element to pan
-            if( ev.target.dataset.ngy2_lightbox_thumbnail != undefined || ev.target.dataset.ngy2_lightbox_gallery != undefined ){
-              G.VOM.panMode = 'gallery';
-            }
-            else {
-              if( G.VOM.zoom.isZooming ) {
-                G.VOM.panMode = 'zoom';
-              }
-              else {
-                G.VOM.panMode = 'media';
-              }
-            }
-          }
-
-          // PAN the determined element
-          switch( G.VOM.panMode ) {
-            case 'zoom':
-              // pan zoomed image
-              ViewerImagePanSetPosition(G.VOM.panPosX + ev.deltaX, G.VOM.panPosY + ev.deltaY, G.VOM.content.current.$media, false);
-              G.VOM.toolsHide();
-              break;
-              
-            case 'media':
-              if( Math.abs(ev.deltaY) > G.VOM.panThreshold && Math.abs(ev.deltaX) < G.VOM.panThreshold && !G.VOM.panXOnly ) {
-                // pan viewer down/up to close the lightbox
-                ViewerMediaPanX( 0 );
-                var dist = 0;
-                if( ev.deltaY < 0 ) {
-                  // pan up
-                  dist = Math.max( ev.deltaY, -200);
-                }
-                else {
-                  // pan down
-                  dist = Math.min( ev.deltaY, 200);
-                }
-                G.VOM.$viewer[0].style[G.CSStransformName] = 'translateY(' + dist + 'px) ';
-                G.VOM.$viewer.css('opacity', 1-Math.abs(dist)/200/2);
-              }
-              else {
-                // pan media left/right
-                if( Math.abs(ev.deltaX) > G.VOM.panThreshold ) {
-                  G.VOM.panXOnly = true;
-                }
-                ViewerMediaPanX( ev.deltaX );
-                G.VOM.$viewer[0].style[G.CSStransformName] = 'translateY(0px)';
-                G.VOM.$viewer.css('opacity', 1);
-              }
-              break;
-              
-            case 'gallery':
-              G.VOM.gallery.PanGallery( ev.deltaX );
-              break;
-          }
-          
-        });
-
-        // PAN END
-        G.VOM.hammertime.on('panend', function(ev) {
-          if( !ViewerEvents() ) { return; }
-
-          switch( G.VOM.panMode ) {
-            case 'zoom':
-              // PAN END in image zoom mode
-              G.VOM.timeImgChanged = new Date().getTime();
-              ViewerImagePanSetPosition( G.VOM.panPosX+ev.deltaX, G.VOM.panPosY+ev.deltaY, G.VOM.content.current.$media, true);
-              break;
-            case 'media':
-              var panY = false;
-              if( !G.VOM.panXOnly ) {
-                if( Math.abs(ev.deltaY) > 50 && Math.abs(ev.deltaX) < 50 ) {
-                  // close viewer
-                  LightboxClose();
-                  panY = true;
-                }
-              }
-              if( !panY ) {
-                if( Math.abs( ev.deltaX ) < 50 ) {
-                  ViewerMediaPanX(0);
-                }
-                else {
-                  ev.deltaX > 50 ? DisplayPreviousMedia( Math.abs(ev.velocityX) ) : DisplayNextMedia( Math.abs(ev.velocityX) );
-                }
-              }
-              G.VOM.panXOnly = false;
-              break;
-            case 'gallery':
-              // PAN END on thumbnail gallery
-              G.VOM.gallery.posX += ev.deltaX;
-              G.VOM.gallery.PanGallery( 0 );
-              G.VOM.gallery.PanGalleryEnd( ev.velocityX );
-              break;
-          }
-
-          G.VOM.panMode = 'off';
-        });
-        
-				
-				// ZOOM FEATURE ENABLED
-        if( G.O.viewerZoom ) {
-
-					G.VOM.hammertime.add( new NGHammer.Tap({ event: 'doubletap', taps: 2, interval: 250 }) );
-          G.VOM.hammertime.add( new NGHammer.Tap({ event: 'singletap' }) );
-          G.VOM.hammertime.get('doubletap').recognizeWith('singletap');
-          G.VOM.hammertime.get('singletap').requireFailure('doubletap');
-
-          // single tap -> next/previous media
-          G.VOM.hammertime.on('singletap', function(ev) {
-
-						if( !ViewerEvents() ) { return; }
-						
-						// Gallery on viewer -> click/touch on one thumbnail -> display corresponding image
-						if( ev.target.dataset.ngy2_lightbox_thumbnail != undefined ){
-
-							var idx = parseInt(ev.target.dataset.ngy2_idx);
-							var vidx = parseInt(ev.target.dataset.ngy2_vidx);
-
-							if( idx != undefined && vidx != G.VOM.content.current.vIdx ) {
-                
-                if( vidx > G.VOM.content.current.vIdx ) {
-                  TriggerCustomEvent('lightboxNextImage');
-        
-                  // replace the next media with selected media
-                  G.VOM.content.next.$media.empty();
-                  var nextItem = G.I[idx];
-                  G.VOM.content.next.vIdx = vidx;
-                  var spreloader = '<div class="nGY2ViewerMediaLoaderDisplayed"></div>';
-                  if( nextItem.mediaKind == 'img' && nextItem.imageWidth != 0 && nextItem.imageHeight != 0 ) {
-                    spreloader = '<div class="nGY2ViewerMediaLoaderHidden"></div>';
-                  }
-                  G.VOM.content.next.$media.append( spreloader + nextItem.mediaMarkup );
-                  if( nextItem.mediaKind == 'img' ) {
-                    G.VOM.ImageLoader.loadImage(VieweImgSizeRetrieved, nextItem);
-                  }
-                  else {
-                    ViewerMediaCenterNotImg( G.VOM.content.next.$media );
-                  }
-                  LightboxDisplay('nextImage');
-
-                }
-                else {
-                  TriggerCustomEvent('lightboxPreviousImage');
-        
-                  // replace the previous media with selected media
-                  G.VOM.content.previous.$media.empty();
-                  var previousItem = G.I[idx];
-                  G.VOM.content.previous.vIdx = vidx;
-                  var spreloader = '<div class="nGY2ViewerMediaLoaderDisplayed"></div>';
-                  if( previousItem.mediaKind == 'img' && previousItem.imageWidth != 0 && previousItem.imageHeight != 0 ) {
-                    spreloader = '<div class="nGY2ViewerMediaLoaderHidden"></div>';
-                  }
-                  G.VOM.content.previous.$media.append( spreloader + previousItem.mediaMarkup );
-                  if( previousItem.mediaKind == 'img' ) {
-                    G.VOM.ImageLoader.loadImage(VieweImgSizeRetrieved, previousItem);
-                  }
-                  else {
-                    ViewerMediaCenterNotImg( G.VOM.content.previous.$media );
-                  }
-                  LightboxDisplay('previousImage');
-                }
-								return;
-							}
-						}
-						
-						
-            StopPropagationPreventDefault(ev.srcEvent);
-            if( G.VOM.toolbarsDisplayed == false ) {
-              debounce( ViewerToolsUnHide, 100, false)();
-							G.VOM.singletapTime = new Date().getTime();
-            }
-            else {
-							// toolbars are displayed -> display next/previous media
-							if( (new Date().getTime()) - G.VOM.singletapTime < 400 ) { return; }		// to avoid conflict with MOUSEMOVE event
-              if( ev.target.className.indexOf('nGY2ViewerMedia') !== -1 ) {
-								var x =0;
-								if( ev.srcEvent instanceof MouseEvent ) {
-									x = ev.srcEvent.pageX;
-								}
-								else {
-									x = ev.srcEvent.changedTouches[0].pageX;
-								}
-								if( x < (G.GOM.cache.viewport.w/2) ) {
-                  DisplayPreviousMedia();
-                }
-                else {
-                  DisplayNextMedia();
-                }
-              }
-            }
-          });
-          
-          // double tap -> zoom
-          G.VOM.hammertime.on('doubletap', function(ev) {
-            if( !ViewerEvents() ) { return; }
-            StopPropagationPreventDefault(ev.srcEvent);
-            
-            if( ev.target.className.indexOf('nGY2ViewerMedia') !== -1 ) {
-              // double tap only on image
-              if( G.VOM.zoom.isZooming ) {
-                G.VOM.zoom.isZooming = false;
-                // G.VOM.zoom.userFactor = 1;
-                ResizeLightbox(true);
-              }
-              else {
-                if( ViewerZoomStart() ) {
-                  G.VOM.zoom.userFactor = 1.5;
-                  ViewerMediaSetPosAndZoom();
-                }
-              }
-            }
-          });
-        
-          // pinch end
-          G.VOM.hammertime.on('pinchend', function(ev) {
-            ev.srcEvent.stopPropagation();
-            ev.srcEvent.preventDefault();  // cancel  mouseenter event
-            G.VOM.timeImgChanged = new Date().getTime();
-          });
-          G.VOM.hammertime.on('pinch', function(ev) {
-            ev.srcEvent.stopPropagation();
-            ev.srcEvent.preventDefault();  // cancel  mouseenter event
-            
-            if( ViewerZoomStart() ) {
-              G.VOM.zoom.userFactor = ev.scale;
-              ViewerZoomMax();
-              ViewerZoomMin();
-              ViewerMediaSetPosAndZoom();   // center media
-            }
-          });
-        }
-				
-				
-        else {
-					// ZOOM FEATURE DISABLED
-
-					G.VOM.hammertime.add( new NGHammer.Tap({ event: 'singletap' }) );
-
-          // click/tap on image to go to next/previous one
-          // G.VOM.hammertime.on('tap', function(ev) {
-          G.VOM.hammertime.on('singletap', function(ev) {
-            if( !ViewerEvents() ) { return; }
-            StopPropagationPreventDefault( ev.srcEvent );
-            if( G.VOM.toolbarsDisplayed == false  ){
-              // display tools on tap if hidden
-              debounce( ViewerToolsUnHide, 100, false)();
-							G.VOM.singletapTime = new Date().getTime();
-            }
-            else {
-							// toolbars are displayed -> display next/previous media
-							if( (new Date().getTime()) - G.VOM.singletapTime < 400 ) { return; }		// to avoid conflict with MOUSEMOVE event
-              if( ev.target.className.indexOf('nGY2ViewerMedia') !== -1 ) {
-								var x = 0;
-								if( ev.srcEvent instanceof MouseEvent ) {
-									x = ev.srcEvent.pageX;
-								}
-								else {
-									x = ev.srcEvent.changedTouches[0].pageX;
-								}
-                if( x < (G.GOM.cache.viewport.w/2) ) {
-                  DisplayPreviousMedia();
-                }
-                else {
-                  DisplayNextMedia();
-                }
-              }
-            }
-            
-          });
-        }
-      }
-    }
-
-
-    function StopPropagationPreventDefault(e) {
-      e.stopPropagation();
-      e.preventDefault();
-    }
-
-    // Hide toolbars on user inactivity
-    function ViewerToolsHide() {
-      if( G.VOM.viewerDisplayed ) {
-        G.VOM.toolbarsDisplayed = false;
-        ViewerToolsOpacity(0);
-      }
-    }
-    
-    function ViewerToolsUnHide() {
-			if( G.VOM.viewerDisplayed ) {
-        G.VOM.toolbarsDisplayed = true;
-        ViewerToolsOpacity(1);
-        G.VOM.toolsHide();        // re-init delay before hide tools+gallery 
-      }
-    }
-    
-    function ViewerToolsOpacity( op ) {
-      if( G.VOM.$toolbar != null ) {
-        G.VOM.$toolbar.css('opacity', op);
+	if( typeof jQuery(this).data('nanogallery2data') === 'undefined'){
+      if( args == 'destroy' ) {
+        // command to destroy but no instance yet --> exit
+        return;
       }
-      if( G.VOM.$toolbarTL != null ) {
-        G.VOM.$toolbarTL.css('opacity', op);
-      }
-      if( G.VOM.$toolbarTR != null ) { 
-        G.VOM.$toolbarTR.css('opacity', op);
-      }
-      
-      // next/previous
-      G.VOM.$content.find('.nGY2ViewerAreaNext').css('opacity', op);
-      G.VOM.$content.find('.nGY2ViewerAreaPrevious').css('opacity', op);
-      
-      // gallery
-      // G.VOM.gallery.$elt.css('opacity', op);
-    }
-    
-    
-    
-    function ViewerToolsOn() {
-      // removes all events
-      G.VOM.$viewer.off('touchstart click', '.ngy2viewerToolAction', ViewerToolsAction); 
-      
-      // action button
-      G.VOM.$viewer.on('touchstart click', '.ngy2viewerToolAction', ViewerToolsAction); 
-    }
-
-    
-    // Actions of the button/elements
-    function ViewerToolsAction(e) {
-      // delay to avoid twice handling on smartphone/tablet (both touchstart click events are fired)
-      if( (new Date().getTime()) - G.timeLastTouchStart < 300 ) { return; }
-      G.timeLastTouchStart = new Date().getTime();
-      
-      var $this = $(this);
-      var ngy2action = $this.data('ngy2action');
-      if( ngy2action == undefined ) { return; }
-      switch( ngy2action ) {
-        case 'next':
-          StopPropagationPreventDefault(e);
-          DisplayNextMedia();
-          break;
-        case 'previous':
-          StopPropagationPreventDefault(e);
-          DisplayPreviousMedia();
-          break;
-        case 'playPause':
-          e.stopPropagation();
-          SlideshowToggle();
-          break;
-        case 'zoomIn':
-          StopPropagationPreventDefault(e);
-          if( ViewerZoomStart() ) { ViewerZoomIn( true ); }
-          break;
-        case 'zoomOut':
-          StopPropagationPreventDefault(e);
-          if( ViewerZoomStart() ) { ViewerZoomIn( false ); }
-          break;
-        case 'minimize':
-          // toggle toolbar visibility
-          StopPropagationPreventDefault(e);
-          if( G.VOM.toolbarMode == 'std' ) {
-            ViewerToolbarForVisibilityMin();
-          }
-          else {
-            ViewerToolbarForVisibilityStd();
-          }
-          break;
-        case 'fullScreen':
-          // Toggle viewer fullscreen mode on/off
-          e.stopPropagation();
-          if( ngscreenfull.enabled ) {
-            ngscreenfull.toggle();
-          }
-          break;
-        case 'info':
-          e.stopPropagation();
-          ItemDisplayInfo( G.VOM.content.current.NGY2Item() );
-          break;
-        case 'close':
-          StopPropagationPreventDefault(e);
-          if( (new Date().getTime()) - G.VOM.timeImgChanged < 400 ) { return; }
-          LightboxClose();
-          break;
-        case 'download':
-          StopPropagationPreventDefault(e);
-          DownloadImage(G.VOM.items[G.VOM.content.current.vIdx].ngy2ItemIdx);
-          break;
-        case 'share':
-          StopPropagationPreventDefault(e);
-          PopupShare(G.VOM.items[G.VOM.content.current.vIdx].ngy2ItemIdx);
-          break;
-        case 'linkOriginal':
-          StopPropagationPreventDefault(e);
-          OpenOriginal( G.VOM.content.current.NGY2Item() );
+      
+      return this.each( function(){
+        (new jQuery.nanogallery2(this, args));
+      });
+    }
+    else {
+			// no options -->
+      // This function breaks the chain, but provides some API methods
+      var nG2 = $(this).data('nanogallery2data').nG2;
+
+			// Lightbox standalone
+      // (Another click on an already opened media)
+			if( args !== undefined && args.lightboxStandalone === true ) {
+        // items exist already (G.I is populated) -> just open the lightbox again
+        nG2.LightboxReOpen();
+				return;
+			}
+			
+      switch(args){
+        case 'displayItem':
+          nG2.DisplayItem(option);
           break;
-        case 'rotateLeft':
-          StopPropagationPreventDefault(e);
-          ViewerImageRotate(-90);
+          
+        case 'search':
+          return( nG2.Search(option));
           break;
-        case 'rotateRight':
-          StopPropagationPreventDefault(e);
-          ViewerImageRotate(90);
-          break;
-        case 'shoppingcart':
-          StopPropagationPreventDefault(e);
-          AddToCart( G.VOM.items[G.VOM.content.current.vIdx].ngy2ItemIdx, 'lightbox');
-          break;
-      }
-      
-      // custom button
-      var fu = G.O.fnImgToolbarCustClick;
-      if( ngy2action.indexOf('custom') == 0  && fu !== null ) {
-        typeof fu == 'function' ? fu(ngy2action, $this, G.VOM.content.current.NGY2Item() ) : window[fu](ngy2action, $this, G.VOM.content.current.NGY2Item() );
+          
+        case 'search2':
+          return nG2.Search2(option, value);
+          break;
+          
+        case 'search2Execute':
+          return nG2.Search2Execute();
+          break;
+          
+        case 'refresh':
+          nG2.Refresh();
+          break;
+
+        case 'resize':
+          nG2.Resize();
+          break;
+          
+        case 'instance':
+          return nG2;
+          break;
+          
+        case 'data':
+          nG2.data= {
+            items: nG2.I,
+            gallery: nG2.GOM,
+            lightbox: nG2.VOM,
+						shoppingcart: nG2.shoppingCart
+          };
+          return nG2.data;
+          break;
+          
+        case 'reload':
+          nG2.ReloadAlbum();
+          return $(this);
+          break;
+          
+        case 'itemsSelectedGet':
+          return nG2.ItemsSelectedGet();
+          break;
+          
+        case 'itemsSetSelectedValue':
+          nG2.ItemsSetSelectedValue(option, value);
+          break;
+          
+        case 'option':
+          if(typeof value === 'undefined'){
+            return nG2.Get(option);
+          }else{
+            nG2.Set(option,value);
+            if( option == 'demoViewportWidth' ) {
+              // force resize event -> for demo purposes
+              $(window).trigger('resize');
+            }
+          }
+          break;
+          
+        case 'destroy':
+          nG2.Destroy();
+          $(this).removeData('nanogallery2data');
+          break;
+          
+        case 'shoppingCartGet':
+          // returns the content of the shoppingcart
+          return nG2.shoppingCart;
+          break;
+          
+        case 'shoppingCartUpdate':
+          // parameters :
+          //  - option = item's ID
+          //  - value = new quantity
+			
+          if( typeof value === 'undefined' || typeof option === 'undefined' ){
+            return false;
+          }
+          
+          var item_ID = option;
+          var new_qty = value;
+
+          for( var i=0; i < nG2.shoppingCart.length; i++) {
+            if( nG2.shoppingCart[i].ID == item_ID ) {
+              
+              // updates counter
+              nG2.shoppingCart[i].qty = new_qty;
+              
+              var item = nG2.I[nG2.shoppingCart[i].idx];
+
+              // updates thumbnail
+              nG2.ThumbnailToolbarOneCartUpdate( item );
+              
+              if( new_qty == 0 ) {
+                // removes item from shoppingcart
+                nG2.shoppingCart.splice(i, 1);
+              }
+              
+              var fu = nG2.O.fnShoppingCartUpdated;
+              if( fu !== null ) {
+                typeof fu == 'function' ? fu(nG2.shoppingCart, item, 'api') : window[fu](nG2.shoppingCart, item, 'api');
+              }
+
+              break;
+            }
+          }
+         
+          return nG2.shoppingCart;
+          break;
+          
+        case 'shoppingCartRemove':
+          // parameters :
+          //  - option = item's ID
+          if( typeof option === 'undefined' ){
+            return false;
+          }
+          var ID = option;
+          for( var i=0; i < nG2.shoppingCart.length; i++) {
+            if( nG2.shoppingCart[i].ID == ID ) {
+              
+              var item = nG2.I[nG2.shoppingCart[i].idx];
+
+              // updates thumbnail
+              nG2.shoppingCart[i].qty = 0;
+              nG2.ThumbnailToolbarOneCartUpdate( item );
+              
+              // removes item from shoppingcart
+              nG2.shoppingCart.splice(i, 1);
+              
+              
+              var fu = nG2.O.fnShoppingCartUpdated;
+              if( fu !== null ) {
+                typeof fu == 'function' ? fu(nG2.shoppingCart, item, 'api') : window[fu](nG2.shoppingCart, item, 'api');
+              }
+
+              break;
+            }
+          }
+          
+          return nG2.shoppingCart;
+          break;
+         
+        case 'closeViewer':
+          nG2.CloseViewer();
+          break;
+        case 'minimizeToolbar':
+          nG2.MinimizeToolbar();
+          break;
+        case 'maximizeToolbar':
+          nG2.MaximizeToolbar();
+          break;
+        case 'paginationPreviousPage':
+          nG2.PaginationPreviousPage();
+          break;
+        case 'paginationNextPage':
+          nG2.paginationNextPage();
+          break;
+        case 'paginationGotoPage':
+          nG2.PaginationGotoPage( option );
+          break;
+        case 'paginationCountPages':
+          nG2.PaginationCountPages();
+          break;
+         
       }
+      return $(this);
+
     }
+  };
+  
 
-    // rotate displayed image
-    function ViewerImageRotate( angle ) {
-      var item = G.VOM.content.current.NGY2Item();
-      if( item.mediaKind == 'img' ) {
-        item.rotationAngle += angle;
-        item.rotationAngle = item.rotationAngle % 360;
-        if( item.rotationAngle < 0 ) {
-          item.rotationAngle += 360;
+  // ###############################
+  // ##### nanogallery2 script #####
+  // ###############################
+
+  /** @function nanoGALLERY2 */
+  function nanoGALLERY2() {
+    "use strict";
+
+    /**
+    * Force reload the current album, if provided by Json
+    */
+    this.LightboxReOpen = function(){
+      LightboxStandaloneDisplay();
+    }
+
+    /**
+    * Force reload the current album, if provided by Json
+    */
+    this.ReloadAlbum = function(){
+      if( G.O.kind === '' ) {
+        throw 'Not supported for this content source:' + G.O.kind;
+      }
+
+      var albumIdx = G.GOM.albumIdx;
+      if( albumIdx == -1 ) {
+        throw ('Current album not found.');
+      }
+      
+      var albumID = G.I[albumIdx].GetID();
+
+      // unselect everything & remove link to album (=logical delete)
+      var l = G.I.length;
+      for( var i = 0; i < l ; i++ ) {
+        var item = G.I[i];
+        if( item.albumID == albumID ) {
+          item.selected = false;
         }
-        ViewerMediaPanX( 0 );
-        ViewerMediaSetPosAndZoomOne( G.VOM.content.current, true );
       }
-    }
-     
-
-    // Display photo infos in popup/modal
-    function ItemDisplayInfo( ng2item ) {
-
-      var content = '<div class="nGY2PopupOneItem">' + ng2item.title + '</div>';
-      content    += '<div class="nGY2PopupOneItemText">' + ng2item.description + '</div>';
-      if( ng2item.author != '' ) {
-        content  += '<div class="nGY2PopupOneItemText">' + G.O.icons.user + ' ' + ng2item.author + '</div>';
-      }
-      if( ng2item.exif.model != '' ) {
-        content  += '<div class="nGY2PopupOneItemText">' + G.O.icons.config + ' ' + ng2item.exif.model + '</div>';
-      }
-      var sexif = G.O.icons.picture + ':';
-      if( ng2item.exif.flash != '' || ng2item.exif.focallength != '' || ng2item.exif.fstop != '' || ng2item.exif.exposure != '' || ng2item.exif.iso != '' || ng2item.exif.time != '' ) {
-      sexif += '<br>';
-      sexif += ng2item.exif.flash == '' ? '' : ' &nbsp; ' + ng2item.exif.flash;
-      sexif += ng2item.exif.focallength == '' ? '' : ' &nbsp; ' + ng2item.exif.focallength+'mm';
-      sexif += ng2item.exif.fstop == '' ? '' : ' &nbsp; f' + ng2item.exif.fstop;
-      sexif += ng2item.exif.exposure == '' ? '' : ' &nbsp; ' + ng2item.exif.exposure+'s';
-      sexif += ng2item.exif.iso == '' ? '' : ' &nbsp; ' + ng2item.exif.iso+' ISO';
-      if( ng2item.exif.time != '' ) {
-        // var date = new Date(parseInt(ng2item.exif.time));
-        // sexif += ' &nbsp; '+date.toLocaleDateString();
-        sexif += ' &nbsp; ' + ng2item.exif.time;
-      }
-      }
-      else {
-        sexif += ' n/a';
-      }
-      content += '<div class="nGY2PopupOneItemText">' + sexif + '</div>';
-
-      if( ng2item.exif.location != '' ) {
-        content += '<div class="nGY2PopupOneItemText">'+G.O.icons.location+' <a href="http://maps.google.com/maps?z=12&t=m&q='+encodeURIComponent(ng2item.exif.location)+'" target="_blank">'+ng2item.exif.location+'</a></div>';
-        // embed google map in iframe (no api key required)
-        content += '<iframe width="300" height="150" frameborder="0" scrolling="no" marginheight="0" marginwidth="0" src="https://maps.google.com/maps?&amp;t=m&amp;q='+encodeURIComponent( ng2item.exif.location ) +'&amp;output=embed"></iframe>';  
-      }
-      else {
-        content += '<div class="nGY2PopupOneItemText">' + G.O.icons.location + ': n/a</div>';
-      }
-
-      var r = { title: G.O.icons.viewerInfo, content: content };
-
-      // callback
-      var fu = G.O.fnPopupMediaInfo;
-      if( fu !== null ) {
-        typeof fu == 'function' ? r=fu(ng2item, r.title, r.content) : r=window[fu](ng2item, r.title, r.content);
-      }
-
-
-      Popup( r.title, r.content, 'Left');
-   
-    }
-    
-
-    
-    function ToolbarAddElt( elt ) {
-      var r = '<div class="ngbt ngy2viewerToolAction ',
-      e=elt.replace(/^\s+|\s+$/g, '');    // remove trailing/leading whitespace
-      switch( e ) {
-        case 'minimizeButton':
-        case 'minimize':
-          var ic = G.O.icons.viewerToolbarMin;
-          if( G.VOM.toolbarMode == 'min' ) {
-            ic = G.O.icons.viewerToolbarStd;
-          }
-          r += 'minimizeButton nGEvent" data-ngy2action="minimize">'+ic+'</div>';
-          break;
-        case 'previousButton':
-        case 'previous':
-          r += 'previousButton nGEvent" data-ngy2action="previous">'+ G.O.icons.viewerPrevious +'</div>';
-          break;
-        case 'pageCounter':
-          r += 'pageCounter nGEvent"></div>';
-          break;
-        case 'nextButton':
-        case 'next':
-          r += 'nextButton nGEvent" data-ngy2action="next">'+ G.O.icons.viewerNext +'</div>';
-          break;
-        case 'playPauseButton':
-        case 'playPause':
-          r += 'playButton playPauseButton nGEvent" data-ngy2action="playPause">'+ G.O.icons.viewerPlay +'</div>';
-          break;
-        case 'rotateLeft':
-          r += 'rotateLeftButton nGEvent" data-ngy2action="rotateLeft">'+ G.O.icons.viewerRotateLeft +'</div>';
-          break;
-        case 'rotateRight':
-          r += 'rotateRightButton nGEvent" data-ngy2action="rotateRight">'+ G.O.icons.viewerRotateRight +'</div>';
-          break;
-        case 'downloadButton':
-        case 'download':
-          r += 'downloadButton nGEvent" data-ngy2action="download">'+ G.O.icons.viewerDownload +'</div>';
-          break;
-        case 'zoomButton':
-        case 'zoom':
-          r += 'nGEvent" data-ngy2action="zoomIn">'+ G.O.icons.viewerZoomIn +'</div><div class="ngbt ngy2viewerToolAction nGEvent" data-ngy2action="zoomOut">'+ G.O.icons.viewerZoomOut +'</div>';
-          break;
-        case 'fullscreenButton':
-        case 'fullscreen':
-          var s = G.O.icons.viewerFullscreenOn;
-          if( ngscreenfull.enabled && G.VOM.viewerIsFullscreen ) {
-            s = G.O.icons.viewerFullscreenOff;
-          }
-          r += 'setFullscreenButton fullscreenButton nGEvent" data-ngy2action="fullScreen">'+s+'</div>';
-          break;
-        case 'infoButton':
-        case 'info':
-          r += 'infoButton nGEvent" data-ngy2action="info">'+ G.O.icons.viewerInfo +'</div>';
-          break;
-        case 'linkOriginalButton':
-        case 'linkOriginal':
-          r += 'linkOriginalButton nGEvent" data-ngy2action="linkOriginal">' + G.O.icons.viewerLinkOriginal + '</div>';
-          break;
-        case 'closeButton':
-        case 'close':
-          r += 'closeButton nGEvent" data-ngy2action="close">'+ G.O.icons.buttonClose +'</div>';
-          break;
-        case 'shareButton':
-        case 'share':
-          r += 'nGEvent" data-ngy2action="share">'+ G.O.icons.viewerShare +'</div>';
-          break;
-        case 'label':
-          r += '"><div class="label"><div class="title nGEvent" itemprop="name"></div><div class="description nGEvent" itemprop="description"></div></div></div>';
-          break;
-        case 'shoppingcart':
-          r += 'closeButton nGEvent" data-ngy2action="shoppingcart">'+ G.O.icons.viewerShoppingcart +'</div>';
-          break;
-        default:
-          // custom button
-          if( e.indexOf('custom') == 0 ) {
-            var t = '';
-            // content to display from custom script
-            var fu = G.O.fnImgToolbarCustInit;
-            if( fu !== null ) {
-              typeof fu == 'function' ? fu(e) : window[fu](e);
-            }
-            if( t == undefined || t == '' ) {
-              // content from icons
-              var n = e.substring(6);
-              t = G.O.icons['viewerCustomTool'+n];
-            }
-            r += 'ngy2CustomBtn ' + e + ' nGEvent" data-ngy2action="' + e + '">' + t + '</div>';
-          }
-          else {
-            r = '';
-          }
-          break;
-      }
-      return r;
-    }
-    
-    
-    // toggle slideshow mode on/off
-    function SlideshowToggle(){
-      if( G.VOM.playSlideshow ) {
-        window.clearTimeout(G.VOM.playSlideshowTimerID);
-        G.VOM.playSlideshow = false;
-        G.VOM.$viewer.find('.playPauseButton').html(G.O.icons.viewerPlay);
-      }
-      else {
-        G.VOM.playSlideshow = true;
-        DisplayNextMedia();
-        G.VOM.$viewer.find('.playPauseButton').html(G.O.icons.viewerPause);
-      }
-    }
-
-    function ViewerToolbarForVisibilityStd() {
-      G.VOM.toolbarMode = 'std';
-      
-      var sTB = '';
-      var t = G.O.viewerToolbar.standard.split(',');
-      for( var i = 0, lt = t.length; i < lt; i++) {
-        sTB += ToolbarAddElt( t[i] );
-      }
-      G.VOM.$toolbar.find('.toolbar').html(sTB);
-      ViewerToolbarElementContent();
-    }
-    
-    function ViewerToolbarForVisibilityMin() {
-      if( G.O.viewerToolbar.minimized == undefined || G.O.viewerToolbar.minimized == '' ) {
-        ViewerToolbarForVisibilityStd();
-      }
-      else {
-        G.VOM.toolbarMode = 'min';
-        var sTB = '';
-        var t = G.O.viewerToolbar.minimized.split(',');
-        for( var i = 0, lt = t.length; i < lt; i++) {
-          sTB += ToolbarAddElt( t[i] );
-        }
-        G.VOM.$toolbar.find('.toolbar').html(sTB);
-        ViewerToolbarElementContent();
-      }
-    }
-    
-    function ViewerToolbarElementContent() {
-    
-      var vomIdx = G.VOM.content.current.vIdx;
-      if( vomIdx == null ) { return; }
-      
-      var item = G.VOM.content.current.NGY2Item();
-    
-      // LABEL
-      var setTxt = false;
-      // set title
-      if( item.title !== undefined && item.title != '' ) {
-        G.VOM.$viewer.find('.ngy2viewerToolAction').find('.title').html(item.title);
-        setTxt = true;
-      }
-      else {
-        G.VOM.$viewer.find('.ngy2viewerToolAction').find('.title').html('');
-      }
-      // set description
-      if( item.description !== undefined && item.description != '' ) {
-        G.VOM.$viewer.find('.ngy2viewerToolAction').find('.description').html(item.description);
-        setTxt = true;
-      }
-      else {
-        G.VOM.$viewer.find('.ngy2viewerToolAction').find('.description').html('');
-      }
-      
-      if( setTxt ) {
-        G.VOM.$viewer.find('.ngy2viewerToolAction').find('.label').show();
-      }
-      else {
-        G.VOM.$viewer.find('.ngy2viewerToolAction').find('.label').hide();
-      }
-      
-      // set page number
-      var viewerMaxImages = G.VOM.items.length;
-      if( viewerMaxImages > 0 ) {
-        G.VOM.$viewer.find('.pageCounter').html((G.VOM.items[vomIdx].mediaNumber)+'/'+viewerMaxImages);
-      }
-      
-      // custom elements
-      var $cu = G.VOM.$viewer.find('.ngy2CustomBtn');
-      var fu = G.O.fnImgToolbarCustDisplay;
-      if( $cu.length > 0 && fu !== null ) {
-        typeof fu == 'function' ? fu($cu, item) : window[fu]($cu, item);
-      }
-      
-      // set event handlers again
-      ViewerToolsOn();
-    }
-    
-    // Pan the media container in the lightbox (left/right)
-    function ViewerMediaPanX( posX ) {
-      G.VOM.swipePosX = posX;
-      if( G.CSStransformName == null ) {
-        // no pan if CSS transform not supported
-        // G.VOM.$mediaCurrent.css({ left: posX }); 
-      }
-      else {
-      
-        // pan left/right the current media
-        // window.ng_draf( function() {
-          G.VOM.content.current.$media[0].style[G.CSStransformName] = 'translate(' + posX + 'px, 0px)';
-        // });
-
-        var itemPrevious = G.VOM.content.previous.NGY2Item();
-        var itemNext = G.VOM.content.next.NGY2Item();
-        
-        // next/previous media
-        if(  G.O.imageTransition.startsWith('SWIPE') ) {
-          if( itemPrevious.mediaTransition() ) {
-            ViewerSetMediaVisibility(G.VOM.content.previous, 1);
-          }
-          if( itemNext.mediaTransition() ) {
-            ViewerSetMediaVisibility(G.VOM.content.next, 1);
-          }
-
-          var sc = Math.min( Math.max( Math.abs(posX) / G.VOM.window.lastWidth, .8), 1);
-          if( G.O.imageTransition == 'SWIPE' ) { sc = 1; }
-
-          if( posX > 0 ) {
-            var dir = G.VOM.window.lastWidth;
-            if( itemPrevious.mediaTransition() ) {
-              // window.ng_draf( function() {
-                G.VOM.content.previous.$media[0].style[G.CSStransformName] = 'translate(' + (-dir + posX) + 'px, 0px) scale(' + sc + ')';
-              // });
-            }
-            if( itemNext.mediaTransition() ) {
-              // window.ng_draf( function() {
-                G.VOM.content.next.$media[0].style[G.CSStransformName] = 'translate(' + (dir) + 'px, 0px) scale(' + sc + ')';
-              // });
-            }
-          }
-          else {
-            var dir = -G.VOM.window.lastWidth;
-            if( itemNext.mediaTransition() ) {
-              // window.ng_draf( function() {
-                G.VOM.content.next.$media[0].style[G.CSStransformName] = 'translate(' + (-dir + posX) + 'px, 0px) scale(' + sc + ')';
-              // });
-            }
-            if( itemPrevious.mediaTransition() ) {
-              // window.ng_draf( function() {
-                G.VOM.content.previous.$media[0].style[G.CSStransformName] = 'translate(' + (dir) + 'px, 0px) scale(' + sc + ')';
-              // });
-            }
-          }
-        }
-        
-        
-        if(  G.O.imageTransition == 'SLIDEAPPEAR' ) {
-          G.VOM.content.previous.$media[0].style[G.CSStransformName] = '';
-          G.VOM.content.next.$media[0].style[G.CSStransformName] = '';
-          if( posX < 0 ) {
-            var o = (-posX) / G.VOM.window.lastWidth;
-            if( itemNext.mediaTransition() ) {
-              ViewerSetMediaVisibility(G.VOM.content.next, o);
-            }
-            if( itemPrevious.mediaTransition() ) {
-              ViewerSetMediaVisibility(G.VOM.content.previous, 0);
-            }
-          }
-          else {
-            var o = posX / G.VOM.window.lastWidth;
-            if( itemPrevious.mediaTransition() ) {
-              ViewerSetMediaVisibility(G.VOM.content.previous, o);
-            }
-            if( itemNext.mediaTransition() ) {
-              ViewerSetMediaVisibility(G.VOM.content.next, 0);
-            }
-          }
-        }
-      }
-    }
-    
-    // Display next image
-    function DisplayNextMedia( velocity ) {
-      velocity = velocity || 0;
-      
-      if( G.VOM.viewerMediaIsChanged || ((new Date().getTime()) - G.VOM.timeImgChanged < 300) ) { return; }
-      
-      TriggerCustomEvent('lightboxNextImage');
-      LightboxDisplay('nextImage', velocity);
-    };
-    
-    // Display previous image
-    function DisplayPreviousMedia( velocity ) {
-      velocity = velocity || 0;
-
-      if( G.VOM.viewerMediaIsChanged || ((new Date().getTime()) - G.VOM.timeImgChanged < 300) ) { return; }
-      if( G.VOM.playSlideshow ) {
-        SlideshowToggle();
-      }
-      
-      TriggerCustomEvent('lightboxPreviousImage');
-      LightboxDisplay( 'previousImage', velocity);
-    };
-
-
-
-    // Display image (with animation if possible)
-    function LightboxDisplay( displayType, velocity ) {
-
-      velocity = velocity || 0;
-
-      if( G.O.debugMode && console.timeline ) { console.timeline('nanogallery2_viewer'); }
-
-      if( G.VOM.playSlideshow ) { window.clearTimeout( G.VOM.playSlideshowTimerID ); }
-      
-      var current_content_item = null;
-      var new_content_item  = null;
-
-      G.VOM.timeImgChanged = new Date().getTime();
-      G.VOM.viewerMediaIsChanged = true;
-      G.VOM.zoom.isZooming = false;
-      ResizeLightbox(true);
-
-      switch( displayType ) {
-        case '':
-            current_content_item = G.VOM.content.current;
-            new_content_item = G.VOM.content.current;
-          break;
-        case 'previousImage':
-            current_content_item = G.VOM.content.current;
-            new_content_item = G.VOM.content.previous;
-          break;
-        default:
-            current_content_item = G.VOM.content.current;
-            new_content_item = G.VOM.content.next;
-      }
-
-      // SetLocationHash( next_ng2item.albumID, next_ng2item.GetID() );
-      SetLocationHash( new_content_item.NGY2Item().albumID, new_content_item.NGY2Item().GetID() );
-
-      if( displayType == '' ) {
-        // first media -> no transition -> exit
-        return;
-      }
-      
-      // animation duration is proportional of the remaining distance
-      var vP = G.GOM.cache.viewport;
-      var t_easing = '';
-      var t_dur = 400 * (vP.w - Math.abs(G.VOM.swipePosX)) / vP.w;
-      if( velocity > 0 ) {
-        // velocity = pixels/millisecond
-         t_dur = Math.min( (vP.w - Math.abs(G.VOM.swipePosX)) / velocity, t_dur);
-         t_easing = 'linear';     // use linear to avoid a slow-down in the transition after user swipe
-      }
-      
-        
-      // animate the image transition between 2 medias
-        
-      if( G.CSStransformName == null  ) {
-        // no CSS transform support -> no animation
-        ViewerSetMediaVisibility(new_content_item, 1);
-        ViewerSetMediaVisibility(current_content_item, 1);
-        LightboxDisplayFinalize(displayType);
-      }
-      else {
-        switch( G.O.imageTransition ) {
-          case 'SWIPE':
-          case 'SWIPE2':
-            var dir = ( displayType == 'nextImage' ? - vP.w : vP.w );
-            new_content_item.$media[0].style[G.CSStransformName] = 'translate(' + (-dir) + 'px, 0px) '
-
-            if( velocity == 0 ) {
-              t_easing = G.O.imageTransition == 'swipe' ? 'easeInOutSine' : 'easeInQuart';
-            }
-            
-            ViewerSetMediaVisibility(G.VOM.content.current, 1);
-            G.VOM.content.current.$media[0].style[G.CSStransformName] = 'translate(0px, 0px)';
-            ViewerSetMediaVisibility(new_content_item, 1);
-
-            new NGTweenable().tween({
-              from:         { t: G.VOM.swipePosX  },
-              to:           { t: (displayType == 'nextImage' ? - vP.w : vP.w) },
-              attachment:   { dT: displayType, new_content_item: new_content_item, dir: dir, media_transition: new_content_item.NGY2Item().mediaTransition()},
-              // delay:        30,
-              duration:     t_dur,
-              easing:       ( t_easing ),
-              step:         function (state, att) {
-                // current displayed media
-                G.VOM.content.current.$media[0].style[G.CSStransformName] = 'translate(' + state.t + 'px, 0px)';
-
-                // new media
-                if( att.media_transition ) {
-                  // new media supports transition
-                  var sc = Math.min( Math.max( (Math.abs(state.t)) / G.VOM.window.lastWidth, .8), 1);
-                  if( G.O.imageTransition == 'SWIPE' ) { sc = 1; }
-                  att.new_content_item.$media[0].style[G.CSStransformName] = 'translate(' + (-att.dir+state.t) + 'px, 0px) scale(' + sc + ')';
-                }
-              },
-              finish:       function (state, att) {
-                G.VOM.content.current.$media[0].style[G.CSStransformName] = '';
-                ViewerSetMediaVisibility(G.VOM.content.current, 0);
-                att.new_content_item.$media[0].style[G.CSStransformName] = '';
-                LightboxDisplayFinalize(att.dT);
-              }
-            });
-            break;
-            
-          case 'SLIDEAPPEAR':
-          default:
-            var dir=(displayType == 'nextImage' ? - vP.w : vP.w);
-            var op = (Math.abs(G.VOM.swipePosX)) / G.VOM.window.lastWidth;
-            new_content_item.$media[0].style[G.CSStransformName] = '';
-            if( velocity == 0 ) {
-              t_easing ='easeInOutSine';
-            }
-            new NGTweenable().tween({
-              from:         { o: op, t: G.VOM.swipePosX },
-              to:           { o: 1,  t: (displayType == 'nextImage' ? - vP.w : vP.w) },
-              attachment:   { dT: displayType, new_content_item:new_content_item, media_transition: new_content_item.NGY2Item().mediaTransition()  },
-              delay:        30,
-              duration:     t_dur,
-              easing:       t_easing,
-              step:         function (state, att) {
-                // current media - translate
-                G.VOM.content.current.$media[0].style[G.CSStransformName]= 'translate('+state.t+'px, 0px)';
-
-                // new media - opacity
-                if( att.media_transition ) {
-                  // new media supports transition
-                  ViewerSetMediaVisibility(att.new_content_item, state.o);
-                }
-              },
-              finish:       function (state, att) {
-                G.VOM.content.current.$media[0].style[G.CSStransformName]= '';
-                LightboxDisplayFinalize(att.dT);
-              }
-            });
-            break;
-        }
-      }
-    
-    }
-  
-
-    function LightboxDisplayFinalize( displayType ) {
-
-      var newVomIdx = 0;
-      switch( displayType ) {
-        case '':
-					// first media to display in lightbox
-					newVomIdx = G.VOM.content.current.vIdx;
-          break;
-        case 'previousImage':
-					// previous media
-					newVomIdx = G.VOM.content.previous.vIdx;
-          break;
-        default:
-					// next media
-					newVomIdx = G.VOM.content.next.vIdx;
-      }
-
-			
-
-      G.VOM.content.current.vIdx = newVomIdx;
-      G.VOM.content.next.vIdx = G.VOM.IdxNext();
-      G.VOM.content.previous.vIdx = G.VOM.IdxPrevious();
-			G.VOM.gallery.Resize();
-			G.VOM.gallery.SetThumbnailActive();
-      
-      var ngy2item = G.VOM.content.current.NGY2Item();
-
-      ViewerToolbarElementContent();
-      if( G.O.debugMode && console.timeline ) { console.timelineEnd('nanogallery2_viewer'); }
-
-      var fu=G.O.fnImgDisplayed;
-      if( fu !== null ) {
-        typeof fu == 'function' ? fu(ngy2item) : window[fu](ngy2item);
-      }
-      
-      G.VOM.swipePosX = 0;
-      if( displayType != '' ) {
-        // not on first media display
-        G.VOM.content.current.$media.removeClass('imgCurrent');
-
-        var $tmp = G.VOM.content.current.$media;
-        switch( displayType ) {
-          case 'nextImage':
-            G.VOM.content.current.$media = G.VOM.content.next.$media;
-            G.VOM.content.next.$media = $tmp;
-            break;
-          case 'previousImage':
-            G.VOM.content.current.$media =  G.VOM.content.previous.$media;
-             G.VOM.content.previous.$media = $tmp;
-            break;
-        }
-      }
-      
-      G.VOM.content.current.$media.addClass('imgCurrent');
-      
-      // re-sort the media containers --> current on top
-      var $pans = G.VOM.$content.find('.nGY2ViewerMediaPan');
-      G.VOM.content.current.$media.insertAfter($pans.last());
-      
-      if( ngy2item.mediaKind == 'img' && ngy2item.imageWidth == 0 ) {
-        ViewerSetMediaVisibility(G.VOM.content.current, 0);
-      }
-      else {
-        G.VOM.content.current.$media.children().eq(0).attr('class', 'nGY2ViewerMediaLoaderHidden');    // hide preloader
-        ViewerSetMediaVisibility(G.VOM.content.current, 1);
-      }
-
-      
-      // set the new NEXT media
-      G.VOM.content.next.$media.empty();
-      var nextItem = G.VOM.content.next.NGY2Item();
-      var spreloader = '<div class="nGY2ViewerMediaLoaderDisplayed"></div>';
-      if( nextItem.mediaKind == 'img' && nextItem.imageWidth != 0 && nextItem.imageHeight != 0 ) {
-        spreloader = '<div class="nGY2ViewerMediaLoaderHidden"></div>';
-      }
-      G.VOM.content.next.$media.append( spreloader + nextItem.mediaMarkup );
-      ViewerSetMediaVisibility(G.VOM.content.next, 0);
-      ViewerSetMediaVisibility(G.VOM.content.previous, 0);
-      if( nextItem.mediaKind == 'img' ) {
-        G.VOM.ImageLoader.loadImage(VieweImgSizeRetrieved, nextItem);
-      }
-      else {
-        ViewerMediaCenterNotImg( G.VOM.content.next.$media );
-      }
-
-      // set the new PREVIOUS media
-      G.VOM.content.previous.$media.empty();
-      var previousItem = G.VOM.content.previous.NGY2Item();
-      spreloader = '<div class="nGY2ViewerMediaLoaderDisplayed"></div>';
-      if( previousItem.mediaKind == 'img' && previousItem.imageWidth != 0 && previousItem.imageHeight != 0 ) {
-        spreloader = '<div class="nGY2ViewerMediaLoaderHidden"></div>';
-      }
-      G.VOM.content.previous.$media.append( spreloader + previousItem.mediaMarkup );
-      ViewerSetMediaVisibility(G.VOM.content.previous, 0);
-      ViewerSetMediaVisibility(G.VOM.content.next, 0);
-      if( previousItem.mediaKind == 'img' ) {
-        G.VOM.ImageLoader.loadImage( VieweImgSizeRetrieved, previousItem );
-      }
-      else {
-        ViewerMediaCenterNotImg( G.VOM.content.previous.$media );
-      }
-
-        
-      // slideshow mode - wait until image is loaded to start the delay for next image
-      if( G.VOM.playSlideshow ) {
-        G.VOM.content.current.$media.children().eq(1).ngimagesLoaded().always( function( instance ) {
-          if( G.VOM.playSlideshow ) {
-            // in the meantime the user could have stopped the slideshow
-            G.VOM.playSlideshowTimerID = window.setTimeout( function(){ DisplayNextMedia(); }, G.VOM.slideshowDelay );
-          }
-        });
-      }
-      
-      // close viewer when user clicks outside of the image
-      // G.VOM.$mediaCurrent.on("click",function(e){
-      //  e.stopPropagation();
-      //  if( (new Date().getTime()) - G.VOM.timeImgChanged < 400 ) { return; }
-      //  StopPropagationPreventDefault(e);
-      //  LightboxClose(G.VOM.currItemIdx);
-      //  return false;
-      // });
-
-      ResizeLightbox();
-
-      G.VOM.viewerMediaIsChanged = false;
-      TriggerCustomEvent('lightboxImageDisplayed');
-      
-    }
-
-    
-    // Is fired as soon as the size of an image has been retrieved (the download may not be finished)
-    function VieweImgSizeRetrieved(w, h, item, n) {
-
-      item.imageWidth = w;
-      item.imageHeight = h;
-  
-      // image sized retrieved for currently displayed media
-      if( G.VOM.content.current.NGY2Item() == item ) {
-        // it is the current displayed media
-        G.VOM.content.current.$media.children().eq(0).attr('class', 'nGY2ViewerMediaLoaderHidden');    // hide preloader
-        ViewerSetMediaVisibility(G.VOM.content.current, 1);
-        G.VOM.zoom.userFactor = 1;
-      }
       
-      if( G.VOM.content.next.NGY2Item() == item ) {   // next media
-        G.VOM.content.next.$media.children().eq(0).attr('class', 'nGY2ViewerMediaLoaderHidden');    // hide preloader
+      G.I[albumIdx].contentIsLoaded = false;
+      
+      DisplayAlbum('-1', albumID);
+    };
+    
+    /**
+     * Set one or several items selected/unselected
+     * @param {array} items
+     */
+    this.ItemsSetSelectedValue = function(items, value){
+      var l = items.length;
+      for( var j = 0; j < l ; j++) {
+        ThumbnailSelectionSet(items[j], value);
       }
-      if( G.VOM.content.previous.NGY2Item() == item ) {   // previous media
-        G.VOM.content.previous.$media.children().eq(0).attr('class', 'nGY2ViewerMediaLoaderHidden');    // hide preloader
+    };
+    
+    /**
+     * Returns an array of selected items
+     * @returns {Array}
+     */
+    this.ItemsSelectedGet = function(){
+      var selectedItems = [];
+      var l = G.I.length;
+      for( var i = 0; i < l ; i++ ) {
+        if( G.I[i].selected == true ) {
+          selectedItems.push(G.I[i]);
+        }
       }
-      
-      ViewerMediaSetPosAndZoom();
-
-    }
-
-    // Viewer - Set the visibility of the media and it's container
-    // function ViewerSetMediaVisibility(item, $media, opacity ) {
-    function ViewerSetMediaVisibility( content_item, opacity ) {
-
-			var item = content_item.NGY2Item();
-      var $media = content_item.$media;
-      
-      if( item.mediaKind == 'img' && item.imageWidth == 0 ) {
-        // do not display image if width is unknown (--> callback will set the width when know)
-        // setting opacity to 0 is not enough -> it is mandatory to change also the visibility to hidden to avoid responds to events (click/touch)
-        // $media.children().css({ opacity: 0, visibility: 'hidden' });
-        $media.children().eq(1).css({ opacity: 0, visibility: 'hidden' });    // hide media
-        // $media.css({ opacity: 0, visibility: 'hidden' });
-        return;
-      }
-      
-      if( opacity == 0 ) {
-        // setting opacity to 0 is not enough -> it is mandatory to change also the visibility to hidden to avoid responds to events (click/touch)
-        // $media.css({ opacity: 0, visibility: 'hidden' });
-        $media.children().css({ opacity: 0, visibility: 'hidden' });    // hide media
-      }
-      else {
-        // $media.css({ opacity: opacity, visibility: 'visible' });
-        $media.children().css({ opacity: opacity, visibility: 'visible' });      // display media
-      }
-    }
-    
-    
-    // Close the internal lightbox
-    function LightboxClose( vomIdx ) {
-
-
-      if( vomIdx == undefined ) {
-        vomIdx = G.VOM.content.current.vIdx;
-      }
-
-      G.VOM.viewerMediaIsChanged = false;
-
-      if( G.VOM.viewerDisplayed ) {
-
-        // set scrollbar visible again
-        jQuery("body").removeClass("nGY2_body_scrollbar");
-        jQuery("#nGY2_body_scrollbar_style").remove();
-        
-        if( G.VOM.playSlideshow ) {
-          window.clearTimeout( G.VOM.playSlideshowTimerID );
-          G.VOM.playSlideshow = false;
-        }
-
-        // G.VOM.userEvents.removeEventListeners();
-        // G.VOM.userEvents=null;
-        G.VOM.hammertime.destroy();
-        G.VOM.hammertime = null;
-
-        if( ngscreenfull.enabled && G.VOM.viewerIsFullscreen ) {
-          G.VOM.viewerIsFullscreen = false;
-          ngscreenfull.exit();
-        }
-        
-        G.VOM.$baseCont.hide(0).off().show(0).html('').remove();
-        G.VOM.viewerDisplayed = false;
-
-        if( G.O.lightboxStandalone ) { return; }
-
-        if( G.O.thumbnailAlbumDisplayImage ) {
-          // content of album displayed directly in lightbox (no gallery display for album content)
-          if( vomIdx == null ) {
-            // lightbox closed with browser back-button
-            // the gallery is already displayed
-          }
-          else {
-            var item = G.I[G.VOM.items[vomIdx].ngy2ItemIdx];
-            var parent = NGY2Item.Get(G, item.albumID);
-            if( G.GOM.albumIdx != parent.albumID ) {
-              // display album only if not already displayed
-              DisplayAlbum('-1', parent.albumID);
-            }
-            else {
-              GalleryResize();        
-              SetLocationHash( '', '' );
-              ThumbnailHoverReInitAll();
-            }
-          }
-            // DisplayAlbum( '-', G.I[G.VOM.items[vomIdx].ngy2ItemIdx].albumID );
-        }
-        else {
-          if( vomIdx != null ) {
-            if( G.GOM.albumIdx == -1 ) {
-              // album not displayed --> display gallery
-              DisplayAlbum( '', G.I[G.VOM.items[vomIdx].ngy2ItemIdx].albumID );
-            }
-            else {
-              GalleryResize();        
-              SetLocationHash( G.I[G.VOM.items[vomIdx].ngy2ItemIdx].albumID, '' );
-              ThumbnailHoverReInitAll();
-            }
-          }
-        }
-        G.VOM.timeImgChanged = new Date().getTime();
-      }
-    }
-
-    
-    // Lightbox resized -> reposition elements
-    function ResizeLightbox( forceUpdate ) {
-      forceUpdate = typeof forceUpdate !== 'undefined' ? forceUpdate : false;
-      
-      if( G.VOM.$toolbar === null ) { return; }   // viewer build not finished
-      
-      
-      // window.requestAnimationFrame( function() {    // synchronize with screen
-      var windowsW = G.VOM.$viewer.width();
-      var windowsH = G.VOM.$viewer.height();
-      var $elt = G.VOM.content.current.$media.children().eq(1);
-      if( $elt == null || G.VOM.content.current.vIdx == -1 ) { return; }
-
-      if( !forceUpdate && G.VOM.window.lastWidth == windowsW  && G.VOM.window.lastHeight == windowsH ) { return; }
-      
-      G.VOM.window.lastWidth = windowsW;
-      G.VOM.window.lastHeight = windowsH;
-
-      var $tb = G.VOM.$toolbar.find('.toolbar');
-      // var tb_OHt = $tb.outerHeight(true);
-
-
-      var galleryHeight = 0;
-      var cBottom = 0;
-			// Height of the thumbnails gallery
-			if( G.O.viewerGallery != 'none' ) {
-        galleryHeight = G.O.viewerGalleryTHeight + 10;
-			}
-			if( G.O.viewerGallery == 'bottom' ) {
-       cBottom = galleryHeight;
-			}
-
-			
-      switch( G.O.viewerToolbar.position ) {
-        case 'top':
-        case 'topOverImage':
-          G.VOM.$content.css({height: windowsH, width: windowsW, top: 0  });
-          G.VOM.$toolbar.css({top: 0, bottom: ''});
-          break;
-        // case 'top':
-          // windowsH -= tb_OHt;
-          // G.VOM.$content.css({height: windowsH, width: windowsW, top: tb_OHt  });
-          // G.VOM.$toolbar.css({top: 0});
-          // break;
-        case 'bottom':
-        case 'bottomOverImage':
-        default:
-          windowsH -= cBottom;
-          G.VOM.$content.css({height: windowsH, width: windowsW, bottom: -cBottom, top: 0  });
-          G.VOM.$toolbar.css({bottom: galleryHeight});
-          break;
-        // case 'bottom':
-        // default:
-          // windowsH -= tb_OHt;
-          // G.VOM.$content.css({ width: windowsW, top: 0, bottom: tb_OHt });
-          // G.VOM.$toolbar.css({bottom: galleryHeight});
-          // break;
-      }
-			
-			
-      if( !G.VOM.viewerMediaIsChanged && G.VOM.zoom.isZooming ) {
-        ViewerMediaSetPosAndZoom();
-      }
-      else {
-				if( !G.VOM.zoom.isZooming && ( G.VOM.zoom.userFactor != 0 || G.VOM.panPosX != 0 || G.VOM.panPosY != 0 || G.VOM.zoom.posX != 0 || G.VOM.zoom.posY != 0 )) {
-					// animate image zoom factor and position back to initial values
-					G.VOM.zoom.isZooming= true;		// activate zooming temporarily
-          new NGTweenable().tween({
-            from:           { userFactor: G.VOM.zoom.userFactor, panPosX: G.VOM.panPosX, panPosY: G.VOM.panPosY, zoomPosX: G.VOM.zoom.posX, zoomPosY: G.VOM.zoom.posY },
-            to:           { userFactor: 1, panPosX: 0, panPosY: 0, zoomPosX: 0, zoomPosY: 0 },
-            easing:       'easeInOutSine',
-            delay:        0,
-            duration:     150,
-            step:         function (state) {
-							G.VOM.zoom.userFactor = state.userFactor;
-							G.VOM.panPosX = state.panPosX;
-							G.VOM.panPosY = state.panPosY;
-							G.VOM.zoom.posX = state.zoomPosX;
-							G.VOM.zoom.posY = state.zoomPosY;
-							ViewerMediaSetPosAndZoom();
-            },
-            finish:       function (state) {
-							G.VOM.zoom.isZooming=false;
-            }
-          });
-			
-				}
-				else {
-					G.VOM.zoom.userFactor = 1;
-					G.VOM.zoom.isZooming = false;
-					G.VOM.panPosX = 0;
-					G.VOM.panPosY = 0;
-					G.VOM.zoom.posX = 0;
-					G.VOM.zoom.posY = 0;
-					ViewerMediaSetPosAndZoom();
-				}
-      }
-    }
-
-		// Retrieve the first parent element which is scrollable
-		// source: ncubica - https://stackoverflow.com/questions/35939886/find-first-scrollable-parent
-		// returns null if nothing found
-		function getScrollableParent (node) {
-			const regex = /(auto|scroll)/;
-			const parents = (_node, ps) => {
-				if (_node.parentNode === null) { return ps; }
-				return parents(_node.parentNode, ps.concat([_node]));
-			};
-
-			const style = (_node, prop) => getComputedStyle(_node, null).getPropertyValue(prop);
-			const overflow = _node => style(_node, 'overflow') + style(_node, 'overflow-y') + style(_node, 'overflow-x');
-			const scroll = _node => regex.test(overflow(_node));
-
-			const scrollParent = (_node) => {
-				if (!(_node instanceof HTMLElement || _node instanceof SVGElement)) {
-					return undefined;
-				}
-
-				const ps = parents(_node.parentNode, []);
-
-				for (let i = 0; i < ps.length; i += 1) {
-					if( ps[i] === document.body ) {
-						return null;
-					}
-					if (scroll(ps[i])) {
-						return ps[i];
-					}
-				}
-
-				return document.scrollingElement || document.documentElement;
-			};
-
-			return scrollParent(node);
-		};
-
-
-
-    /** @function BuildSkeleton */
-    /** Build the gallery structure **/
-    function BuildSkeleton() {
-
-		
-      // store markup if defined
-      // var $elements = G.$E.base.children('a');
-      var $elements = G.$E.base.children();
-      if( $elements.length > 0 ) {
-        G.O.$markup = $elements;
-      }
-    
-      if( !G.O.lightboxStandalone ) {
-        G.$E.base.text('');
-        G.$E.base.addClass('ngy2_container');
-      
-        // RTL or LTR
-        var sRTL='';
-        if( G.O.RTL ) {
-          sRTL = 'style="text-align:right;direction:rtl;"';
-        }
-      
-        // theme
-        G.$E.base.addClass(G.O.theme)
-        // gallery color scheme
-        SetGalleryTheme();
-
-        // Hide icons (thumbnails and breadcrumb)
-        if( G.O.thumbnailLabel.get('hideIcons') ) {
-          G.O.icons.thumbnailAlbum = '';
-          G.O.icons.thumbnailImage = '';
-        }
-
-        // Navigation bar
-        var styleNavigation="";
-        if( G.O.navigationFontSize != undefined && G.O.navigationFontSize != '' ) {
-          styleNavigation=' style="font-size:'+G.O.navigationFontSize+';"';
-        }      
-        G.$E.conNavigationBar = jQuery('<div class="nGY2Navigationbar" '+styleNavigation+'></div>').appendTo(G.$E.base);
-
-        // pre-loader
-        G.$E.conLoadingB = jQuery('<div class="nanoGalleryLBarOff"><div></div><div></div><div></div><div></div><div></div></div>').appendTo(G.$E.base);
-
-        // gallery
-        G.$E.conTnParent = jQuery('<div class="nGY2Gallery"></div>').appendTo( G.$E.base );
-        G.$E.conTn = jQuery('<div class="nGY2GallerySub"></div>').appendTo( G.$E.conTnParent );
-
-        // configure gallery
-        switch( G.O.thumbnailAlignment ) {
-          case 'left':
-            G.$E.conTnParent.css({'text-align':'left'});
-            // G.$E.conNavBCon.css({'margin-left':0 });
-            break;
-          case 'right':
-            G.$E.conTnParent.css({'text-align':'right'});
-            // G.$E.conNavBCon.css({ 'margin-right':0});
-            break;
-        }
-        
-        // apply galleryBuildInit2 css settings to the gallery
-        if( G.O.galleryBuildInit2 !== undefined ) {
-        var t1=G.O.galleryBuildInit2.split('|');
-          for( var i=0; i<t1.length; i++ ) {
-            var o1=t1[i].split('_');
-            if( o1.length == 2 ) {
-              G.$E.conTn.css(o1[0], o1[1]);
-            }
-          }
-        }
-        
-        // configure gallery depending on some thumbnail hover effects
-        var effects=G.tn.hoverEffects.std.concat(G.tn.hoverEffects.level1);
-        for( var j=0; j<effects.length; j++) {
-          switch( effects[j].type ) {
-            case 'scale':
-            case 'rotateZ':
-            case 'rotateX':
-            case 'rotateY':
-            case 'translateX':
-            case 'translateY':
-              // handle some special cases
-              if( effects[j].element == '.nGY2GThumbnail' ) {
-                // allow thumbnail upscale over the gallery's aera
-                G.$E.base.css('overflow', 'visible');
-                G.$E.base.find('.nGY2GallerySub').css('overflow', 'visible');
-                G.$E.conTnParent.css('overflow', 'visible');
-              }
-              break;
-          }
-        }
-        
-        // Gallery bottom container
-        G.$E.conTnBottom = jQuery('<div class="nGY2GalleryBottom" '+styleNavigation+'></div>').appendTo( G.$E.conTnParent );
-        
-        // portable edition
-        if( G.O.portable ) {
-          // http://www.picresize.com/
-          // http://base64encode.net/base64-image-encoder
-          // var logo='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB8AAAAWCAYAAAA4oUfxAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QA/wD/AP+gvaeTAAAAB3RJTUUH4QMPBwY6mxZgsAAABTFJREFUSMe1ll9oVGcaxn/fd86ZSWbSkEBMiWNdTTfRxiVbXFiU1bjKGqNexlURKys0tHqXpQZ64Sq4FxKqFy4qFSm9kA1FHNhFISgJqFCd6lL/YC7M3jhrJv5JmGSSMzPnzDnfuxdpZtP4b1vaF154P3gPD+/zPC/nVSKiAQOsBj7O5XK/nZiYeEtELH6iUEqFNTU1U9XV1d8AnwNfA1qJCMCfHz169NcjR45UXL16VWWzWQnD0PxU4JZl6draWtXW1iYHDx4sLlmy5C/AZwRB0JVOpyWRSHhACMjPmOHChQuL6XRagiDoUiIyumvXrpq+vr6obduqs7OTjRvbsbSFUgqUgKjyFG5+mlKpVH6LCMYYRAQRQSmF1hqtNd+xijGGVCpFMpkkCALZuXOn19fXN6Gmp6dNc3NzMDo66nR2dnL+/Hm+Ov933PwUAPHKagqei4gBFNs7dxGPx38U/du2bSOZTNLQ0FB6+PChbWez2WI+n3dEhI3tf+Det0N8de0Imz9YQWHa48u/3afjgxbqEpUM/es/uF8W+fijffi+TywWQ0S4fv06t2/fJpfLsXjxYtauXUtTUxNBECAihGFIJBJh1apVXLhwgXw+r7LZbNGeYU7MLD1BEPCLxkWs+HUT+SmPJY0TvPerd6l/J05YcLCGHWzbxrZtHjx4wP79+7l27dr3Jqyurqarq4ujR49i2zYAWmvCMJyVygCiZ7dh9kOtNb5XopD3KBQ8fL9EseBRyHsUCz6zS3Dnzh3WrVtXBq6oqGDBggUA5HI5jh07xo4dOzDmf0ujVBlGAWjmhTGC41hEow6RiI3j2DgRh0jUxonYWJaFGGHPnj2Mj49jWRYHDhzg7t27DA0NMTAwwOrVqwFIJpOcOHECx3Fe6oEXwG3bYux5ltHHz3mSGePpk+c8yczUI+knVFVVcePmDe7fvw9AT08Pvb29NDc3U1dXx4YNG7h8+TItLS1orTl58iT5fL68Ga8En55yWb6iifff/iPD/0iQGfglG3/zJ6a+beHf/3yH6Mjv+P269Vy5cgWlFDU1NXR3dxOGYdlcnudRVVXFvn37MMaQTqcZHh5+Kbg99zHjSodPuj997cqMjY0hItTW1hKPx9FalzW1LIswDFm0aBEAQRDguu6bJ581hOd5GBNiTEgYhuXa8z1EhIaGBgAymQzpdBqlFKVSiTCc6bcsi5s3bwJQWVlJfX39fMO9XHMAy7LQeibn1o7toJSio6MDAN/36e7uxvd9IpEIlmURjUZJpVKcOXMGpRStra0sXbr0peDfo30+LS+4U2uMMaxcuZLdu3dz7tw5+vv7aWtrY+/evdTX13Pr1i1OnTrF5OQkAIcPH8ayrNeCvx51njTGGE6fPk0mk2FwcJBUKkUqlXqh9/jx42zatKnMzJzhBEArpZT+zjGWZSEiBEHwypzVtbKykosXL3Lo0CEaGxvLpovFYqxZs4ZLly6VJQnDEBEpM6C11kopheu6JpFI+Fpr2bJli/zYGBkZkeHhYZmcnHxlz9atW0VrLYlEwndd19ixWOzx5s2b3z579qzp7+/X7e3ttLa2Yox5QaP5MfenEY1G0VoTBAHFYhFjTJlJrTX37t1jYGAAY4zp6OiQWCz2mCAItj979kyWL1/uAwE/7zERLFu2zH/69KkEQbB99ozaOz4+fqy3t7d2cHAwdF1XKaXe6P7/16AiQjwel/Xr1+uenp6Jurq6T4Av1JwD8j3gQ2BVsVh8S72J8x8QIiIVFRVTQAo4CwwB+r93qCLI9wKZ8AAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNy0wMy0xNVQwNzowNjo1OC0wNDowMBNQsyUAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTctMDMtMTVUMDc6MDY6NTgtMDQ6MDBiDQuZAAAAAElFTkSuQmCC';
-          var logo = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAYCAYAAACbU/80AAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QA/wD/AP+gvaeTAAAAB3RJTUUH4QgDBCAWVVC/hwAABRxJREFUSMetll9oVFcexz/nnDvJRBmSzWTrmD9uNGZsHta0/qFIFQTxRcnCBgTFNlX0YR8W+1AK9lGwCBJYgn0KKr5136S4gpUQTR4caJRslcxYWV3iaphQapJJppO5957z60Mmk4mN1q75wg/OPefc+/v9vt/fueenKEFEqICqsNWAVNiCA7XwaS0iZeejo6OIiCltdIBdJXMLOYp5/PjxsoTVS5nr0mYDJIE/lObeBhaYAn4oJbboAwBvBedHJicnPx8YGGh/8eJF1dvKoJSShoYGf//+/Zl4PP4l8M2yIEoSLErx6c2bN6W1tXVRglWzLVu2SCqVEhE5LiI457SIoEREW2udMaZtcnLy+2QyWZ3L5XRHR4f+4MNdoBUahUJhcWilmZ/NE4ZhOQHn3LIi1lqjtS6vjY6O8uTJE9vc3MyDBw+mYrHYn0Uk63me8gCtlHLA7uHh4bW5XC7oePddPTQ8xHffDjM/PYe3thqMws35iAcHPj5ENBp9Yxmy2Sw7d+40z549C+7du9ewb9++D6y13wDaK+kE0DAzMyNKKbXtvfd5EfzM+Ef/4C+8x23+wzPm+IhtfMf3/Ksuyl+7u9FaY63l+vXrpFIpCoUCmzdvpquri9bWVoIgQClFIpFg48aNPH/+XE9NTQkQLTGmvEXKRERprZWIEIYhQRjQbN6hmUb+tCaPNnM055v40f3If7XBGMPT8af0fNLD0NDQsozPnDlDb28vx44dIwxDRARrLSKCKmUbiUQQkWWnoLJ20UpjFYAjVA6rBJTFV5ZIJIIfBBw4eICxsTHq6uo4dOgQ8XicgYEB7t69y/Hjx4nH43R1dVHB8q+w4hlXSmGd5edwmjCco5DLkZ+aJvTnyIdTrFmzhn9+/TVjY2M0NTVx+/Zt+vv7OXfuHKlUip6eHgBOnz6N7/vlYl0JKzIw78/T+sdGbn6yjf5ZS2HtJgIP+mcC5kySI1uSXPjqAlprTp06RWdnJ8ViEaUUVVVVnD9/nqtXr5LJZHj48CFbt279fQEEYUisZi2fXel9bWU750gmkwRBgNYaz/Ow1lJfX088Hmd2dpZcLvdaBl4pgQChH4B1iHU4a8E6Qj9ARGhpaUFrzeDgIJFIBGMM1lqMMWQyGSYmJohEIqxfv/7314CIoADtGTAaZTTaLI2VUhw+fBjnHBcvXuTy5cs45/A8j3Q6zcmTJ/F9n71799LW1rbgSOs3D+B1lBljcM7R3d3N0aNHKRQKnDhxgs7OTnbt2sX27dsZGRkhHo/T19e3+Kt/fQ1YawFwzolSCs/zUEqVtX1VcJcuXSKRSNDf3086nS6v79mzh76+Pjo6OigWi1RXV2OMWZC29PL8/PxSAL7vE41Gf4rFYkpEePToEb7vU1VVxW+ht7eXs2fPcv/+fQqFAps2baKlpaW8Xl1dTS6XY3x8HBFxtbW1BiiW4hAlInp8fNxt2LChPZvN/ru9vT2Sz+e93bt3qx07diwrzJWYcM5RU1NDNBots5bP53HOlS+kO3fuMDIy4hKJhKTT6ena2tqtxWJxoqamRr98HX9x7do1qaurExYaiXCVzK5bt04GBwdFRP728nVcWZAO+Hsmk/nsxo0bTTMzM5FXHZ83hYhQX1/vHzx48H9tbW1ngSsVvpYCmJ2dJRaLKRbapjpgOxB7K+9LmAbuAnOAnpiYcI2NjUsRLlo2myUMQ1M5t5rmnDO3bt1aNlfmd4W2XL/0/H8pUDF2rNCW/wLRuCkxx8V6wgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNy0wOC0wM1QwNDozMjoyMi0wNDowMO7mdkwAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTctMDgtMDNUMDQ6MzI6MjItMDQ6MDCfu87wAAAAAElFTkSuQmCC';
-          var st = "font-weight:bold !important;color: #FF0075 !important;font-size: 14px !important;text-transform: lowercase !important;cursor:pointer !important;text-align: center !important;Text-Shadow: #000000 1px 0px 0px, #000000 1px 1px 0px, #000000 1px -1px 0px, #000000 -1px 1px 0px, #000000 -1px 0px 0px, #000000 -1px -1px 0px, #000000 0px 1px 0px, #000000 0px -1px 0px !important;";
-          G.$E.ngy2i=jQuery('<div class="nGY2PortInfo"><a href="http://nano.gallery" target="_blank" title="nanogallery2 | easy photo gallery for your website" style="' + st + '"><img src="' + logo + '" style="height:32px !important;width:initial !important;box-shadow: none !important;vertical-align: middle !important;"/> &nbsp; nanogallery2</a></div>').appendTo(G.$E.base);
-          
-          G.$E.ngy2i.find('a').on({
-            mouseenter: function () {
-              jQuery(this).attr('style', st);
-            },
-            mouseleave: function () {
-              jQuery(this).attr('style', st);
-            }
-          });
-        }
-      }
-      
-      // Error console
-      G.$E.conConsole = jQuery('<div class="nGY2ConsoleParent"></div>').appendTo(G.$E.base);
-
-      // i18n translations
-      i18n();
-
-      if( !G.O.lightboxStandalone ) {
-        // cache some thumbnails data (sizes, styles...)
-        ThumbnailDefCaches();
-
-        // do special settings depending for some options
-        // thumbnail display transition
-        switch( G.tn.opt.Get('displayTransition') ) {
-          case 'SCALEDOWN':
-          case 'RANDOMSCALE':
-          default:
-            G.$E.base.css('overflow', 'visible');
-            G.$E.conTnParent.css('overflow', 'visible');
-            G.$E.conTn.css('overflow', 'visible');
-            break;
-        }
-      }
-      
-    }
-    
-    function TriggerCustomEvent ( eventName ) {
-      // G.$E.base.trigger('pageChanged.nanogallery2', new Event('pageChanged.nanogallery2'));
-      var eN = eventName + '.nanogallery2';
-      var event=null;
-      try {
-          event = new Event( eN );
-        } catch(e) {
-          event = document.createEvent('Event');
-          event.initEvent(eN, false, false);
-        }
-        G.$E.base.trigger(eN, event);
-    }
-    
-    
-    /** @function SetGlobalEvents */
-    function SetGlobalEvents() {
-      // GLOBAL EVENT MANAGEMENT
-
-      if( !G.O.lightboxStandalone ) {
-        G.$E.conTnParent.on({
-          mouseenter: GalleryMouseEnter,
-          mouseleave: GalleryMouseLeave
-        }, ".nGY2GThumbnail");    //pass the element as an argument to .on
-      
-        // G.GOM.hammertime = new NGHammer(G.$E.conTn[0], { touchAction: 'none' });
-        G.GOM.hammertime = new NGHammer( G.$E.conTn[0] );
-        // G.GOM.hammertime.domEvents = true;
-      
-        
-        // PAN on gallery (pagination)
-        G.GOM.hammertime.on('pan', function(ev) {
-          if( !G.VOM.viewerDisplayed ) {
-            if( G.O.paginationSwipe && G.layout.support.rows && G.galleryDisplayMode.Get() == 'PAGINATION' ) {
-              if( Math.abs(ev.deltaY) > G.GOM.panThreshold ) {
-                G.GOM.panYOnly = true;
+      return selectedItems;
+    };    
+    
+    /**
+     * Returns the value of an option
+     * @param {string} option
+     * @returns {nanoGALLERY.G.O}
+     */
+    this.Get = function(option){
+        return G.O[option];
+    };
+
+    /**
+     * Set a new value for a defined option
+     * @param {string} option
+     */
+    this.Set = function(option, value){
+        G.O[option] = value;
+        switch( option ) {
+          case 'thumbnailSelectable':
+            ThumbnailSelectionClear();
+            // refresh the displayed gallery
+            GalleryRender( G.GOM.albumIdx );
+            break;
+        }
+    };
+    
+    /**
+     * Refresh the current gallery
+     */
+    this.Refresh = function() {
+      // Refresh the displayed gallery
+      GalleryRender( G.GOM.albumIdx );
+    };
+    /**
+     * Resize the current gallery
+     */
+    this.Resize = function() {
+      // resize the displayed gallery
+      GalleryResize();
+    };
+
+    /**
+     * display one item (image or gallery)
+     *   itemID syntax:
+     *    - albumID --> display one album
+     *    - albumID/imageID --> display one image
+    */
+    this.DisplayItem = function( itemID ) {
+      var IDs=parseIDs( itemID );
+      if( IDs.imageID != '0' ) {
+        DisplayPhoto( IDs.imageID, IDs.albumID );
+      }
+      else {
+        DisplayAlbum( '-1', IDs.albumID );
+      }
+    };
+    
+    this.ThumbnailToolbarOneCartUpdate = function ( item ) {
+      ThumbnailBuildToolbarOneCartUpdate( item );
+    }
+    
+      
+      
+    var CountItemsToDisplay = function( gIdx ) {
+      if( G.I[gIdx] == undefined ) { return 0; }
+      var albumID = G.I[gIdx].GetID();
+      var l = G.I.length;
+      var cnt = 0;
+      for( var idx = 0; idx < l; idx++ ) {
+        var item = G.I[idx];
+        if( item.isToDisplay(albumID) ) {
+          cnt++;
+        }
+      }
+      return cnt;
+    } 
+    /**
+     * Search in the displayed gallery (in thumbnails title)
+     */
+    this.Search = function( search ) {
+      G.GOM.albumSearch = search.toUpperCase();
+      var gIdx = G.GOM.albumIdx;
+      GalleryRender( G.GOM.albumIdx );
+      return CountItemsToDisplay( gIdx );
+    };
+
+    /**
+     * Search2 in title and tags - set search values
+     */
+    this.Search2 = function( searchTitle, searchTags ) {
+      if( searchTitle != null && searchTitle != undefined ) {
+        G.GOM.albumSearch = searchTitle.toUpperCase().trim();
+      }
+      else {
+        G.GOM.albumSearch = '';
+      }
+      
+      if( searchTags != null && searchTags != undefined ) {
+        G.GOM.albumSearchTags = searchTags.toUpperCase().trim();
+      }
+      else {
+        G.GOM.albumSearchTags = '';
+      }
+      return CountItemsToDisplay( G.GOM.albumIdx );
+    };
+    
+    /**
+     * Search2 - execute the search on title and tags
+     */
+    this.Search2Execute = function() {
+      var gIdx = G.GOM.albumIdx;
+      GalleryRender( G.GOM.albumIdx );
+      return CountItemsToDisplay( gIdx );
+    };
+    
+    
+    /**
+     * Destroy the current gallery
+     */
+    this.Destroy = function(){
+
+      if( G.GOM.hammertime != null ) {
+        G.GOM.hammertime.destroy();
+        G.GOM.hammertime = null;
+      }
+
+      if( G.VOM.hammertime != null ) {
+        G.VOM.hammertime.destroy();
+        G.VOM.hammertime = null;
+      }
+      
+      // color scheme
+      $('#ngycs_' + G.baseEltID).remove()
+      
+      G.GOM.items = [];
+      NGY2Item.New( G, G.i18nTranslations.breadcrumbHome, '', '0', '-1', 'album' );
+      G.GOM.navigationBar.$newContent = null;
+      G.$E.base.empty();
+      G.$E.base.removeData();
+      if( G.O.locationHash ) {
+        jQuery(window).off('hashchange.nanogallery2.' + G.baseEltID);
+      }
+
+      jQuery(window).off('resize.nanogallery2.' + G.baseEltID);
+      jQuery(window).off('orientationChange.nanogallery2.' + G.baseEltID);
+      jQuery(window).off('scroll.nanogallery2.' + G.baseEltID);
+			if( G.$E.scrollableParent !== null ) {
+				G.$E.scrollableParent.off('scroll.nanogallery2.' + G.baseEltID);
+			}
+      G.GOM.firstDisplay = true;
+    };
+    
+    /**
+     * CloseViewer - close the media viewer
+     */
+    this.CloseViewer = function() {
+      LightboxClose(null);
+      return false;
+    };
+    
+    /**
+     * MinimizeToolbar - display the minimized lightbox main toolbar
+     */
+    this.MinimizeToolbar = function() {
+      ViewerToolbarForVisibilityMin();
+      return false;
+    };
+    
+    /**
+     * MaximizeToolbar - display the maximized/standard lightbox main toolbar
+     */
+    this.MaximizeToolbar = function() {
+      ViewerToolbarForVisibilityStd();
+      return false;
+    };
+    
+    /**
+     * PaginationPreviousPage - gallery paginate to previous page
+     */
+    this.PaginationPreviousPage = function() {
+      paginationPreviousPage();
+      return false;
+    };
+    
+    
+    /**
+     * PaginationNextPage - gallery paginate to next page
+     */
+    this.PaginationNextPage = function() {
+      paginationNextPage();
+      return false;
+    };
+    
+    
+    /**
+     * PaginationGotoPage - gallery paginate to specific page
+     */
+    this.PaginationGotoPage = function( page ) {
+      var aIdx = G.$E.conPagin.data('galleryIdx');
+      if( page > 1 ) { page--; }
+      G.GOM.pagination.currentPage = page;
+
+      // scroll to top of gallery if not displayed
+      G.GOM.ScrollToTop();
+
+      GalleryDisplayPart1( true );
+      GalleryDisplayPart2( true );
+      return false;
+    };
+
+    /**
+     * PaginationCountPages - gallery pagination - returns the number of pages
+     */
+    this.PaginationCountPages = function() {
+      if( G.GOM.items.length == 0 ) { return 0; }   // no thumbnail to display
+
+      var nbPages = Math.ceil((G.GOM.items[G.GOM.items.length - 1].row + 1) / G.galleryMaxRows.Get());
+      return nbPages;
+    };
+    
+    /**
+     * PaginationCountPages - gallery pagination - returns the number of pages
+     */
+
+    
+    
+    // throttle()
+    // author: underscore.js - http://underscorejs.org/docs/underscore.html
+    // Returns a function, that, when invoked, will only be triggered at most once during a given window of time.
+    // Normally, the throttled function will run as much as it can, without ever going more than once per wait duration;
+    // but if you’d like to disable the execution on the leading edge, pass {leading: false}.
+    // To disable execution on the trailing edge, ditto.
+    var throttle = function(func, wait, options) {
+      var context, args, result;
+      var timeout = null;
+      var previous = 0;
+      if (!options) options = {};
+      var later = function() {
+        previous = options.leading === false ? 0 : new Date().getTime();
+        timeout = null;
+        result = func.apply(context, args);
+        if (!timeout) context = args = null;
+      };
+      return function() {
+        var now = new Date().getTime();
+        if (!previous && options.leading === false) previous = now;
+        var remaining = wait - (now - previous);
+        context = this;
+        args = arguments;
+        if (remaining <= 0 || remaining > wait) {
+          if (timeout) {
+            clearTimeout(timeout);
+            timeout = null;
+          }
+          previous = now;
+          result = func.apply(context, args);
+          if (!timeout) context = args = null;
+        } else if (!timeout && options.trailing !== false) {
+          timeout = setTimeout(later, remaining);
+        }
+        return result;
+      };
+    };
+    
+    
+    // DEBOUNCE
+    // author: John Hann - http://unscriptable.com/2009/03/20/debouncing-javascript-methods/
+    // execAsap - false means executing at the end of the detection period
+    var debounce = function (func, threshold, execAsap) {
+      var timeout;
+      return function debounced () {
+          var obj = this, args = arguments;
+          function delayed () {
+              if (!execAsap)
+                  func.apply(obj, args);
+              timeout = null; 
+          };
+   
+          if (timeout)
+              clearTimeout(timeout);
+              // clearRequestTimeout(timeout);
+          else if (execAsap)
+              func.apply(obj, args);
+          timeout = setTimeout(delayed, threshold || 100); 
+          // timeout = requestTimeout(delayed, threshold || 100); 
+      };
+    }
+    
+
+    // Double requestAnimationFrame
+    window.ng_draf = function (cb) {
+      return requestAnimationFrame(function() {
+        window.requestAnimationFrame(cb)
+      })
+    }    
+    
+    // REQUESTTIMEOUT - replace SETTIMEOUT - https://gist.github.com/joelambert/1002116
+    /**
+     * Behaves the same as setTimeout except uses requestAnimationFrame() where possible for better performance
+     * @param {function} fn The callback function
+     * @param {int} delay The delay in milliseconds
+     */
+
+    window.requestTimeout = function(fn, delay) {
+      // if( !window.requestAnimationFrame      	&& 
+        // !window.webkitRequestAnimationFrame && 
+        // !(window.mozRequestAnimationFrame && window.mozCancelRequestAnimationFrame) && // Firefox 5 ships without cancel support
+        // !window.oRequestAnimationFrame      && 
+        // !window.msRequestAnimationFrame)
+          return window.setTimeout(fn, delay);
+          
+      var start = new Date().getTime(),
+        handle = new Object();
+        
+      function loop(){
+        var current = new Date().getTime(),
+          delta = current - start;
+  delta = delay;
+  
+        delta >= delay ? fn.call() : handle.value = requestAnimFrame(loop);
+      };
+      
+      handle.value = requestAnimFrame(loop);
+      return handle;
+    };
+
+    
+    // requestAnimationFrame() shim by Paul Irish
+    // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
+    window.requestAnimFrame = (function() {
+      return  window.requestAnimationFrame       || 
+          window.webkitRequestAnimationFrame || 
+          window.mozRequestAnimationFrame    || 
+          window.oRequestAnimationFrame      || 
+          window.msRequestAnimationFrame     || 
+          function(/* function */ callback, /* DOMElement */ element){
+            window.setTimeout(callback, 1000 / 60);
+          };
+    })();    
+    
+    
+    // CLEARREQUESTTIMEOUT - to replace CLEARTIMEOUT - https://gist.github.com/joelambert/1002116
+    /**
+     * Behaves the same as clearTimeout except uses cancelRequestAnimationFrame() where possible for better performance
+     * @param {int|object} fn The callback function
+     */
+    window.clearRequestTimeout = function(handle) {
+        window.cancelAnimationFrame ? window.cancelAnimationFrame(handle.value) :
+        window.webkitCancelAnimationFrame ? window.webkitCancelAnimationFrame(handle.value) :
+        window.webkitCancelRequestAnimationFrame ? window.webkitCancelRequestAnimationFrame(handle.value) : /* Support for legacy API */
+        window.mozCancelRequestAnimationFrame ? window.mozCancelRequestAnimationFrame(handle.value) :
+        window.oCancelRequestAnimationFrame	? window.oCancelRequestAnimationFrame(handle.value) :
+        window.msCancelRequestAnimationFrame ? window.msCancelRequestAnimationFrame(handle.value) :
+        clearTimeout(handle);
+    };    
+    
+    
+    
+    /*
+    ** Global data for this nanogallery2 instance
+    **/
+    var G=this;
+    G.I =                       [];           // gallery items
+    G.Id =                      [];           // gallery items
+    G.O =                       null;         // user options
+    G.baseEltID =               null;         // ID of the base element
+    G.$E = { 
+      base:                     null,         // base element
+      conTnParent:              null,         // $g_containerThumbnailsParent
+      conLoadingB:              null,         // loading bar - nanoGalleryLBarOff
+      conConsole:               null,         // console for error messages
+      conNavigationBar:         null,         // gallery navigation bar
+      conTnBottom:              null,         // container on the bottom of the gallery
+			scrollableParent:					null					// first scrollable parent container
+    };
+    G.shoppingCart =            [];
+    G.layout = {                              // Layout informations
+      internal :                true,
+      engine :                  '',
+      support :                 { rows: false },
+      prerequisite :            { imageSize: false },
+      SetEngine: function() {
+
+      if( G.layout.internal ) {
+          if( G.tn.settings.width[G.GOM.curNavLevel][G.GOM.curWidth] == 'auto' || G.tn.settings.width[G.GOM.curNavLevel][G.GOM.curWidth] == '' ) {
+            // do not use getH() / getW() here!
+            G.layout.engine = 'JUSTIFIED';
+            G.layout.support.rows = true;
+            G.layout.prerequisite.imageSize = true;
+            return;
+          }
+          if( G.tn.settings.height[G.GOM.curNavLevel][G.GOM.curWidth] == 'auto' || G.tn.settings.height[G.GOM.curNavLevel][G.GOM.curWidth] == '' ) {
+            // do not use getH() / getW() here!
+            G.layout.engine = 'CASCADING';
+            G.layout.support.rows = false;
+            G.layout.prerequisite.imageSize = true;
+            return;
+          }
+
+          if( G.tn.settings.getMosaic() != null ) {
+            G.layout.engine = 'MOSAIC';
+            G.layout.support.rows = true;
+            G.layout.prerequisite.imageSize = false;
+            return;
+          }
+
+          G.layout.engine = 'GRID';
+          G.layout.support.rows=true;
+          // if( G.tn.opt.Get('crop') === true ) {
+            // G.layout.prerequisite.imageSize = true;
+          // }
+          // else {
+            G.layout.prerequisite.imageSize = false;
+          // }
+        }
+      }
+    };
+    G.galleryResizeEventEnabled = false;
+    G.galleryMaxRows = { l1: 0, lN: 0,
+      Get: function() {
+        return G.galleryMaxRows[G.GOM.curNavLevel];
+      }
+    };
+    G.galleryMaxItems = { l1: 0, lN: 0,
+      Get: function() {
+        return G.galleryMaxItems[G.GOM.curNavLevel];
+      }
+    };
+    G.galleryFilterTags = { l1: 0, lN: 0,
+      Get: function() {
+        return G.galleryFilterTags[G.GOM.curNavLevel];
+      }
+    };
+    G.galleryFilterTagsMode = { l1: 0, lN: 0,
+      Get: function() {
+        return G.galleryFilterTagsMode[G.GOM.curNavLevel];
+      }
+    };
+    G.galleryDisplayMode = { l1: 'FULLCONTENT', lN: 'FULLCONTENT',
+      Get: function() {
+        return G.galleryDisplayMode[G.GOM.curNavLevel];
+      }
+    };
+    G.galleryLastRowFull = { l1: false, lN: false,
+      Get: function() {
+        return G.galleryLastRowFull[G.GOM.curNavLevel];
+      }
+    };
+    G.gallerySorting = { l1: '', lN: '',
+      Get: function() {
+        return G.gallerySorting[G.GOM.curNavLevel];
+      }
+    };
+    G.galleryDisplayTransition = { l1: 'none', lN: 'none',
+      Get: function() {
+        return G.galleryDisplayTransition[G.GOM.curNavLevel];
+      }
+    };
+    G.galleryDisplayTransitionDuration = { l1: 500, lN: 500,
+      Get: function() {
+        return G.galleryDisplayTransitionDuration[G.GOM.curNavLevel];
+      }
+    };
+    G.$currentTouchedThumbnail = null;    
+    
+    
+    
+    
+    // ##### GENERAL THUMBNAILS PROPERTIES -->
+    G.tn = {
+      // levell specific options
+      opt:  {
+        l1: { crop: true, stacks: 0, stacksTranslateX: 0, stacksTranslateY: 0, stacksTranslateZ: 0, stacksRotateX: 0, stacksRotateY: 0, stacksRotateZ: 0, stacksScale: 0, borderHorizontal: 0, borderVertical: 0, baseGridHeight: 0, displayTransition: 'FADEIN', displayTransitionStartVal: 0, displayTransitionEasing: 'easeOutQuart', displayTransitionDuration: 240, displayInterval: 15 },
+        lN: { crop: true, stacks: 0, stacksTranslateX: 0, stacksTranslateY: 0, stacksTranslateZ: 0, stacksRotateX: 0, stacksRotateY: 0, stacksRotateZ: 0, stacksScale: 0, borderHorizontal: 0, borderVertical: 0, baseGridHeight: 0, displayTransition: 'FADEIN', displayTransitionStartVal: 0, displayTransitionEasing: 'easeOutQuart', displayTransitionDuration: 240, displayInterval: 15 },
+        Get: function(opt) {
+          return G.tn.opt[G.GOM.curNavLevel][opt];
+        }
+      },
+      scale:                          1,         // image scale depending of the hover effect
+      labelHeight: {                  // in case label on bottom, otherwise always=0
+        l1: 0, lN: 0,
+        get: function() {
+          return G.tn.labelHeight[G.GOM.curNavLevel];
+        }
+      },
+      defaultSize: {                  // default thumbnail size
+                                      // label height is not included
+        width: {  l1 : { xs:0, sm:0, me:0, la:0, xl:0 }, lN : { xs:0, sm:0, me:0, la:0, xl:0 } },
+        height: { l1 : { xs:0, sm:0, me:0, la:0, xl:0 }, lN : { xs:0, sm:0, me:0, la:0, xl:0 } },
+        getWidth: function() {
+          return G.tn.defaultSize.width[G.GOM.curNavLevel][G.GOM.curWidth];
+        },
+        getOuterWidth: function() {     // width including border
+      G.tn.borderWidth = G.tn.opt.Get('borderHorizontal');
+      G.tn.borderHeight = G.tn.opt.Get('borderVertical');
+          var w = G.tn.defaultSize.width[G.GOM.curNavLevel][G.GOM.curWidth] + G.tn.opt.Get('borderHorizontal') * 2;
+          if( G.O.thumbnailLabel.get('position') == 'right' || G.O.thumbnailLabel.get('position') == 'left' ) {
+            w += G.tn.defaultSize.width[G.GOM.curNavLevel][G.GOM.curWidth];
+          }
+          return w;
+        },
+        getHeight: function() {
+          return G.tn.defaultSize.height[G.GOM.curNavLevel][G.GOM.curWidth];
+        },
+        getOuterHeight: function() {     // height, border included
+          return G.tn.defaultSize.height[G.GOM.curNavLevel][G.GOM.curWidth]+G.tn.opt.Get('borderVertical')*2;
+        }
+      },
+      settings: {                     // user defined width/height of the image to display depending on the screen size
+        width: {  l1 : { xs:0, sm:0, me:0, la:0, xl:0, xsc:'u', smc:'u', mec:'u', lac:'u', xlc:'u' },
+                  lN : { xs:0, sm:0, me:0, la:0, xl:0, xsc:'u', smc:'u', mec:'u', lac:'u', xlc:'u' } },
+        height: { l1 : { xs:0, sm:0, me:0, la:0, xl:0, xsc:'u', smc:'u', mec:'u', lac:'u', xlc:'u' }, 
+                  lN : { xs:0, sm:0, me:0, la:0, xl:0, xsc:'u', smc:'u', mec:'u', lac:'u', xlc:'u' } },
+        getH: function(l, w) {
+          var cl = (l == undefined ? G.GOM.curNavLevel : l);
+          var cw = (w == undefined ? G.GOM.curWidth : w);
+          if( G.layout.engine == 'MOSAIC' ) {
+          return this.height[cl][cw] * this.mosaic[cl+'Factor']['h'][cw];
+          }
+          else {
+            return this.height[cl][cw];
+          }
+        },
+        getW: function(l, w) {
+          var cl = (l == undefined ? G.GOM.curNavLevel : l);
+          var cw = (w == undefined ? G.GOM.curWidth : w);
+          if( G.layout.engine == 'MOSAIC' ) {
+            return this.width[cl][cw] * this.mosaic[cl+'Factor']['w'][cw];
+          }
+          else {
+            return this.width[cl][cw];
+            // return G.tn.settings.width[G.GOM.curNavLevel][G.GOM.curWidth];
+          }
+        },
+        mosaic: { l1 : { xs: null, sm: null, me: null, la: null, xl: null },
+                  lN : { xs: null, sm: null, me: null, la: null, xl: null },
+                  l1Factor : { h :{ xs: 1, sm: 1, me: 1, la: 1, xl: 1 }, w :{ xs: 1, sm: 1, me: 1, la: 1, xl: 1 }},
+                  lNFactor : { h :{ xs: 1, sm: 1, me: 1, la: 1, xl: 1 }, w :{ xs: 1, sm: 1, me: 1, la: 1, xl: 1 }}
+                  },
+				getMosaic: function() {
+          return this.mosaic[G.GOM.curNavLevel][G.GOM.curWidth];
+        },
+        mosaicCalcFactor: function(l, w) {
+            // retrieve max size multiplicator
+            var maxW = 1;
+            var maxH = 1;
+            for( var n = 0; n < G.tn.settings.mosaic[l][w].length; n++ ) {
+              maxW = Math.max(maxW, this.mosaic[l][w][n]['w']);
+              maxH = Math.max(maxH, this.mosaic[l][w][n]['h']);
+            }
+            this.mosaic[l + 'Factor']['h'][w] = maxH;
+            this.mosaic[l + 'Factor']['w'][w] = maxW;
+        },
+        gutterHeight: { l1 : { xs:0, sm:0, me:0, la:0, xl:0 }, lN : { xs:0, sm:0, me:0, la:0, xl:0 } },
+        gutterWidth:  { l1 : { xs:0, sm:0, me:0, la:0, xl:0 }, lN : { xs:0, sm:0, me:0, la:0, xl:0 } },
+        GetResponsive: function( setting ) {
+          return this[setting][G.GOM.curNavLevel][G.GOM.curWidth];
+        }
+      },
+      // thumbnail hover effects
+      hoverEffects : {
+        std :   [],
+        level1: [],
+        get: function() {
+          if( G.GOM.curNavLevel == 'l1' && this.level1.length !== 0 ) {
+            return this.level1;
+          }
+          else {
+            return this.std;
+          }
+        }
+      },
+      // thumbnail init
+      buildInit : {
+        std :   [],
+        level1: [],
+        get: function() {
+          if( G.GOM.curNavLevel == 'l1' && this.level1.length !== 0 ) {
+            return this.level1;
+          }
+          else {
+            return this.std;
+          }
+        }
+      },
+      // thumbnail toolbars
+      toolbar: {
+        album :   { topLeft : '', topRight: '', bottomLeft: '', bottomRight: '' },
+        image :   { topLeft : '', topRight: '', bottomLeft: '', bottomRight: '' },
+        albumUp : { topLeft : '', topRight: '', bottomLeft: '', bottomRight: '' },
+        get: function( item ) {
+          return this[item.kind];
+        },
+      },
+      style: {
+        // inline CSS
+        l1 : { label: '', title: '', desc: '' },
+        lN : { label: '', title: '', desc: '' },
+        getTitle : function() {
+          return ('style="' + this[G.GOM.curNavLevel].title + '"');
+        },
+        getDesc : function() {
+          return ('style="' + this[G.GOM.curNavLevel].desc + '"');
+        },
+        getLabel: function() {
+          var s='style="'+ this[G.GOM.curNavLevel].label;
+          s+= (G.O.RTL ? '"direction:RTL;"' :'');
+          s+='"';
+          return s;
+        }
+      }
+    };
+    G.scrollTimeOut =             0;
+    G.i18nTranslations =          {'paginationPrevious':'Previous', 'paginationNext':'Next', 'breadcrumbHome':'List of Albums', 'thumbnailImageTitle':'', 'thumbnailAlbumTitle':'', 'thumbnailImageDescription':'', 'thumbnailAlbumDescription':'' };
+    G.emptyGif =                  'data:image/gif;base64,R0lGODlhEAAQAIAAAP///////yH5BAEKAAEALAAAAAAQABAAAAIOjI+py+0Po5y02ouzPgUAOw==';
+    G.CSStransformName =          FirstSupportedPropertyName(["transform", "msTransform", "MozTransform", "WebkitTransform", "OTransform"]);
+    // G.CSSfilterName =          FirstSupportedPropertyName(["filter", "WebkitFilter"]);
+    G.CSStransformStyle =         FirstSupportedPropertyName(["transformStyle", "msTransformStyle", "MozTransformStyle", "WebkitTransformStyle", "OTransformStyle"]);
+    G.CSSperspective =            FirstSupportedPropertyName(["perspective", "msPerspective", "MozPerspective", "WebkitPerspective", "OPerspective"]);
+    G.CSSbackfaceVisibilityName = FirstSupportedPropertyName(["backfaceVisibility", "msBackfaceVisibility", "MozBackfaceVisibility", "WebkitBackfaceVisibility", "OBackfaceVisibility"]);
+    G.CSStransitionName =         FirstSupportedPropertyName(["transition", "msTransition", "MozTransition", "WebkitTransition", "OTransition"]);
+    G.CSSanimationName =          FirstSupportedPropertyName(["animation", "msAnimation", "MozAnimation", "WebkitAnimation", "OAnimation"]);
+    G.GalleryResizeThrottled =    throttle(GalleryResize, 30, {leading: false});
+    
+
+    G.blackList =                 null;     // album black list
+    G.whiteList =                 null;     // album white list
+    G.tagBlackList =              null;     // tag black list
+
+    G.albumList =                 [];       // album list
+    G.locationHashLastUsed =      '';
+    G.custGlobals =               {};
+    G.touchAutoOpenDelayTimerID = 0;
+    G.i18nLang =                  '';
+    G.timeLastTouchStart =        0;
+    G.custGlobals =               {};
+    G.markupOrApiProcessed =      false;
+    
+    //------------------------
+    //--- Gallery Object Model
+    G.GOM = {
+      albumIdx :                  -1, // index (in G.I) of the currently displayed album
+      clipArea :                  { top: 0, height: 0 }, // area of the GOM to display on screen
+      displayArea :               { width: 0 , height: 0 }, // size of the GOM area (=used area, not available area)
+      displayAreaLast :           { width: 0 , height: 0 }, // previous size of the GOM area
+      displayedMoreSteps :        0,  // current number of displayed steps (moreButton mode)
+      items:                      [], // current items of the GOMS
+      $imgPreloader:              [],
+      thumbnails2Display:         [],
+      itemsDisplayed :            0, // number of currently displayed thumbnails
+      firstDisplay :              true,
+      firstDisplayTime :          0,      // in conjunction with galleryRenderDelay
+      navigationBar : {           // content of the navigation bar (for breadcrumb, filter tags and next/previous pagination)
+        displayed:                false,
+        $newContent:              ''
+      },
+      cache : {                   // cached data
+        viewport:                 null,
+        containerOffset:          null,
+        areaWidth:                100         // available area width
+      },
+      nbSelected :                0,        // number of selected items
+      pagination :                { currentPage: 0 }, // pagination data
+      panThreshold:               60,       // threshold value (in pixels) to block horizontal pan/swipe
+      panYOnly:                   false,    // threshold value reach -> definitively block horizontal pan until end of pan
+      lastFullRow :               -1,       // number of the last row without holes
+      lastDisplayedIdx:           -1,       // used to display the counter of not displayed items
+      displayInterval :           { from: 0, len: 0 },
+      userEvents:                 null,
+      hammertime:                 null,
+      curNavLevel:                'l1',   // current navigation level (l1 or LN)
+      curWidth:                   'me',
+      albumSearch:                '',     // current search string -> title (used to filter the thumbnails on screen)
+      albumSearchTags:            '',     // current search string -> tags
+      lastZIndex:                 0,      // used to put a thumbnail on top of all others (for exemple for scale hover effect)
+      lastRandomValue:            0,
+      slider : {                          // slider on last thumbnail
+        hostIdx:                  -1,       // idx of the thumbnail hosting the slider
+        hostItem:                 null,     // item hosting the slider
+        currentIdx:               0,        // idx of the current displayed item
+        nextIdx:                  0,        // idx of the next item to display in the slider
+        timerID:                  0,
+        tween:                    null      // tranistion tween instance
+      },
+      NGY2Item: function( idx ) {   // returns a NGY2Item or null if it does not exist
+        if( G.GOM.items[idx] == undefined || G.GOM.items[idx] == null ) { return null; }
+        var i = G.GOM.items[idx].thumbnailIdx;
+        return G.I[i];
+      },
+      // One GOM item (thumbnail)
+      // function GTn(index, width, height) {
+      GTn: function(index, width, height) {
+        this.thumbnailIdx = index;
+        this.width =                0;      // thumbnail width
+        this.height =               0;      // thumbnail height
+        this.top =                  0;      // position: top
+        this.left =                 0;      // position: left
+        this.row =                  0;      // position: row number
+        this.imageWidth =           width;  // image width
+        this.imageHeight =          height; // image height
+        this.resizedContentWidth =  0;
+        this.resizedContentHeight = 0;
+        this.displayed =            false;
+        this.neverDisplayed =       true;
+        this.inDisplayArea =        false;
+      },
+      // Position the top of the gallery to make it visible, if not displayed
+      ScrollToTop: function() {
+    
+        if( G.$E.scrollableParent === null && !topInViewportVert(G.$E.base, 20) ) {
+          // $('html, body').animate({scrollTop: G.$E.base.offset().top}, 200);
+          G.$E.base.get(0).scrollIntoView();
+        }
+        
+        if( G.$E.scrollableParent !== null ) {
+          // gallery in a scrollable container: check if we have to scroll up so that the top of the gallery is visible
+          // vertical position of the scrollbar
+          var scrollTop = G.$E.scrollableParent.scrollTop();
+          // top of the gallery relative to the top of the scrollable container
+          var dist = Math.abs(G.$E.scrollableParent.offset().top - G.$E.base.offset().top - scrollTop);
+          if( scrollTop > dist ) {
+            window.ng_draf( function() {
+              // we need a little delay before setting the new scrollbar (but why?....)
+               G.$E.scrollableParent.scrollTop(dist);
+            });
+          }
+        }
+      }
+    };
+    
+    
+    //------------------------
+    //--- Viewer Object Model
+    
+    G.VOM = {
+      viewerDisplayed:            false,  // is the viewer currently displayed
+      viewerIsFullscreen:         false,  // viewer in fullscreen mode
+      infoDisplayed:              false,  // is the info box displayed
+      toolbarsDisplayed:          true,   // the toolbars are displayed
+      toolsHide:                  null,
+      zoom : {
+        posX:                     0,      // position to center zoom in/out
+        posY:                     0,
+        userFactor:               1,      // user zoom factor (applied to the baseZoom factor)
+        isZooming:                false
+      },
+      padding:                    { H: 0, V: 0 }, // padding for the image
+      window:                     { lastWidth: 0, lastHeight: 0 },
+      $viewer:                    null,
+      $toolbar:                   null,   // viewerToolbar
+      $toolbarTL:                 null,   // viewer toolbar on top left
+      $toolbarTR:                 null,   // viewer toolbar on top right
+      
+      toolbarMode:                'std',  // current toolbar mode (standard, minimized)
+      playSlideshow :             false,  // slide show mode status
+      playSlideshowTimerID:       0,      // slideshow mode time
+      slideshowDelay:             3000,   // slideshow mode - delay before next image
+      albumID:                    -1,
+      viewerMediaIsChanged:       false,  // media display is currently modified
+      items:                      [],     // current list of images to be managed by the viewer
+
+      panMode:                    'off',  // if panning, which element -> media, gallery, or zoom - if not -> off
+
+      $baseCont:                  null,   // lightbox container
+      $content:                   null,   // pointer to the 3 media in the viewer
+      content: {
+        previous : {
+            vIdx: -1,
+            $media: null,
+            NGY2Item: function() {
+              return G.I[ G.VOM.items[G.VOM.content.previous.vIdx].ngy2ItemIdx ];
+            }
+        },
+        current : {
+            vIdx: -1,
+            $media: null,
+            NGY2Item: function() {
+              return G.I[ G.VOM.items[G.VOM.content.current.vIdx].ngy2ItemIdx ];
+            }
+        },
+        next : {
+            vIdx: -1,
+            $media: null,
+            NGY2Item: function() {
+              return G.I[ G.VOM.items[G.VOM.content.next.vIdx].ngy2ItemIdx ];
+            }
+        }
+      },
+      IdxNext: function() {
+        var n = 0;
+        // if( G.VOM.currItemIdx <= (G.VOM.items.length-1) ) {
+        if( G.VOM.content.current.vIdx < (G.VOM.items.length-1) ) {
+          n = G.VOM.content.current.vIdx + 1;
+        }
+        return n;
+      },
+      IdxPrevious: function() {
+        var n = G.VOM.content.current.vIdx - 1;
+        if( G.VOM.content.current.vIdx == 0 ) {
+          n = G.VOM.items.length - 1;
+        }
+        return n;
+      },
+			
+			gallery: {
+        $elt: null,             // Base container
+        $tmbCont: null,         // Thumbnail container
+				gwidth: 0,              // thumbnail container width (all thumbnails)
+        vwidth: 0,              // visible width of the gallery (just for the visible thumbnails)
+				oneTmbWidth: 0,
+				firstDisplay: true,
+				posX: 0,
+				SetThumbnailActive() {
+					if( G.O.viewerGallery == 'none' ) { return; }
+					this.$tmbCont.children().removeClass('activeVThumbnail');
+					this.$tmbCont.children().eq( G.VOM.content.current.vIdx ).addClass('activeVThumbnail');
+					this.firstDisplay = false;
+				},
+				Resize: function() {
+					if( G.O.viewerGallery == 'none' ) { return; }
+
+					if( !this.firstDisplay ) {
+						var viewerW = G.VOM.$viewer.width();
+						
+						// Center base element 
+						var maxTmb = Math.trunc(viewerW / this.oneTmbWidth);      // max thumbnail that can be displayed
+            this.vwidth = maxTmb * this.oneTmbWidth;
+						this.$elt.css({ width: this.vwidth, left: (viewerW - this.vwidth)/2 });
+            
+						// Set the position the thumbnails container (if there's no enough space for all thumbnails)
+						if( G.VOM.items.length >= maxTmb ) {
+							var tmbPos = this.oneTmbWidth * G.VOM.content.current.vIdx;    // left position of the selected thumbnail
+              
+              if( (tmbPos + this.posX) < this.vwidth ) {
+                  if( tmbPos + this.posX < 0 ) {
+                    this.posX = -tmbPos;
+                  }
               }
-              if( !G.GOM.panYOnly ) {
-                G.$E.conTn.css( G.CSStransformName , 'translate('+(ev.deltaX)+'px,0px)');
+              else {
+                if( tmbPos + this.posX >= this.vwidth ) {
+                  this.posX = this.vwidth - (tmbPos + this.oneTmbWidth)
+                }
+              }
+						}
+						
+            this.PanGallery(0);
+					}
+          else {
+            // first display of the gallery -> opacity transition
+            new NGTweenable().tween({
+              from:         { opacity: 0 },
+              to:           { opacity: 1 },
+              easing:       'easeInOutSine',
+              duration:     1000,
+              step:         function (state) {
+                // G.VOM.gallery.$elt.css( state );
+              },
+              finish:       function (state) {
+                // G.VOM.gallery.$elt.css({ opacity: 1});
+              }
+            });
+
+          }
+				},
+        PanGallery: function( panX ){
+          
+          // all thumbnails are visible -> center the base element
+          if( this.gwidth < G.VOM.$viewer.width() ) {       // this.oneTmbWidth
+            this.posX = (G.VOM.$viewer.width() - this.gwidth) / 2;   
+            panX = 0;   // block pan
+          }
+          
+          // if( this.posX > (this.vwidth - this.oneTmbWidth) ) {
+          if( this.posX > (this.vwidth - this.oneTmbWidth) ) {
+            // gallery is outside of the screen, right side
+            this.posX = this.vwidth - this.oneTmbWidth;
+          }
+          if( (this.posX+this.gwidth) < this.oneTmbWidth ) {
+            // gallery is outside of the screen, left side
+            this.posX = -this.gwidth + this.oneTmbWidth;
+          }
+          
+          this.$tmbCont.css( G.CSStransformName , 'translateX(' + (this.posX + panX) + 'px)');
+        },
+        PanGalleryEnd: function( velocity ) {      // velocity = pixels/millisecond
+
+          var d = velocity * 100;         // distance 
+          new NGTweenable().tween({
+            from:         { pan: G.VOM.gallery.posX },
+            to:           { pan: G.VOM.gallery.posX + d },
+            easing:       'easeOutQuad',
+            duration:     500,
+            step:         function (state) {
+              G.VOM.gallery.posX = state.pan;
+              G.VOM.gallery.PanGallery( 0 );
+            }
+          });
+        }
+				
+			},
+      userEvents:         null,   // user events management
+      hammertime:         null,   // hammer.js manager
+      swipePosX:          0,      // current horizontal swip position
+      panPosX:            0,      // position for manual pan
+      panPosY:            0,
+      panThreshold:       60,     // threshold value (in pixels) to block vertical pan
+      panXOnly:           false,  // threshold value reach -> definitively block vertical pan until end of pan
+      singletapTime:			0,
+			viewerTheme:        '',
+      timeImgChanged:     0,
+      ImageLoader: {
+        // fires a callback when image size is know (during download)
+        // inspired by ROB - http://stackoverflow.com/users/226507/rob
+        maxChecks:        1000,
+        list:             [],
+        intervalHandle :  null,
+
+        loadImage : function (callback, ngitem) {
+          if( ngitem.mediaKind != 'img' ) { return; }     // ignore - only for images
+          var img = new Image ();
+          img.src = ngitem.responsiveURL();
+          if (img.width && img.height) {
+            callback (img.width, img.height, ngitem, 0);
+            }
+          else {
+            var obj = {image: img, url: ngitem.responsiveURL(), ngitem: ngitem, callback: callback, checks: 1};
+            var i;
+            for (i=0; i < this.list.length; i++)    {
+              if (this.list[i] == null)
+                break;
               }
-            }
-          }
-        });
-        G.GOM.hammertime.on('panend', function(ev) {
-          if( !G.VOM.viewerDisplayed ) {
-            if( G.O.paginationSwipe && G.layout.support.rows && G.galleryDisplayMode.Get() == 'PAGINATION' ) {
-              if( !G.GOM.panYOnly ) {
-                if( ev.deltaX > 50 ) {
-                  paginationPreviousPage();
-                  return;
-                }
-                if(  ev.deltaX < -50 ) {
-                  paginationNextPage();
-                  return;
+            this.list[i] = obj;
+            if (!this.intervalHandle)
+              this.intervalHandle = setInterval(this.interval, 50);
+            }
+          },
+
+        // called by setInterval  
+        interval : function () {
+          var count = 0;
+          var list = G.VOM.ImageLoader.list, item;
+          for (var i=0; i<list.length; i++) {
+            item = list[i];
+            if (item != null) {
+              if (item.image.width && item.image.height) {
+                G.VOM.ImageLoader.list[i] = null;
+                item.callback (item.image.width, item.image.height, item.ngitem, item.checks);
+                }
+              else if (item.checks > G.VOM.ImageLoader.maxChecks) {
+                G.VOM.ImageLoader.list[i] = null;
+                item.callback (0, 0, item.ngitem, item.checks);
                 }
+              else {
+                count++;
+                item.checks++;
+                }
+              }
+            }
+          if (count == 0) {
+            G.VOM.ImageLoader.list = [];
+            clearInterval (G.VOM.ImageLoader.intervalHandle);
+            delete G.VOM.ImageLoader.intervalHandle;
+            }
+          }
+        }
+    }
+    // One VOM item (image)
+    function VImg( index ) {
+      this.$e = null;
+      this.ngy2ItemIdx = index;
+      this.mediaNumber = G.VOM.items.length + 1;     
+      this.posX = 0;    // to center the element
+      this.posY = 0;
+    }
+    
+    
+    //------------------------
+    //--- popup
+    G.popup = {
+      isDisplayed:      false,
+      $elt:             null,
+      close: function() {
+        if( this.$elt != null ) {
+          var tweenable = new NGTweenable();
+          tweenable.tween({
+            from:       { opacity:1  },
+            to:         { opacity:0 },
+            attachment: { t: this },
+            easing:     'easeInOutSine',
+            duration:   100,
+            step: function (state, att) {
+              if( att.t.$elt != null ) {
+                att.t.$elt.css('opacity',state.opacity);
+              }
+            },
+            finish: function (state, att) {
+              if( att.t.$elt != null ) {
+                att.t.$elt.remove();
+                att.t.$elt=null;
               }
-              G.GOM.panYOnly = false;
-              G.$E.conTn.css( G.CSStransformName , 'translate(0px,0px)');
-              // pX=0;
-            }
-          }
-        });
-        // tap on gallery
-        G.GOM.hammertime.on('tap', function(ev) {
-          if( !G.VOM.viewerDisplayed ) {
-            ev.srcEvent.stopPropagation();
-            ev.srcEvent.preventDefault();  // cancel  mouseenter event
-
-            if( ev.pointerType == 'mouse') {
-              if( GalleryClicked(ev.srcEvent) == 'exit' ) { return; }
-            }
-            else {
-              var r = GalleryEventRetrieveElementl(ev.srcEvent, false);
-              if( r.GOMidx == -1 ) { return; }
-              if( r.action != 'NONE' && r.action != 'OPEN' ) {
-                // toolbar touched --> execute action
-                GalleryClicked(ev.srcEvent);
-                return;
-              }
-              
-              if( G.GOM.slider.hostIdx == r.GOMidx ) {
-                // touch on thumbnail slider -> open immediately
-                ThumbnailHoverOutAll();
-                ThumbnailOpen(G.GOM.items[G.GOM.slider.currentIdx].thumbnailIdx, true);
-                return;
-              }
-
-              if( (G.GOM.curNavLevel == 'l1' && G.O.touchAnimationL1 == false) ||  (G.GOM.curNavLevel == 'lN' && G.O.touchAnimation == false) ) {
-                // open on single touch (no hover animation)
-                ThumbnailOpen(G.GOM.items[r.GOMidx].thumbnailIdx, true);
-                return;
-              }
-              
-              if( G.O.touchAutoOpenDelay > 0 ) {
-                // open on single touch after end of hover animation (=defined delay)
-                ThumbnailHoverOutAll();
-                ThumbnailHover( r.GOMidx );
-                window.clearInterval( G.touchAutoOpenDelayTimerID );
-                G.touchAutoOpenDelayTimerID = window.setInterval(function(){
-                  window.clearInterval( G.touchAutoOpenDelayTimerID );
-                  ThumbnailOpen( G.GOM.items[r.GOMidx].thumbnailIdx, true );
-                }, G.O.touchAutoOpenDelay );
-              }
-              else {
-                // two touch scenario
-                if( !G.I[G.GOM.items[r.GOMidx].thumbnailIdx].hovered ) {
-                  ThumbnailHoverOutAll();
-                  ThumbnailHover(r.GOMidx);
-                }
-                else {
-                  // second touch
-                  ThumbnailOpen(G.GOM.items[r.GOMidx].thumbnailIdx, true);
-                }
-              }
-            }
-          }
-        });
-      
-        // browser location hash management
-        if( G.O.locationHash ) {
-          // jQuery(window).bind( 'hashchange', function() {
-            // ProcessLocationHash();
-          // });
-          jQuery(window).on('hashchange.nanogallery2.' + G.baseEltID, function() {ProcessLocationHash();} );
-        }
-      }
-      
-      // Page resize / orientation change
-      jQuery(window).on('resize.nanogallery2.' + G.baseEltID + ' orientationChange.nanogallery2.' + G.baseEltID, debounce( ResizeWindowEvent, G.O.eventsDebounceDelay, false) );
-      
-      // Event page scrolled
-      jQuery(window).on('scroll.nanogallery2.' + G.baseEltID, debounce( OnScrollEvent, G.O.eventsDebounceDelay, false) );
-
-      if( !G.O.lightboxStandalone ) {
-        // Scroll event on first scrollable parent element
-        G.$E.scrollableParent = getScrollableParent( G.$E.base[0] );
-        var sp = getScrollableParent( G.$E.base[0] );
-        if( sp !== null ) {
-          G.$E.scrollableParent = jQuery( sp );
-          G.$E.scrollableParent.on('scroll.nanogallery2.' + G.baseEltID, debounce( OnScrollEvent, G.O.eventsDebounceDelay, false) );
-        }
-      }
-			
-      // lightbox: hide tools/gallery after defined delay
-      G.VOM.toolsHide = debounce( ViewerToolsHide, G.O.viewerHideToolsDelay, false );
-      
-      // Keyboard management
-      jQuery(document).keyup(function(e) {
-        if( G.popup.isDisplayed ) {
-          switch( e.keyCode) {
-            case 27:    // Esc key
-              G.popup.close();
-              break;
-          }
-        }
-        else {
-          if( G.VOM.viewerDisplayed ) {
-            ViewerToolsUnHide();
-            switch( e.keyCode) {
-              case 27:    // Escape key
-              case 40:    // DOWN
-              case 38:    // UP
-                LightboxClose();
-                break;
-              case 32:    // SPACE
-              case 13:    // ENTER
-                SlideshowToggle();
-                break;
-              case 39:    // RIGHT
-              case 33:    // PAGE UP
-                DisplayNextMedia();
-                break;
-              case 37:    // LEFT
-              case 34:    // PAGE DOWN
-                DisplayPreviousMedia();
-                break;
-              case 35:    // END
-              case 36:    // BEGIN
-            }
-          }
-        }
-      });
-      
-      // mouse wheel to zoom in/out the image displayed in the internal lightbox
-      jQuery(window).bind('mousewheel wheel', function(e){
-        if( G.VOM.viewerDisplayed ) {
-          var deltaY = 0;
-          e.preventDefault();
-
-          if( ViewerZoomStart() ) {
-            if (e.originalEvent.deltaY) { // FireFox 17+ (IE9+, Chrome 31+?)
-              deltaY = e.originalEvent.deltaY;
-            } else if (e.originalEvent.wheelDelta) {
-              deltaY = -e.originalEvent.wheelDelta;
-            }
-            ViewerZoomIn( deltaY <= 0 ? true : false );
-          }
-        }
-      });
-      
-      // mouse move -> unhide lightbox toolbars
-      jQuery(window).bind('mousemove', function(e){
-        if( G.VOM.viewerDisplayed ) {
-					if( G.VOM.toolbarsDisplayed == false ) {
-						G.VOM.singletapTime = new Date().getTime();		// to avoid conflict with SINGLETAP event
-						debounce( ViewerToolsUnHide, 100, false )();
-					}
-        }
-      });
-      
-      // fullscreen mode on/off --> internal lightbox
-      if( ngscreenfull.enabled ) {
-        // ngscreenfull.onchange(() => {
-        ngscreenfull.onchange( function() {
-          if( G.VOM.viewerDisplayed ) {
-            if( ngscreenfull.isFullscreen ) {
-              G.VOM.viewerIsFullscreen=true;
-              G.VOM.$viewer.find('.fullscreenButton').html(G.O.icons.viewerFullscreenOff);
-            }
-            else {
-              G.VOM.viewerIsFullscreen=false;
-              G.VOM.$viewer.find('.fullscreenButton').html(G.O.icons.viewerFullscreenOn);
-            }
-          }
-        });
-      }
-
-    }
-    
-		
-    //----- Manage browser location hash (deep linking and browser back/forward)
-    function ProcessLocationHash() {
-
-      // standard use case -> location hash processing
-      if( !G.O.locationHash ) { return false; }
-
-      var curGal = '#nanogallery/' + G.baseEltID + '/',
-      newLocationHash = location.hash;
-      if( G.O.debugMode ) {
-        console.log('------------------------ PROCESS LOCATION HASH');
-        console.log('newLocationHash1: ' +newLocationHash);
-        console.log('G.locationHashLastUsed: ' +G.locationHashLastUsed);
-      }
-      
-      if( newLocationHash == '' ) {
-        // if( G.GOM.lastDisplayedIdx != -1 ) {
-        if( G.locationHashLastUsed !== '' ) {
-          // back button and no hash --> display first album
-          if( G.O.debugMode ) { console.log('display root album'  ); }
-          G.locationHashLastUsed = '';
-          if( G.O.debugMode ) { console.log('new3 G.locationHashLastUsed: ' + G.locationHashLastUsed); }
-          DisplayAlbum('', '0');
-          return true;
-        }
-      }
-
-      if( newLocationHash == G.locationHashLastUsed ) { return; }
-      
-      if( newLocationHash.indexOf(curGal) == 0 ) {
-        // item IDs detected
-        var IDs=parseIDs( newLocationHash.substring(curGal.length) );
-        if( IDs.imageID != '0' ) {
-          if( G.O.debugMode ) { console.log('display image: ' + IDs.albumID +'-'+ IDs.imageID ); }
-          DisplayPhoto( IDs.imageID, IDs.albumID );
-          return true;
-        }
-        else {
-          if( G.O.debugMode ) { console.log('display album: ' + IDs.albumID  ); }
-          DisplayAlbum( '-1', IDs.albumID );
-          return true;
-        }
-      }    
-      
-      return false;
-    }
-
-    //---- Set a new browser location hash
-    function SetLocationHash(albumID, imageID ) {
-      if( !G.O.locationHash || G.O.lightboxStandalone ) { return false; }
-
-      if( G.O.debugMode ) {
-        console.log('------------------------ SET LOCATION HASH');
-      }
-      
-      if( imageID == '' && (albumID == '-1' || albumID == '0' || G.O.album == albumID ) ) {
-        // root album level --> do not set top.location.hash if not already set
-        if( location.hash != '' ) {
-          // try to clear the hash if set
-          if ("pushState" in history) {
-            history.pushState("", document.title, window.location.pathname + window.location.search);
-          }
-          else {
-            location.hash='';
-          }
-        }
-        G.locationHashLastUsed='';
-        if( G.O.debugMode ) { console.log('new2 G.locationHashLastUsed: '+G.locationHashLastUsed); }
-        return;
-      }
-      
-      var newLocationHash='#'+'nanogallery/'+G.baseEltID+'/'+ albumID;
-      if( imageID != '' ) {
-        newLocationHash+='/'+imageID;
-      }
-
-      var lH=location.hash;
-      if( G.O.debugMode ) {
-        console.log('newLocationHash2: '+newLocationHash);
-        console.log('location.hash: '+lH);
-      }
-
-      G.locationHashLastUsed=newLocationHash;
-      if( G.O.debugMode ) { console.log('new G.locationHashLastUsed: '+G.locationHashLastUsed); }
-      
-      if(  lH == '' || lH != newLocationHash ) {
-        // G.locationHashLastUsed='#'+newLocationHash;
-        try {
-          top.location.hash=newLocationHash;
-        }
-        catch(e) {
-          // location hash is not supported by current browser --> disable the option
-          G.O.locationHash=false;
-        }
-      }
-    }
-    
-    
-		// WINDOW RESIZE EVENT
-    function ResizeWindowEvent() {
-      CacheViewport();
-
-			var l = G.GOM.curNavLevel;
-			var w = G.GOM.curWidth;
-			
-      if( G.VOM.viewerDisplayed ) {
-				// lightbox
-        ResizeLightbox();
-        G.VOM.gallery.Resize();
-      }
-      else {
-				// gallery
-        if( G.galleryResizeEventEnabled ) {
-          var nw = RetrieveCurWidth();
-
-					if( G.GOM.albumIdx != -1 ) {
-
-						// check if the gallery needs to be rendered again because the width changed
-						
-            var s = G.tn.settings;
-						if( G.layout.engine == "MOSAIC") {
-							// Mosaic layout
-							if( JSON.stringify(s.mosaic[l][w]) !== JSON.stringify(s.mosaic[l][nw]) ) {
-								// mosaic definition changed
-								G.GOM.curWidth = nw;
-								G.GOM.pagination.currentPage = 0;
-								GalleryRender( G.GOM.albumIdx );
-							}
-						}
-						else {
-							// other layouts
-							if( s.height[l][w] != s.height[l][nw] || s.width[l][w] != s.width[l][nw] || s.gutterHeight[l][w] != s.gutterHeight[l][nw]  || s.gutterWidth[l][w] != s.gutterWidth[l][nw]  ) {
-								// thumbnail size / gutter size changed --> render the gallery with the new values
-								G.GOM.curWidth = nw;
-								//G.layout.SetEngine();
-								G.GOM.pagination.currentPage = 0;
-								GalleryRender( G.GOM.albumIdx );
-							}
-						}
-						return;
-          }
-          else {
-            GalleryResize();
-          }
-        }
-      }
-    }
-    
-     
-
-		// SCROLL EVENT -> on WINDOW or SCROLLABLE PARENT CONTAINER
-    function OnScrollEvent() {
-			if( !G.VOM.viewerDisplayed ) {
-        GalleryResizeOnScrollEvent();
-      }
-    }
-		
-    // the gallery may currently be refreshed, so ensure that at the end of the refresh, the gallery is refreshed again because the page may have been scrolled in the meantime
-    function GalleryResizeOnScrollEvent() {
-      if( G.galleryResizeEventEnabled == false) {
-        window.setTimeout(GalleryResizeOnScrollEvent, 10);  // check again in 10ms
-      } else {
-        GalleryResize();
-      }
-    }
-
-
-    
-    // I18N : define text translations
-    function i18n() {
-
-      // browser language
-      G.i18nLang = (navigator.language || navigator.userLanguage).toUpperCase();
-      if( G.i18nLang === 'UNDEFINED') { G.i18nLang=''; }
-
-      var llang=-('_'+G.i18nLang).length;
-      
-      if( toType(G.O.i18n) == 'object' ){
-      
-        for( var key in G.O.i18n ) {
-          //var value = G.O.i18n[key];
-          var s=key.substr(llang);
-          if( s == ('_'+G.i18nLang) ) {
-            G.i18nTranslations[key.substr(0,key.length-s.length)]=G.O.i18n[key];
-          } 
-          else {
-            G.i18nTranslations[key]=G.O.i18n[key];
-          }
-        }
-      }
-    }
-
-    function GetI18nItem( item, property ) {
-      var s='';
-      if( G.i18nLang != '' ) {
-        if( item[property+'_'+G.i18nLang] !== undefined && item[property+'_'+G.i18nLang].length>0 ) {
-          s=item[property+'_'+G.i18nLang];
-          return s;
-        }
-      }
-      s=item[property];
-      return s;
-    }
-
-    
-    function RetrieveCurWidth() {
-      var vpW = G.GOM.cache.viewport.w;
-      
-      if( G.O.breakpointSizeSM > 0 && vpW < G.O.breakpointSizeSM) { return 'xs'; }
-      if( G.O.breakpointSizeME > 0 && vpW < G.O.breakpointSizeME) { return 'sm'; }
-      if( G.O.breakpointSizeLA > 0 && vpW < G.O.breakpointSizeLA) { return 'me'; }
-      if( G.O.breakpointSizeXL > 0 && vpW < G.O.breakpointSizeXL) { return 'la'; }
-      
-      return 'xl';
-    }
-
-    
-    /** @function browserNotification */
-    function browserNotification() {
-      var m = 'Your browser version is not supported anymore. The image gallery cannot be displayed. <br><br>Please update to a more recent one. Download:<br>';
-      m    += '&nbsp;&nbsp;&nbsp; <a href="http://www.google.com/chrome/?hl=en-US)">Chrome</a><br>';
-      m    += '&nbsp;&nbsp;&nbsp; <a href="http://www.mozilla.com/firefox/)">Firefox</a><br>';
-      m    += '&nbsp;&nbsp;&nbsp; <a href="http://www.microsoft.com/windows/internet-explorer/default.aspx">Internet Explorer</a><br>';
-      m    += '&nbsp;&nbsp;&nbsp; <a href="http://www.apple.com/safari/download/">Safari</a>';
-      NanoAlert(G,  m, false);
-    }
-
-    // Original author : John Hrvatin, Lead Program Manager, Internet Explorer - http://blogs.msdn.com/b/ie/archive/2011/10/28/a-best-practice-for-programming-with-vendor-prefixes.aspx
-    function FirstSupportedPropertyName(prefixedPropertyNames) {
-      var tempDiv = document.createElement("div");
-      for (var i = 0; i < prefixedPropertyNames.length; ++i) {
-        if (typeof tempDiv.style[prefixedPropertyNames[i]] != 'undefined')
-          return prefixedPropertyNames[i];
-      }
-      return null;
-    }
-
-    
-    
-    
-  }
-  
-
-
-//##########################################################################################################################
-//## imagesLoaded ##########################################################################################################
-//##########################################################################################################################
-
-// external module EMBEDED in nanogallery
-// NGY BUILD:
-// replace "imagesLoaded" with "ngimagesLoaded"
-// replace "ImagesLoaded" with "ngImagesLoaded"
-// replace "EvEmitter" with "ngEvEmitter"
-// replace "var $ = window.jQuery" with "var $ = jQuery;"
-// 2x (global.ngEvEmitter and window.ngimagesLoaded = f...)ignore package manager and set browser global
-
-
-
-/*!
- * imagesLoaded PACKAGED v4.1.1
- * JavaScript is all like "You images are done yet or what?"
- * MIT License
- */
-
-/**
- * EvEmitter v1.0.3
- * Lil' event emitter
- * MIT License
- */
-
- 
-/* jshint unused: true, undef: true, strict: true */
-
-( function( global, factory ) {
-  // universal module definition
-  /* jshint strict: false */ /* globals define, module, window */
-//  if ( typeof define == 'function' && define.amd ) {
-    // AMD - RequireJS
-//    define( 'ev-emitter/ev-emitter',factory );
-//  } else if ( typeof module == 'object' && module.exports ) {
-    // CommonJS - Browserify, Webpack
-//    module.exports = factory();
-//  } else {
-    // Browser globals
-    global.ngEvEmitter = factory();
-//  }
-
-}( typeof window != 'undefined' ? window : this, function() {
-
-
-
-function ngEvEmitter() {}
-
-var proto = ngEvEmitter.prototype;
-
-proto.on = function( eventName, listener ) {
-  if ( !eventName || !listener ) {
-    return;
-  }
-  // set events hash
-  var events = this._events = this._events || {};
-  // set listeners array
-  var listeners = events[ eventName ] = events[ eventName ] || [];
-  // only add once
-  if ( listeners.indexOf( listener ) == -1 ) {
-    listeners.push( listener );
-  }
-
-  return this;
-};
-
-proto.once = function( eventName, listener ) {
-  if ( !eventName || !listener ) {
-    return;
-  }
-  // add event
-  this.on( eventName, listener );
-  // set once flag
-  // set onceEvents hash
-  var onceEvents = this._onceEvents = this._onceEvents || {};
-  // set onceListeners object
-  var onceListeners = onceEvents[ eventName ] = onceEvents[ eventName ] || {};
-  // set flag
-  onceListeners[ listener ] = true;
-
-  return this;
-};
-
-proto.off = function( eventName, listener ) {
-  var listeners = this._events && this._events[ eventName ];
-  if ( !listeners || !listeners.length ) {
-    return;
-  }
-  var index = listeners.indexOf( listener );
-  if ( index != -1 ) {
-    listeners.splice( index, 1 );
-  }
-
-  return this;
-};
-
-proto.emitEvent = function( eventName, args ) {
-  var listeners = this._events && this._events[ eventName ];
-  if ( !listeners || !listeners.length ) {
-    return;
-  }
-  var i = 0;
-  var listener = listeners[i];
-  args = args || [];
-  // once stuff
-  var onceListeners = this._onceEvents && this._onceEvents[ eventName ];
-
-  while ( listener ) {
-    var isOnce = onceListeners && onceListeners[ listener ];
-    if ( isOnce ) {
-      // remove listener
-      // remove before trigger to prevent recursion
-      this.off( eventName, listener );
-      // unset once flag
-      delete onceListeners[ listener ];
-    }
-    // trigger listener
-    listener.apply( this, args );
-    // get next listener
-    i += isOnce ? 0 : 1;
-    listener = listeners[i];
-  }
-
-  return this;
-};
-
-return ngEvEmitter;
-
-}));
-
-/*!
- * ngimagesLoaded v4.1.1
- * JavaScript is all like "You images are done yet or what?"
- * MIT License
- */
-
-( function( window, factory ) { 'use strict';
-  // universal module definition
-
-  /*global define: false, module: false, require: false */
-
-//  if ( typeof define == 'function' && define.amd ) {
-    // AMD
-//    define( [
-//      'ev-emitter/ev-emitter'
-//    ], function( ngEvEmitter ) {
-//      return factory( window, ngEvEmitter );
-//    });
-//  } else if ( typeof module == 'object' && module.exports ) {
-    // CommonJS
-//    module.exports = factory(
-//      window,
-//      require('ev-emitter')
-//    );
-//  } else {
-    // browser global
-    window.ngimagesLoaded = factory(
-      window,
-      window.ngEvEmitter
-    );
-  //}
-
-})( window,
-
-// --------------------------  factory -------------------------- //
-
-function factory( window, ngEvEmitter ) {
-
-
-
-// var $ = window.jQuery;
-var $ = jQuery;
-var console = window.console;
-
-// -------------------------- helpers -------------------------- //
-
-// extend objects
-function extend( a, b ) {
-  for ( var prop in b ) {
-    a[ prop ] = b[ prop ];
-  }
-  return a;
-}
-
-// turn element or nodeList into an array
-function makeArray( obj ) {
-  var ary = [];
-  if ( Array.isArray( obj ) ) {
-    // use object if already an array
-    ary = obj;
-  } else if ( typeof obj.length == 'number' ) {
-    // convert nodeList to array
-    for ( var i=0; i < obj.length; i++ ) {
-      ary.push( obj[i] );
-    }
-  } else {
-    // array of single index
-    ary.push( obj );
-  }
-  return ary;
-}
-
-// -------------------------- ngimagesLoaded -------------------------- //
-
-/**
- * @param {Array, Element, NodeList, String} elem
- * @param {Object or Function} options - if function, use as callback
- * @param {Function} onAlways - callback function
- */
-function ngImagesLoaded( elem, options, onAlways ) {
-  // coerce ngImagesLoaded() without new, to be new ngImagesLoaded()
-  if ( !( this instanceof ngImagesLoaded ) ) {
-    return new ngImagesLoaded( elem, options, onAlways );
-  }
-  // use elem as selector string
-  if ( typeof elem == 'string' ) {
-    elem = document.querySelectorAll( elem );
-  }
-
-  this.elements = makeArray( elem );
-  this.options = extend( {}, this.options );
-
-  if ( typeof options == 'function' ) {
-    onAlways = options;
-  } else {
-    extend( this.options, options );
-  }
-
-  if ( onAlways ) {
-    this.on( 'always', onAlways );
-  }
-
-  this.getImages();
-
-  if ( $ ) {
-    // add jQuery Deferred object
-    this.jqDeferred = new $.Deferred();
-  }
-
-  // HACK check async to allow time to bind listeners
-  setTimeout( function() {
-    this.check();
-  }.bind( this ));
-}
-
-ngImagesLoaded.prototype = Object.create( ngEvEmitter.prototype );
-
-ngImagesLoaded.prototype.options = {};
-
-ngImagesLoaded.prototype.getImages = function() {
-  this.images = [];
-
-  // filter & find items if we have an item selector
-  this.elements.forEach( this.addElementImages, this );
-};
-
-/**
- * @param {Node} element
- */
-ngImagesLoaded.prototype.addElementImages = function( elem ) {
-  // filter siblings
-  if ( elem.nodeName == 'IMG' ) {
-    this.addImage( elem );
-  }
-  // get background image on element
-  if ( this.options.background === true ) {
-    this.addElementBackgroundImages( elem );
-  }
-
-  // find children
-  // no non-element nodes, #143
-  var nodeType = elem.nodeType;
-  if ( !nodeType || !elementNodeTypes[ nodeType ] ) {
-    return;
-  }
-  var childImgs = elem.querySelectorAll('img');
-  // concat childElems to filterFound array
-  for ( var i=0; i < childImgs.length; i++ ) {
-    var img = childImgs[i];
-    this.addImage( img );
-  }
-
-  // get child background images
-  if ( typeof this.options.background == 'string' ) {
-    var children = elem.querySelectorAll( this.options.background );
-    for ( i=0; i < children.length; i++ ) {
-      var child = children[i];
-      this.addElementBackgroundImages( child );
-    }
-  }
-};
-
-var elementNodeTypes = {
-  1: true,
-  9: true,
-  11: true
-};
-
-ngImagesLoaded.prototype.addElementBackgroundImages = function( elem ) {
-  var style = getComputedStyle( elem );
-  if ( !style ) {
-    // Firefox returns null if in a hidden iframe https://bugzil.la/548397
-    return;
-  }
-  // get url inside url("...")
-  var reURL = /url\((['"])?(.*?)\1\)/gi;
-  var matches = reURL.exec( style.backgroundImage );
-  while ( matches !== null ) {
-    var url = matches && matches[2];
-    if ( url ) {
-      this.addBackground( url, elem );
-    }
-    matches = reURL.exec( style.backgroundImage );
-  }
-};
-
-/**
- * @param {Image} img
- */
-ngImagesLoaded.prototype.addImage = function( img ) {
-  var loadingImage = new LoadingImage( img );
-  this.images.push( loadingImage );
-};
-
-ngImagesLoaded.prototype.addBackground = function( url, elem ) {
-  var background = new Background( url, elem );
-  this.images.push( background );
-};
-
-ngImagesLoaded.prototype.check = function() {
-  var _this = this;
-  this.progressedCount = 0;
-  this.hasAnyBroken = false;
-  // complete if no images
-  if ( !this.images.length ) {
-    this.complete();
-    return;
-  }
-
-  function onProgress( image, elem, message ) {
-    // HACK - Chrome triggers event before object properties have changed. #83
-    setTimeout( function() {
-      _this.progress( image, elem, message );
-    });
-  }
-
-  this.images.forEach( function( loadingImage ) {
-    loadingImage.once( 'progress', onProgress );
-    loadingImage.check();
-  });
-};
-
-ngImagesLoaded.prototype.progress = function( image, elem, message ) {
-  this.progressedCount++;
-  this.hasAnyBroken = this.hasAnyBroken || !image.isLoaded;
-  // progress event
-  this.emitEvent( 'progress', [ this, image, elem ] );
-  if ( this.jqDeferred && this.jqDeferred.notify ) {
-    this.jqDeferred.notify( this, image );
-  }
-  // check if completed
-  if ( this.progressedCount == this.images.length ) {
-    this.complete();
-  }
-
-  if ( this.options.debug && console ) {
-    console.log( 'progress: ' + message, image, elem );
-  }
-};
-
-ngImagesLoaded.prototype.complete = function() {
-  var eventName = this.hasAnyBroken ? 'fail' : 'done';
-  this.isComplete = true;
-  this.emitEvent( eventName, [ this ] );
-  this.emitEvent( 'always', [ this ] );
-  if ( this.jqDeferred ) {
-    var jqMethod = this.hasAnyBroken ? 'reject' : 'resolve';
-    this.jqDeferred[ jqMethod ]( this );
-  }
-};
-
-// --------------------------  -------------------------- //
-
-function LoadingImage( img ) {
-  this.img = img;
-}
-
-LoadingImage.prototype = Object.create( ngEvEmitter.prototype );
-
-LoadingImage.prototype.check = function() {
-  // If complete is true and browser supports natural sizes,
-  // try to check for image status manually.
-  var isComplete = this.getIsImageComplete();
-  if ( isComplete ) {
-    // report based on naturalWidth
-    this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' );
-    return;
-  }
-
-  // If none of the checks above matched, simulate loading on detached element.
-  this.proxyImage = new Image();
-  this.proxyImage.addEventListener( 'load', this );
-  this.proxyImage.addEventListener( 'error', this );
-  // bind to image as well for Firefox. #191
-  this.img.addEventListener( 'load', this );
-  this.img.addEventListener( 'error', this );
-  this.proxyImage.src = this.img.src;
-};
-
-LoadingImage.prototype.getIsImageComplete = function() {
-  return this.img.complete && this.img.naturalWidth !== undefined;
-};
-
-LoadingImage.prototype.confirm = function( isLoaded, message ) {
-  this.isLoaded = isLoaded;
-  this.emitEvent( 'progress', [ this, this.img, message ] );
-};
-
-// ----- events ----- //
-
-// trigger specified handler for event type
-LoadingImage.prototype.handleEvent = function( event ) {
-  var method = 'on' + event.type;
-  if ( this[ method ] ) {
-    this[ method ]( event );
-  }
-};
-
-LoadingImage.prototype.onload = function() {
-  this.confirm( true, 'onload' );
-  this.unbindEvents();
-};
-
-LoadingImage.prototype.onerror = function() {
-  this.confirm( false, 'onerror' );
-  this.unbindEvents();
-};
-
-LoadingImage.prototype.unbindEvents = function() {
-  this.proxyImage.removeEventListener( 'load', this );
-  this.proxyImage.removeEventListener( 'error', this );
-  this.img.removeEventListener( 'load', this );
-  this.img.removeEventListener( 'error', this );
-};
-
-// -------------------------- Background -------------------------- //
-
-function Background( url, element ) {
-  this.url = url;
-  this.element = element;
-  this.img = new Image();
-}
-
-// inherit LoadingImage prototype
-Background.prototype = Object.create( LoadingImage.prototype );
-
-Background.prototype.check = function() {
-  this.img.addEventListener( 'load', this );
-  this.img.addEventListener( 'error', this );
-  this.img.src = this.url;
-  // check if image is already complete
-  var isComplete = this.getIsImageComplete();
-  if ( isComplete ) {
-    this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' );
-    this.unbindEvents();
-  }
-};
-
-Background.prototype.unbindEvents = function() {
-  this.img.removeEventListener( 'load', this );
-  this.img.removeEventListener( 'error', this );
-};
-
-Background.prototype.confirm = function( isLoaded, message ) {
-  this.isLoaded = isLoaded;
-  this.emitEvent( 'progress', [ this, this.element, message ] );
-};
-
-// -------------------------- jQuery -------------------------- //
-
-ngImagesLoaded.makeJQueryPlugin = function( jQuery ) {
-  jQuery = jQuery || window.jQuery;
-  if ( !jQuery ) {
-    return;
-  }
-  // set local variable
-  $ = jQuery;
-  // $().ngimagesLoaded()
-  $.fn.ngimagesLoaded = function( options, callback ) {
-    var instance = new ngImagesLoaded( this, options, callback );
-    return instance.jqDeferred.promise( $(this) );
-  };
-};
-// try making plugin
-ngImagesLoaded.makeJQueryPlugin();
-
-// --------------------------  -------------------------- //
-
-return ngImagesLoaded;
-
-});
-
-
-
-//##########################################################################################################################
-//## screenfull.js #########################################################################################################
-//##########################################################################################################################
-
-// screenfull.js
-// v4.0.1
-// by sindresorhus - https://github.com/sindresorhus
-// from: https://github.com/sindresorhus/screenfull.js
-
-// external module embeded in nanogallery
-// NGY BUILD:
-// replace "screenfull" with "ngscreenfull"
-// 
-
-(function () {
-	'use strict';
-
-	var document = typeof window !== 'undefined' && typeof window.document !== 'undefined' ? window.document : {};
-	var isCommonjs = typeof module !== 'undefined' && module.exports;
-	var keyboardAllowed = typeof Element !== 'undefined' && 'ALLOW_KEYBOARD_INPUT' in Element;
-
-	var fn = (function () {
-		var val;
-
-		var fnMap = [
-			[
-				'requestFullscreen',
-				'exitFullscreen',
-				'fullscreenElement',
-				'fullscreenEnabled',
-				'fullscreenchange',
-				'fullscreenerror'
-			],
-			// New WebKit
-			[
-				'webkitRequestFullscreen',
-				'webkitExitFullscreen',
-				'webkitFullscreenElement',
-				'webkitFullscreenEnabled',
-				'webkitfullscreenchange',
-				'webkitfullscreenerror'
-
-			],
-			// Old WebKit (Safari 5.1)
-			[
-				'webkitRequestFullScreen',
-				'webkitCancelFullScreen',
-				'webkitCurrentFullScreenElement',
-				'webkitCancelFullScreen',
-				'webkitfullscreenchange',
-				'webkitfullscreenerror'
-
-			],
-			[
-				'mozRequestFullScreen',
-				'mozCancelFullScreen',
-				'mozFullScreenElement',
-				'mozFullScreenEnabled',
-				'mozfullscreenchange',
-				'mozfullscreenerror'
-			],
-			[
-				'msRequestFullscreen',
-				'msExitFullscreen',
-				'msFullscreenElement',
-				'msFullscreenEnabled',
-				'MSFullscreenChange',
-				'MSFullscreenError'
-			]
-		];
-
-		var i = 0;
-		var l = fnMap.length;
-		var ret = {};
-
-		for (; i < l; i++) {
-			val = fnMap[i];
-			if (val && val[1] in document) {
-				for (i = 0; i < val.length; i++) {
-					ret[fnMap[0][i]] = val[i];
-				}
-				return ret;
-			}
-		}
-
-		return false;
-	})();
-
-	var eventNameMap = {
-		change: fn.fullscreenchange,
-		error: fn.fullscreenerror
-	};
-
-	var ngscreenfull = {
-		request: function (elem) {
-			return new Promise(function (resolve) {
-				var request = fn.requestFullscreen;
-
-				var onFullScreenEntered = function () {
-					this.off('change', onFullScreenEntered);
-					resolve();
-				}.bind(this);
-
-				elem = elem || document.documentElement;
-
-				// Work around Safari 5.1 bug: reports support for
-				// keyboard in fullscreen even though it doesn't.
-				// Browser sniffing, since the alternative with
-				// setTimeout is even worse.
-				if (/ Version\/5\.1(?:\.\d+)? Safari\//.test(navigator.userAgent)) {
-					elem[request]();
-				} else {
-					elem[request](keyboardAllowed ? Element.ALLOW_KEYBOARD_INPUT : {});
-				}
-
-				this.on('change', onFullScreenEntered);
-			}.bind(this));
-		},
-		exit: function () {
-			return new Promise(function (resolve) {
-				if (!this.isFullscreen) {
-					resolve();
-					return;
-				}
-
-				var onFullScreenExit = function () {
-					this.off('change', onFullScreenExit);
-					resolve();
-				}.bind(this);
-
-				document[fn.exitFullscreen]();
-
-				this.on('change', onFullScreenExit);
-			}.bind(this));
-		},
-		toggle: function (elem) {
-			return this.isFullscreen ? this.exit() : this.request(elem);
-		},
-		onchange: function (callback) {
-			this.on('change', callback);
-		},
-		onerror: function (callback) {
-			this.on('error', callback);
-		},
-		on: function (event, callback) {
-			var eventName = eventNameMap[event];
-			if (eventName) {
-				document.addEventListener(eventName, callback, false);
-			}
-		},
-		off: function (event, callback) {
-			var eventName = eventNameMap[event];
-			if (eventName) {
-				document.removeEventListener(eventName, callback, false);
-			}
-		},
-		raw: fn
-	};
-
-	if (!fn) {
-		if (isCommonjs) {
-			module.exports = false;
-		} else {
-			window.ngscreenfull = false;
-		}
-
-		return;
-	}
-
-	Object.defineProperties(ngscreenfull, {
-		isFullscreen: {
-			get: function () {
-				return Boolean(document[fn.fullscreenElement]);
-			}
-		},
-		element: {
-			enumerable: true,
-			get: function () {
-				return document[fn.fullscreenElement];
-			}
-		},
-		enabled: {
-			enumerable: true,
-			get: function () {
-				// Coerce to boolean in case of old WebKit
-				return Boolean(document[fn.fullscreenEnabled]);
-			}
-		}
-	});
-
-	if (isCommonjs) {
-		module.exports = ngscreenfull;
-	} else {
-		window.ngscreenfull = ngscreenfull;
-	}
-})();
-
-
-  
-//##########################################################################################################################
-//## Shifty ################################################################################################################
-//##########################################################################################################################
-  
- /*!
- * Shifty
- * By Jeremy Kahn - jeremyckahn@gmail.com
- */
-
-// external module EMBEDED in nanogallery
-// NGY BUILD:
-// 
-// replace "Tweenable" with "NGTweenable"
-// replace "define.amd" with "define.amdDISABLED"
-/* shifty - v1.5.3 - 2016-11-29 - http://jeremyckahn.github.io/shifty */
-;(function () {
-  var root = this || Function('return this')();
-
-/**
- * Shifty Core
- * By Jeremy Kahn - jeremyckahn@gmail.com
- */
-
-var NGTweenable = (function () {
-
-  'use strict';
-
-  // Aliases that get defined later in this function
-  var formula;
-
-  // CONSTANTS
-  var DEFAULT_SCHEDULE_FUNCTION;
-  var DEFAULT_EASING = 'linear';
-  var DEFAULT_DURATION = 500;
-  var UPDATE_TIME = 1000 / 60;
-
-  var _now = Date.now
-       ? Date.now
-       : function () {return +new Date();};
-
-  var now = typeof SHIFTY_DEBUG_NOW !== 'undefined' ? SHIFTY_DEBUG_NOW : _now;
-
-  if (typeof window !== 'undefined') {
-    // requestAnimationFrame() shim by Paul Irish (modified for Shifty)
-    // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
-    DEFAULT_SCHEDULE_FUNCTION = window.requestAnimationFrame
-       || window.webkitRequestAnimationFrame
-       || window.oRequestAnimationFrame
-       || window.msRequestAnimationFrame
-       || (window.mozCancelRequestAnimationFrame
-       && window.mozRequestAnimationFrame)
-       || setTimeout;
-  } else {
-    DEFAULT_SCHEDULE_FUNCTION = setTimeout;
-  }
-
-  function noop () {
-    // NOOP!
-  }
-
-  /**
-   * Handy shortcut for doing a for-in loop. This is not a "normal" each
-   * function, it is optimized for Shifty.  The iterator function only receives
-   * the property name, not the value.
-   * @param {Object} obj
-   * @param {Function(string)} fn
-   * @private
-   */
-  function each (obj, fn) {
-    var key;
-    for (key in obj) {
-      if (Object.hasOwnProperty.call(obj, key)) {
-        fn(key);
-      }
-    }
-  }
-
-  /**
-   * Perform a shallow copy of Object properties.
-   * @param {Object} targetObject The object to copy into
-   * @param {Object} srcObject The object to copy from
-   * @return {Object} A reference to the augmented `targetObj` Object
-   * @private
-   */
-  function shallowCopy (targetObj, srcObj) {
-    each(srcObj, function (prop) {
-      targetObj[prop] = srcObj[prop];
-    });
-
-    return targetObj;
-  }
-
-  /**
-   * Copies each property from src onto target, but only if the property to
-   * copy to target is undefined.
-   * @param {Object} target Missing properties in this Object are filled in
-   * @param {Object} src
-   * @private
-   */
-  function defaults (target, src) {
-    each(src, function (prop) {
-      if (typeof target[prop] === 'undefined') {
-        target[prop] = src[prop];
-      }
-    });
-  }
-
-  /**
-   * Calculates the interpolated tween values of an Object for a given
-   * timestamp.
-   * @param {Number} forPosition The position to compute the state for.
-   * @param {Object} currentState Current state properties.
-   * @param {Object} originalState: The original state properties the Object is
-   * tweening from.
-   * @param {Object} targetState: The destination state properties the Object
-   * is tweening to.
-   * @param {number} duration: The length of the tween in milliseconds.
-   * @param {number} timestamp: The UNIX epoch time at which the tween began.
-   * @param {Object} easing: This Object's keys must correspond to the keys in
-   * targetState.
-   * @private
-   */
-  function tweenProps (forPosition, currentState, originalState, targetState,
-    duration, timestamp, easing) {
-    var normalizedPosition =
-        forPosition < timestamp ? 0 : (forPosition - timestamp) / duration;
-
-
-    var prop;
-    var easingObjectProp;
-    var easingFn;
-    for (prop in currentState) {
-      if (currentState.hasOwnProperty(prop)) {
-        easingObjectProp = easing[prop];
-        easingFn = typeof easingObjectProp === 'function'
-          ? easingObjectProp
-          : formula[easingObjectProp];
-
-        currentState[prop] = tweenProp(
-          originalState[prop],
-          targetState[prop],
-          easingFn,
-          normalizedPosition
-        );
-      }
-    }
-
-    return currentState;
-  }
-
-  /**
-   * Tweens a single property.
-   * @param {number} start The value that the tween started from.
-   * @param {number} end The value that the tween should end at.
-   * @param {Function} easingFunc The easing curve to apply to the tween.
-   * @param {number} position The normalized position (between 0.0 and 1.0) to
-   * calculate the midpoint of 'start' and 'end' against.
-   * @return {number} The tweened value.
-   * @private
-   */
-  function tweenProp (start, end, easingFunc, position) {
-    return start + (end - start) * easingFunc(position);
-  }
-
-  /**
-   * Applies a filter to NGTweenable instance.
-   * @param {NGTweenable} tweenable The `NGTweenable` instance to call the filter
-   * upon.
-   * @param {String} filterName The name of the filter to apply.
-   * @private
-   */
-  function applyFilter (tweenable, filterName) {
-    var filters = NGTweenable.prototype.filter;
-    var args = tweenable._filterArgs;
-
-    each(filters, function (name) {
-      if (typeof filters[name][filterName] !== 'undefined') {
-        filters[name][filterName].apply(tweenable, args);
-      }
-    });
-  }
-
-  var timeoutHandler_endTime;
-  var timeoutHandler_currentTime;
-  var timeoutHandler_isEnded;
-  var timeoutHandler_offset;
-  /**
-   * Handles the update logic for one step of a tween.
-   * @param {NGTweenable} tweenable
-   * @param {number} timestamp
-   * @param {number} delay
-   * @param {number} duration
-   * @param {Object} currentState
-   * @param {Object} originalState
-   * @param {Object} targetState
-   * @param {Object} easing
-   * @param {Function(Object, *, number)} step
-   * @param {Function(Function,number)}} schedule
-   * @param {number=} opt_currentTimeOverride Needed for accurate timestamp in
-   * NGTweenable#seek.
-   * @private
-   */
-  function timeoutHandler (tweenable, timestamp, delay, duration, currentState,
-    originalState, targetState, easing, step, schedule,
-    opt_currentTimeOverride) {
-
-    timeoutHandler_endTime = timestamp + delay + duration;
-
-    timeoutHandler_currentTime =
-    Math.min(opt_currentTimeOverride || now(), timeoutHandler_endTime);
-
-    timeoutHandler_isEnded =
-      timeoutHandler_currentTime >= timeoutHandler_endTime;
-
-    timeoutHandler_offset = duration - (
-      timeoutHandler_endTime - timeoutHandler_currentTime);
-
-    if (tweenable.isPlaying()) {
-      if (timeoutHandler_isEnded) {
-        step(targetState, tweenable._attachment, timeoutHandler_offset);
-        tweenable.stop(true);
-      } else {
-        tweenable._scheduleId =
-          schedule(tweenable._timeoutHandler, UPDATE_TIME);
-
-        applyFilter(tweenable, 'beforeTween');
-
-        // If the animation has not yet reached the start point (e.g., there was
-        // delay that has not yet completed), just interpolate the starting
-        // position of the tween.
-        if (timeoutHandler_currentTime < (timestamp + delay)) {
-          tweenProps(1, currentState, originalState, targetState, 1, 1, easing);
-        } else {
-          tweenProps(timeoutHandler_currentTime, currentState, originalState,
-            targetState, duration, timestamp + delay, easing);
-        }
-
-        applyFilter(tweenable, 'afterTween');
-
-        step(currentState, tweenable._attachment, timeoutHandler_offset);
-      }
-    }
-  }
-
-
-  /**
-   * Creates a usable easing Object from a string, a function or another easing
-   * Object.  If `easing` is an Object, then this function clones it and fills
-   * in the missing properties with `"linear"`.
-   * @param {Object.<string|Function>} fromTweenParams
-   * @param {Object|string|Function} easing
-   * @return {Object.<string|Function>}
-   * @private
-   */
-  function composeEasingObject (fromTweenParams, easing) {
-    var composedEasing = {};
-    var typeofEasing = typeof easing;
-
-    if (typeofEasing === 'string' || typeofEasing === 'function') {
-      each(fromTweenParams, function (prop) {
-        composedEasing[prop] = easing;
-      });
-    } else {
-      each(fromTweenParams, function (prop) {
-        if (!composedEasing[prop]) {
-          composedEasing[prop] = easing[prop] || DEFAULT_EASING;
-        }
-      });
-    }
-
-    return composedEasing;
-  }
-
-  /**
-   * NGTweenable constructor.
-   * @class NGTweenable
-   * @param {Object=} opt_initialState The values that the initial tween should
-   * start at if a `from` object is not provided to `{{#crossLink
-   * "NGTweenable/tween:method"}}{{/crossLink}}` or `{{#crossLink
-   * "NGTweenable/setConfig:method"}}{{/crossLink}}`.
-   * @param {Object=} opt_config Configuration object to be passed to
-   * `{{#crossLink "NGTweenable/setConfig:method"}}{{/crossLink}}`.
-   * @module NGTweenable
-   * @constructor
-   */
-  function NGTweenable (opt_initialState, opt_config) {
-    this._currentState = opt_initialState || {};
-    this._configured = false;
-    this._scheduleFunction = DEFAULT_SCHEDULE_FUNCTION;
-
-    // To prevent unnecessary calls to setConfig do not set default
-    // configuration here.  Only set default configuration immediately before
-    // tweening if none has been set.
-    if (typeof opt_config !== 'undefined') {
-      this.setConfig(opt_config);
-    }
-  }
-
-  /**
-   * Configure and start a tween.
-   * @method tween
-   * @param {Object=} opt_config Configuration object to be passed to
-   * `{{#crossLink "NGTweenable/setConfig:method"}}{{/crossLink}}`.
-   * @chainable
-   */
-  NGTweenable.prototype.tween = function (opt_config) {
-    if (this._isTweening) {
-      return this;
-    }
-
-    // Only set default config if no configuration has been set previously and
-    // none is provided now.
-    if (opt_config !== undefined || !this._configured) {
-      this.setConfig(opt_config);
-    }
-
-    this._timestamp = now();
-    this._start(this.get(), this._attachment);
-    return this.resume();
-  };
-
-  /**
-   * Configure a tween that will start at some point in the future.
-   *
-   * @method setConfig
-   * @param {Object} config The following values are valid:
-   * - __from__ (_Object=_): Starting position.  If omitted, `{{#crossLink
-   *   "NGTweenable/get:method"}}get(){{/crossLink}}` is used.
-   * - __to__ (_Object=_): Ending position.
-   * - __duration__ (_number=_): How many milliseconds to animate for.
-   * - __delay__ (_delay=_): How many milliseconds to wait before starting the
-   *   tween.
-   * - __start__ (_Function(Object, *)_): Function to execute when the tween
-   *   begins.  Receives the state of the tween as the first parameter and
-   *   `attachment` as the second parameter.
-   * - __step__ (_Function(Object, *, number)_): Function to execute on every
-   *   tick.  Receives `{{#crossLink
-   *   "NGTweenable/get:method"}}get(){{/crossLink}}` as the first parameter,
-   *   `attachment` as the second parameter, and the time elapsed since the
-   *   start of the tween as the third. This function is not called on the
-   *   final step of the animation, but `finish` is.
-   * - __finish__ (_Function(Object, *)_): Function to execute upon tween
-   *   completion.  Receives the state of the tween as the first parameter and
-   *   `attachment` as the second parameter.
-   * - __easing__ (_Object.<string|Function>|string|Function=_): Easing curve
-   *   name(s) or function(s) to use for the tween.
-   * - __attachment__ (_*_): Cached value that is passed to the
-   *   `step`/`start`/`finish` methods.
-   * @chainable
-   */
-  NGTweenable.prototype.setConfig = function (config) {
-    config = config || {};
-    this._configured = true;
-
-    // Attach something to this NGTweenable instance (e.g.: a DOM element, an
-    // object, a string, etc.);
-    this._attachment = config.attachment;
-
-    // Init the internal state
-    this._pausedAtTime = null;
-    this._scheduleId = null;
-    this._delay = config.delay || 0;
-    this._start = config.start || noop;
-    this._step = config.step || noop;
-    this._finish = config.finish || noop;
-    this._duration = config.duration || DEFAULT_DURATION;
-    this._currentState = shallowCopy({}, config.from || this.get());
-    this._originalState = this.get();
-    this._targetState = shallowCopy({}, config.to || this.get());
-
-    var self = this;
-    this._timeoutHandler = function () {
-      timeoutHandler(self,
-        self._timestamp,
-        self._delay,
-        self._duration,
-        self._currentState,
-        self._originalState,
-        self._targetState,
-        self._easing,
-        self._step,
-        self._scheduleFunction
-      );
-    };
-
-    // Aliases used below
-    var currentState = this._currentState;
-    var targetState = this._targetState;
-
-    // Ensure that there is always something to tween to.
-    defaults(targetState, currentState);
-
-    this._easing = composeEasingObject(
-      currentState, config.easing || DEFAULT_EASING);
-
-    this._filterArgs =
-      [currentState, this._originalState, targetState, this._easing];
-
-    applyFilter(this, 'tweenCreated');
-    return this;
-  };
-
-  /**
-   * @method get
-   * @return {Object} The current state.
-   */
-  NGTweenable.prototype.get = function () {
-    return shallowCopy({}, this._currentState);
-  };
-
-  /**
-   * @method set
-   * @param {Object} state The current state.
-   */
-  NGTweenable.prototype.set = function (state) {
-    this._currentState = state;
-  };
-
-  /**
-   * Pause a tween.  Paused tweens can be resumed from the point at which they
-   * were paused.  This is different from `{{#crossLink
-   * "NGTweenable/stop:method"}}{{/crossLink}}`, as that method
-   * causes a tween to start over when it is resumed.
-   * @method pause
-   * @chainable
-   */
-  NGTweenable.prototype.pause = function () {
-    this._pausedAtTime = now();
-    this._isPaused = true;
-    return this;
-  };
-
-  /**
-   * Resume a paused tween.
-   * @method resume
-   * @chainable
-   */
-  NGTweenable.prototype.resume = function () {
-    if (this._isPaused) {
-      this._timestamp += now() - this._pausedAtTime;
-    }
-
-    this._isPaused = false;
-    this._isTweening = true;
-
-    this._timeoutHandler();
-
-    return this;
-  };
-
-  /**
-   * Move the state of the animation to a specific point in the tween's
-   * timeline.  If the animation is not running, this will cause the `step`
-   * handlers to be called.
-   * @method seek
-   * @param {millisecond} millisecond The millisecond of the animation to seek
-   * to.  This must not be less than `0`.
-   * @chainable
-   */
-  NGTweenable.prototype.seek = function (millisecond) {
-    millisecond = Math.max(millisecond, 0);
-    var currentTime = now();
-
-    if ((this._timestamp + millisecond) === 0) {
-      return this;
-    }
-
-    this._timestamp = currentTime - millisecond;
-
-    if (!this.isPlaying()) {
-      this._isTweening = true;
-      this._isPaused = false;
-
-      // If the animation is not running, call timeoutHandler to make sure that
-      // any step handlers are run.
-      timeoutHandler(this,
-        this._timestamp,
-        this._delay,
-        this._duration,
-        this._currentState,
-        this._originalState,
-        this._targetState,
-        this._easing,
-        this._step,
-        this._scheduleFunction,
-        currentTime
-      );
-
-      this.pause();
-    }
-
-    return this;
-  };
-
-  /**
-   * Stops and cancels a tween.
-   * @param {boolean=} gotoEnd If `false` or omitted, the tween just stops at
-   * its current state, and the `finish` handler is not invoked.  If `true`,
-   * the tweened object's values are instantly set to the target values, and
-   * `finish` is invoked.
-   * @method stop
-   * @chainable
-   */
-  NGTweenable.prototype.stop = function (gotoEnd) {
-    this._isTweening = false;
-    this._isPaused = false;
-    this._timeoutHandler = noop;
-
-    (root.cancelAnimationFrame            ||
-    root.webkitCancelAnimationFrame     ||
-    root.oCancelAnimationFrame          ||
-    root.msCancelAnimationFrame         ||
-    root.mozCancelRequestAnimationFrame ||
-    root.clearTimeout)(this._scheduleId);
-
-    if (gotoEnd) {
-      applyFilter(this, 'beforeTween');
-      tweenProps(
-        1,
-        this._currentState,
-        this._originalState,
-        this._targetState,
-        1,
-        0,
-        this._easing
-      );
-      applyFilter(this, 'afterTween');
-      applyFilter(this, 'afterTweenEnd');
-      this._finish.call(this, this._currentState, this._attachment);
-    }
-
-    return this;
-  };
-
-  /**
-   * @method isPlaying
-   * @return {boolean} Whether or not a tween is running.
-   */
-  NGTweenable.prototype.isPlaying = function () {
-    return this._isTweening && !this._isPaused;
-  };
-
-  /**
-   * Set a custom schedule function.
-   *
-   * If a custom function is not set,
-   * [`requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/window.requestAnimationFrame)
-   * is used if available, otherwise
-   * [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/Window.setTimeout)
-   * is used.
-   * @method setScheduleFunction
-   * @param {Function(Function,number)} scheduleFunction The function to be
-   * used to schedule the next frame to be rendered.
-   */
-  NGTweenable.prototype.setScheduleFunction = function (scheduleFunction) {
-    this._scheduleFunction = scheduleFunction;
-  };
-
-  /**
-   * `delete` all "own" properties.  Call this when the `NGTweenable` instance
-   * is no longer needed to free memory.
-   * @method dispose
-   */
-  NGTweenable.prototype.dispose = function () {
-    var prop;
-    for (prop in this) {
-      if (this.hasOwnProperty(prop)) {
-        delete this[prop];
-      }
-    }
-  };
-
-  /**
-   * Filters are used for transforming the properties of a tween at various
-   * points in a NGTweenable's life cycle.  See the README for more info on this.
-   * @private
-   */
-  NGTweenable.prototype.filter = {};
-
-  /**
-   * This object contains all of the tweens available to Shifty.  It is
-   * extensible - simply attach properties to the `NGTweenable.prototype.formula`
-   * Object following the same format as `linear`.
-   *
-   * `pos` should be a normalized `number` (between 0 and 1).
-   * @property formula
-   * @type {Object(function)}
-   */
-  NGTweenable.prototype.formula = {
-    linear: function (pos) {
-      return pos;
-    }
-  };
-
-  formula = NGTweenable.prototype.formula;
-
-  shallowCopy(NGTweenable, {
-    'now': now
-    ,'each': each
-    ,'tweenProps': tweenProps
-    ,'tweenProp': tweenProp
-    ,'applyFilter': applyFilter
-    ,'shallowCopy': shallowCopy
-    ,'defaults': defaults
-    ,'composeEasingObject': composeEasingObject
-  });
-
-  // `root` is provided in the intro/outro files.
-
-  // A hook used for unit testing.
-  if (typeof SHIFTY_DEBUG_NOW === 'function') {
-    root.timeoutHandler = timeoutHandler;
-  }
-
-  // Bootstrap NGTweenable appropriately for the environment.
-  if (typeof exports === 'object') {
-    // CommonJS
-    module.exports = NGTweenable;
-  } else if (typeof define === 'function' && define.amdDISABLED) {
-    // AMD
-    define(function () {return NGTweenable;});
-  } else if (typeof root.NGTweenable === 'undefined') {
-    // Browser: Make `NGTweenable` globally accessible.
-    root.NGTweenable = NGTweenable;
-  }
-
-  return NGTweenable;
-
-} ());
-
-/*!
- * All equations are adapted from Thomas Fuchs'
- * [Scripty2](https://github.com/madrobby/scripty2/blob/master/src/effects/transitions/penner.js).
- *
- * Based on Easing Equations (c) 2003 [Robert
- * Penner](http://www.robertpenner.com/), all rights reserved. This work is
- * [subject to terms](http://www.robertpenner.com/easing_terms_of_use.html).
- */
-
-/*!
- *  TERMS OF USE - EASING EQUATIONS
- *  Open source under the BSD License.
- *  Easing Equations (c) 2003 Robert Penner, all rights reserved.
- */
-
-;(function () {
-
-  NGTweenable.shallowCopy(NGTweenable.prototype.formula, {
-    easeInQuad: function (pos) {
-      return Math.pow(pos, 2);
-    },
-
-    easeOutQuad: function (pos) {
-      return -(Math.pow((pos - 1), 2) - 1);
-    },
-
-    easeInOutQuad: function (pos) {
-      if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,2);}
-      return -0.5 * ((pos -= 2) * pos - 2);
-    },
-
-    easeInCubic: function (pos) {
-      return Math.pow(pos, 3);
-    },
-
-    easeOutCubic: function (pos) {
-      return (Math.pow((pos - 1), 3) + 1);
-    },
-
-    easeInOutCubic: function (pos) {
-      if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,3);}
-      return 0.5 * (Math.pow((pos - 2),3) + 2);
-    },
-
-    easeInQuart: function (pos) {
-      return Math.pow(pos, 4);
-    },
-
-    easeOutQuart: function (pos) {
-      return -(Math.pow((pos - 1), 4) - 1);
-    },
-
-    easeInOutQuart: function (pos) {
-      if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,4);}
-      return -0.5 * ((pos -= 2) * Math.pow(pos,3) - 2);
-    },
-
-    easeInQuint: function (pos) {
-      return Math.pow(pos, 5);
-    },
-
-    easeOutQuint: function (pos) {
-      return (Math.pow((pos - 1), 5) + 1);
-    },
-
-    easeInOutQuint: function (pos) {
-      if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,5);}
-      return 0.5 * (Math.pow((pos - 2),5) + 2);
-    },
-
-    easeInSine: function (pos) {
-      return -Math.cos(pos * (Math.PI / 2)) + 1;
-    },
-
-    easeOutSine: function (pos) {
-      return Math.sin(pos * (Math.PI / 2));
-    },
-
-    easeInOutSine: function (pos) {
-      return (-0.5 * (Math.cos(Math.PI * pos) - 1));
-    },
-
-    easeInExpo: function (pos) {
-      return (pos === 0) ? 0 : Math.pow(2, 10 * (pos - 1));
-    },
-
-    easeOutExpo: function (pos) {
-      return (pos === 1) ? 1 : -Math.pow(2, -10 * pos) + 1;
-    },
-
-    easeInOutExpo: function (pos) {
-      if (pos === 0) {return 0;}
-      if (pos === 1) {return 1;}
-      if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(2,10 * (pos - 1));}
-      return 0.5 * (-Math.pow(2, -10 * --pos) + 2);
-    },
-
-    easeInCirc: function (pos) {
-      return -(Math.sqrt(1 - (pos * pos)) - 1);
-    },
-
-    easeOutCirc: function (pos) {
-      return Math.sqrt(1 - Math.pow((pos - 1), 2));
-    },
-
-    easeInOutCirc: function (pos) {
-      if ((pos /= 0.5) < 1) {return -0.5 * (Math.sqrt(1 - pos * pos) - 1);}
-      return 0.5 * (Math.sqrt(1 - (pos -= 2) * pos) + 1);
-    },
-
-    easeOutBounce: function (pos) {
-      if ((pos) < (1 / 2.75)) {
-        return (7.5625 * pos * pos);
-      } else if (pos < (2 / 2.75)) {
-        return (7.5625 * (pos -= (1.5 / 2.75)) * pos + 0.75);
-      } else if (pos < (2.5 / 2.75)) {
-        return (7.5625 * (pos -= (2.25 / 2.75)) * pos + 0.9375);
-      } else {
-        return (7.5625 * (pos -= (2.625 / 2.75)) * pos + 0.984375);
-      }
-    },
-
-    easeInBack: function (pos) {
-      var s = 1.70158;
-      return (pos) * pos * ((s + 1) * pos - s);
-    },
-
-    easeOutBack: function (pos) {
-      var s = 1.70158;
-      return (pos = pos - 1) * pos * ((s + 1) * pos + s) + 1;
-    },
-
-    easeInOutBack: function (pos) {
-      var s = 1.70158;
-      if ((pos /= 0.5) < 1) {
-        return 0.5 * (pos * pos * (((s *= (1.525)) + 1) * pos - s));
-      }
-      return 0.5 * ((pos -= 2) * pos * (((s *= (1.525)) + 1) * pos + s) + 2);
-    },
-
-    elastic: function (pos) {
-      // jshint maxlen:90
-      return -1 * Math.pow(4,-8 * pos) * Math.sin((pos * 6 - 1) * (2 * Math.PI) / 2) + 1;
-    },
-
-    swingFromTo: function (pos) {
-      var s = 1.70158;
-      return ((pos /= 0.5) < 1) ?
-          0.5 * (pos * pos * (((s *= (1.525)) + 1) * pos - s)) :
-          0.5 * ((pos -= 2) * pos * (((s *= (1.525)) + 1) * pos + s) + 2);
-    },
-
-    swingFrom: function (pos) {
-      var s = 1.70158;
-      return pos * pos * ((s + 1) * pos - s);
-    },
-
-    swingTo: function (pos) {
-      var s = 1.70158;
-      return (pos -= 1) * pos * ((s + 1) * pos + s) + 1;
-    },
-
-    bounce: function (pos) {
-      if (pos < (1 / 2.75)) {
-        return (7.5625 * pos * pos);
-      } else if (pos < (2 / 2.75)) {
-        return (7.5625 * (pos -= (1.5 / 2.75)) * pos + 0.75);
-      } else if (pos < (2.5 / 2.75)) {
-        return (7.5625 * (pos -= (2.25 / 2.75)) * pos + 0.9375);
-      } else {
-        return (7.5625 * (pos -= (2.625 / 2.75)) * pos + 0.984375);
-      }
-    },
-
-    bouncePast: function (pos) {
-      if (pos < (1 / 2.75)) {
-        return (7.5625 * pos * pos);
-      } else if (pos < (2 / 2.75)) {
-        return 2 - (7.5625 * (pos -= (1.5 / 2.75)) * pos + 0.75);
-      } else if (pos < (2.5 / 2.75)) {
-        return 2 - (7.5625 * (pos -= (2.25 / 2.75)) * pos + 0.9375);
-      } else {
-        return 2 - (7.5625 * (pos -= (2.625 / 2.75)) * pos + 0.984375);
-      }
-    },
-
-    easeFromTo: function (pos) {
-      if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,4);}
-      return -0.5 * ((pos -= 2) * Math.pow(pos,3) - 2);
-    },
-
-    easeFrom: function (pos) {
-      return Math.pow(pos,4);
-    },
-
-    easeTo: function (pos) {
-      return Math.pow(pos,0.25);
-    }
-  });
-
-}());
-
-// jshint maxlen:100
-/**
- * The Bezier magic in this file is adapted/copied almost wholesale from
- * [Scripty2](https://github.com/madrobby/scripty2/blob/master/src/effects/transitions/cubic-bezier.js),
- * which was adapted from Apple code (which probably came from
- * [here](http://opensource.apple.com/source/WebCore/WebCore-955.66/platform/graphics/UnitBezier.h)).
- * Special thanks to Apple and Thomas Fuchs for much of this code.
- */
-
-/**
- *  Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
- *
- *  Redistribution and use in source and binary forms, with or without
- *  modification, are permitted provided that the following conditions are met:
- *
- *  1. Redistributions of source code must retain the above copyright notice,
- *  this list of conditions and the following disclaimer.
- *
- *  2. Redistributions in binary form must reproduce the above copyright notice,
- *  this list of conditions and the following disclaimer in the documentation
- *  and/or other materials provided with the distribution.
- *
- *  3. Neither the name of the copyright holder(s) nor the names of any
- *  contributors may be used to endorse or promote products derived from
- *  this software without specific prior written permission.
- *
- *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- *  POSSIBILITY OF SUCH DAMAGE.
- */
-;(function () {
-  // port of webkit cubic bezier handling by http://www.netzgesta.de/dev/
-  function cubicBezierAtTime(t,p1x,p1y,p2x,p2y,duration) {
-    var ax = 0,bx = 0,cx = 0,ay = 0,by = 0,cy = 0;
-    function sampleCurveX(t) {
-      return ((ax * t + bx) * t + cx) * t;
-    }
-    function sampleCurveY(t) {
-      return ((ay * t + by) * t + cy) * t;
-    }
-    function sampleCurveDerivativeX(t) {
-      return (3.0 * ax * t + 2.0 * bx) * t + cx;
-    }
-    function solveEpsilon(duration) {
-      return 1.0 / (200.0 * duration);
-    }
-    function solve(x,epsilon) {
-      return sampleCurveY(solveCurveX(x, epsilon));
-    }
-    function fabs(n) {
-      if (n >= 0) {
-        return n;
-      } else {
-        return 0 - n;
-      }
-    }
-    function solveCurveX(x, epsilon) {
-      var t0,t1,t2,x2,d2,i;
-      for (t2 = x, i = 0; i < 8; i++) {
-        x2 = sampleCurveX(t2) - x;
-        if (fabs(x2) < epsilon) {
-          return t2;
-        }
-        d2 = sampleCurveDerivativeX(t2);
-        if (fabs(d2) < 1e-6) {
-          break;
-        }
-        t2 = t2 - x2 / d2;
-      }
-      t0 = 0.0;
-      t1 = 1.0;
-      t2 = x;
-      if (t2 < t0) {
-        return t0;
-      }
-      if (t2 > t1) {
-        return t1;
-      }
-      while (t0 < t1) {
-        x2 = sampleCurveX(t2);
-        if (fabs(x2 - x) < epsilon) {
-          return t2;
-        }
-        if (x > x2) {
-          t0 = t2;
-        }else {
-          t1 = t2;
-        }
-        t2 = (t1 - t0) * 0.5 + t0;
-      }
-      return t2; // Failure.
-    }
-    cx = 3.0 * p1x;
-    bx = 3.0 * (p2x - p1x) - cx;
-    ax = 1.0 - cx - bx;
-    cy = 3.0 * p1y;
-    by = 3.0 * (p2y - p1y) - cy;
-    ay = 1.0 - cy - by;
-    return solve(t, solveEpsilon(duration));
-  }
-  /**
-   *  getCubicBezierTransition(x1, y1, x2, y2) -> Function
-   *
-   *  Generates a transition easing function that is compatible
-   *  with WebKit's CSS transitions `-webkit-transition-timing-function`
-   *  CSS property.
-   *
-   *  The W3C has more information about CSS3 transition timing functions:
-   *  http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
-   *
-   *  @param {number} x1
-   *  @param {number} y1
-   *  @param {number} x2
-   *  @param {number} y2
-   *  @return {function}
-   *  @private
-   */
-  function getCubicBezierTransition (x1, y1, x2, y2) {
-    return function (pos) {
-      return cubicBezierAtTime(pos,x1,y1,x2,y2,1);
-    };
-  }
-  // End ported code
-
-  /**
-   * Create a Bezier easing function and attach it to `{{#crossLink
-   * "NGTweenable/formula:property"}}NGTweenable#formula{{/crossLink}}`.  This
-   * function gives you total control over the easing curve.  Matthew Lein's
-   * [Ceaser](http://matthewlein.com/ceaser/) is a useful tool for visualizing
-   * the curves you can make with this function.
-   * @method setBezierFunction
-   * @param {string} name The name of the easing curve.  Overwrites the old
-   * easing function on `{{#crossLink
-   * "NGTweenable/formula:property"}}NGTweenable#formula{{/crossLink}}` if it
-   * exists.
-   * @param {number} x1
-   * @param {number} y1
-   * @param {number} x2
-   * @param {number} y2
-   * @return {function} The easing function that was attached to
-   * NGTweenable.prototype.formula.
-   */
-  NGTweenable.setBezierFunction = function (name, x1, y1, x2, y2) {
-    var cubicBezierTransition = getCubicBezierTransition(x1, y1, x2, y2);
-    cubicBezierTransition.displayName = name;
-    cubicBezierTransition.x1 = x1;
-    cubicBezierTransition.y1 = y1;
-    cubicBezierTransition.x2 = x2;
-    cubicBezierTransition.y2 = y2;
-
-    return NGTweenable.prototype.formula[name] = cubicBezierTransition;
-  };
-
-
-  /**
-   * `delete` an easing function from `{{#crossLink
-   * "NGTweenable/formula:property"}}NGTweenable#formula{{/crossLink}}`.  Be
-   * careful with this method, as it `delete`s whatever easing formula matches
-   * `name` (which means you can delete standard Shifty easing functions).
-   * @method unsetBezierFunction
-   * @param {string} name The name of the easing function to delete.
-   * @return {function}
-   */
-  NGTweenable.unsetBezierFunction = function (name) {
-    delete NGTweenable.prototype.formula[name];
-  };
-
-})();
-
-;(function () {
-
-  function getInterpolatedValues (
-    from, current, targetState, position, easing, delay) {
-    return NGTweenable.tweenProps(
-      position, current, from, targetState, 1, delay, easing);
-  }
-
-  // Fake a NGTweenable and patch some internals.  This approach allows us to
-  // skip uneccessary processing and object recreation, cutting down on garbage
-  // collection pauses.
-  var mockNGTweenable = new NGTweenable();
-  mockNGTweenable._filterArgs = [];
-
-  /**
-   * Compute the midpoint of two Objects.  This method effectively calculates a
-   * specific frame of animation that `{{#crossLink
-   * "NGTweenable/tween:method"}}{{/crossLink}}` does many times over the course
-   * of a full tween.
-   *
-   *     var interpolatedValues = NGTweenable.interpolate({
-   *       width: '100px',
-   *       opacity: 0,
-   *       color: '#fff'
-   *     }, {
-   *       width: '200px',
-   *       opacity: 1,
-   *       color: '#000'
-   *     }, 0.5);
-   *
-   *     console.log(interpolatedValues);
-   *     // {opacity: 0.5, width: "150px", color: "rgb(127,127,127)"}
-   *
-   * @static
-   * @method interpolate
-   * @param {Object} from The starting values to tween from.
-   * @param {Object} targetState The ending values to tween to.
-   * @param {number} position The normalized position value (between `0.0` and
-   * `1.0`) to interpolate the values between `from` and `to` for.  `from`
-   * represents `0` and `to` represents `1`.
-   * @param {Object.<string|Function>|string|Function} easing The easing
-   * curve(s) to calculate the midpoint against.  You can reference any easing
-   * function attached to `NGTweenable.prototype.formula`, or provide the easing
-   * function(s) directly.  If omitted, this defaults to "linear".
-   * @param {number=} opt_delay Optional delay to pad the beginning of the
-   * interpolated tween with.  This increases the range of `position` from (`0`
-   * through `1`) to (`0` through `1 + opt_delay`).  So, a delay of `0.5` would
-   * increase all valid values of `position` to numbers between `0` and `1.5`.
-   * @return {Object}
-   */
-  NGTweenable.interpolate = function (
-    from, targetState, position, easing, opt_delay) {
-
-    var current = NGTweenable.shallowCopy({}, from);
-    var delay = opt_delay || 0;
-    var easingObject = NGTweenable.composeEasingObject(
-      from, easing || 'linear');
-
-    mockNGTweenable.set({});
-
-    // Alias and reuse the _filterArgs array instead of recreating it.
-    var filterArgs = mockNGTweenable._filterArgs;
-    filterArgs.length = 0;
-    filterArgs[0] = current;
-    filterArgs[1] = from;
-    filterArgs[2] = targetState;
-    filterArgs[3] = easingObject;
-
-    // Any defined value transformation must be applied
-    NGTweenable.applyFilter(mockNGTweenable, 'tweenCreated');
-    NGTweenable.applyFilter(mockNGTweenable, 'beforeTween');
-
-    var interpolatedValues = getInterpolatedValues(
-      from, current, targetState, position, easingObject, delay);
-
-    // Transform values back into their original format
-    NGTweenable.applyFilter(mockNGTweenable, 'afterTween');
-
-    return interpolatedValues;
-  };
-
-}());
-
-/**
- * This module adds string interpolation support to Shifty.
- *
- * The Token extension allows Shifty to tween numbers inside of strings.  Among
- * other things, this allows you to animate CSS properties.  For example, you
- * can do this:
- *
- *     var tweenable = new NGTweenable();
- *     tweenable.tween({
- *       from: { transform: 'translateX(45px)' },
- *       to: { transform: 'translateX(90xp)' }
- *     });
- *
- * `translateX(45)` will be tweened to `translateX(90)`.  To demonstrate:
- *
- *     var tweenable = new NGTweenable();
- *     tweenable.tween({
- *       from: { transform: 'translateX(45px)' },
- *       to: { transform: 'translateX(90px)' },
- *       step: function (state) {
- *         console.log(state.transform);
- *       }
- *     });
- *
- * The above snippet will log something like this in the console:
- *
- *     translateX(60.3px)
- *     ...
- *     translateX(76.05px)
- *     ...
- *     translateX(90px)
- *
- * Another use for this is animating colors:
- *
- *     var tweenable = new NGTweenable();
- *     tweenable.tween({
- *       from: { color: 'rgb(0,255,0)' },
- *       to: { color: 'rgb(255,0,255)' },
- *       step: function (state) {
- *         console.log(state.color);
- *       }
- *     });
- *
- * The above snippet will log something like this:
- *
- *     rgb(84,170,84)
- *     ...
- *     rgb(170,84,170)
- *     ...
- *     rgb(255,0,255)
- *
- * This extension also supports hexadecimal colors, in both long (`#ff00ff`)
- * and short (`#f0f`) forms.  Be aware that hexadecimal input values will be
- * converted into the equivalent RGB output values.  This is done to optimize
- * for performance.
- *
- *     var tweenable = new NGTweenable();
- *     tweenable.tween({
- *       from: { color: '#0f0' },
- *       to: { color: '#f0f' },
- *       step: function (state) {
- *         console.log(state.color);
- *       }
- *     });
- *
- * This snippet will generate the same output as the one before it because
- * equivalent values were supplied (just in hexadecimal form rather than RGB):
- *
- *     rgb(84,170,84)
- *     ...
- *     rgb(170,84,170)
- *     ...
- *     rgb(255,0,255)
- *
- * ## Easing support
- *
- * Easing works somewhat differently in the Token extension.  This is because
- * some CSS properties have multiple values in them, and you might need to
- * tween each value along its own easing curve.  A basic example:
- *
- *     var tweenable = new NGTweenable();
- *     tweenable.tween({
- *       from: { transform: 'translateX(0px) translateY(0px)' },
- *       to: { transform:   'translateX(100px) translateY(100px)' },
- *       easing: { transform: 'easeInQuad' },
- *       step: function (state) {
- *         console.log(state.transform);
- *       }
- *     });
- *
- * The above snippet will create values like this:
- *
- *     translateX(11.56px) translateY(11.56px)
- *     ...
- *     translateX(46.24px) translateY(46.24px)
- *     ...
- *     translateX(100px) translateY(100px)
- *
- * In this case, the values for `translateX` and `translateY` are always the
- * same for each step of the tween, because they have the same start and end
- * points and both use the same easing curve.  We can also tween `translateX`
- * and `translateY` along independent curves:
- *
- *     var tweenable = new NGTweenable();
- *     tweenable.tween({
- *       from: { transform: 'translateX(0px) translateY(0px)' },
- *       to: { transform:   'translateX(100px) translateY(100px)' },
- *       easing: { transform: 'easeInQuad bounce' },
- *       step: function (state) {
- *         console.log(state.transform);
- *       }
- *     });
- *
- * The above snippet will create values like this:
- *
- *     translateX(10.89px) translateY(82.35px)
- *     ...
- *     translateX(44.89px) translateY(86.73px)
- *     ...
- *     translateX(100px) translateY(100px)
- *
- * `translateX` and `translateY` are not in sync anymore, because `easeInQuad`
- * was specified for `translateX` and `bounce` for `translateY`.  Mixing and
- * matching easing curves can make for some interesting motion in your
- * animations.
- *
- * The order of the space-separated easing curves correspond the token values
- * they apply to.  If there are more token values than easing curves listed,
- * the last easing curve listed is used.
- * @submodule NGTweenable.token
- */
-
-// token function is defined above only so that dox-foundation sees it as
-// documentation and renders it.  It is never used, and is optimized away at
-// build time.
-
-;(function (NGTweenable) {
-
-  /**
-   * @typedef {{
-   *   formatString: string
-   *   chunkNames: Array.<string>
-   * }}
-   * @private
-   */
-  var formatManifest;
-
-  // CONSTANTS
-
-  var R_NUMBER_COMPONENT = /(\d|\-|\.)/;
-  var R_FORMAT_CHUNKS = /([^\-0-9\.]+)/g;
-  var R_UNFORMATTED_VALUES = /[0-9.\-]+/g;
-  var R_RGB = new RegExp(
-    'rgb\\(' + R_UNFORMATTED_VALUES.source +
-    (/,\s*/.source) + R_UNFORMATTED_VALUES.source +
-    (/,\s*/.source) + R_UNFORMATTED_VALUES.source + '\\)', 'g');
-  var R_RGB_PREFIX = /^.*\(/;
-  var R_HEX = /#([0-9]|[a-f]){3,6}/gi;
-  var VALUE_PLACEHOLDER = 'VAL';
-
-  // HELPERS
-
-  /**
-   * @param {Array.number} rawValues
-   * @param {string} prefix
-   *
-   * @return {Array.<string>}
-   * @private
-   */
-  function getFormatChunksFrom (rawValues, prefix) {
-    var accumulator = [];
-
-    var rawValuesLength = rawValues.length;
-    var i;
-
-    for (i = 0; i < rawValuesLength; i++) {
-      accumulator.push('_' + prefix + '_' + i);
-    }
-
-    return accumulator;
-  }
-
-  /**
-   * @param {string} formattedString
-   *
-   * @return {string}
-   * @private
-   */
-  function getFormatStringFrom (formattedString) {
-    var chunks = formattedString.match(R_FORMAT_CHUNKS);
-
-    if (!chunks) {
-      // chunks will be null if there were no tokens to parse in
-      // formattedString (for example, if formattedString is '2').  Coerce
-      // chunks to be useful here.
-      chunks = ['', ''];
-
-      // If there is only one chunk, assume that the string is a number
-      // followed by a token...
-      // NOTE: This may be an unwise assumption.
-    } else if (chunks.length === 1 ||
-      // ...or if the string starts with a number component (".", "-", or a
-      // digit)...
-    formattedString.charAt(0).match(R_NUMBER_COMPONENT)) {
-      // ...prepend an empty string here to make sure that the formatted number
-      // is properly replaced by VALUE_PLACEHOLDER
-      chunks.unshift('');
-    }
-
-    return chunks.join(VALUE_PLACEHOLDER);
-  }
-
-  /**
-   * Convert all hex color values within a string to an rgb string.
-   *
-   * @param {Object} stateObject
-   *
-   * @return {Object} The modified obj
-   * @private
-   */
-  function sanitizeObjectForHexProps (stateObject) {
-    NGTweenable.each(stateObject, function (prop) {
-      var currentProp = stateObject[prop];
-
-      if (typeof currentProp === 'string' && currentProp.match(R_HEX)) {
-        stateObject[prop] = sanitizeHexChunksToRGB(currentProp);
-      }
-    });
-  }
-
-  /**
-   * @param {string} str
-   *
-   * @return {string}
-   * @private
-   */
-  function  sanitizeHexChunksToRGB (str) {
-    return filterStringChunks(R_HEX, str, convertHexToRGB);
-  }
-
-  /**
-   * @param {string} hexString
-   *
-   * @return {string}
-   * @private
-   */
-  function convertHexToRGB (hexString) {
-    var rgbArr = hexToRGBArray(hexString);
-    return 'rgb(' + rgbArr[0] + ',' + rgbArr[1] + ',' + rgbArr[2] + ')';
-  }
-
-  var hexToRGBArray_returnArray = [];
-  /**
-   * Convert a hexadecimal string to an array with three items, one each for
-   * the red, blue, and green decimal values.
-   *
-   * @param {string} hex A hexadecimal string.
-   *
-   * @returns {Array.<number>} The converted Array of RGB values if `hex` is a
-   * valid string, or an Array of three 0's.
-   * @private
-   */
-  function hexToRGBArray (hex) {
-
-    hex = hex.replace(/#/, '');
-
-    // If the string is a shorthand three digit hex notation, normalize it to
-    // the standard six digit notation
-    if (hex.length === 3) {
-      hex = hex.split('');
-      hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
-    }
-
-    hexToRGBArray_returnArray[0] = hexToDec(hex.substr(0, 2));
-    hexToRGBArray_returnArray[1] = hexToDec(hex.substr(2, 2));
-    hexToRGBArray_returnArray[2] = hexToDec(hex.substr(4, 2));
-
-    return hexToRGBArray_returnArray;
-  }
-
-  /**
-   * Convert a base-16 number to base-10.
-   *
-   * @param {Number|String} hex The value to convert
-   *
-   * @returns {Number} The base-10 equivalent of `hex`.
-   * @private
-   */
-  function hexToDec (hex) {
-    return parseInt(hex, 16);
-  }
-
-  /**
-   * Runs a filter operation on all chunks of a string that match a RegExp
-   *
-   * @param {RegExp} pattern
-   * @param {string} unfilteredString
-   * @param {function(string)} filter
-   *
-   * @return {string}
-   * @private
-   */
-  function filterStringChunks (pattern, unfilteredString, filter) {
-    var pattenMatches = unfilteredString.match(pattern);
-    var filteredString = unfilteredString.replace(pattern, VALUE_PLACEHOLDER);
-
-    if (pattenMatches) {
-      var pattenMatchesLength = pattenMatches.length;
-      var currentChunk;
-
-      for (var i = 0; i < pattenMatchesLength; i++) {
-        currentChunk = pattenMatches.shift();
-        filteredString = filteredString.replace(
-          VALUE_PLACEHOLDER, filter(currentChunk));
-      }
-    }
-
-    return filteredString;
-  }
-
-  /**
-   * Check for floating point values within rgb strings and rounds them.
-   *
-   * @param {string} formattedString
-   *
-   * @return {string}
-   * @private
-   */
-  function sanitizeRGBChunks (formattedString) {
-    return filterStringChunks(R_RGB, formattedString, sanitizeRGBChunk);
-  }
-
-  /**
-   * @param {string} rgbChunk
-   *
-   * @return {string}
-   * @private
-   */
-  function sanitizeRGBChunk (rgbChunk) {
-    var numbers = rgbChunk.match(R_UNFORMATTED_VALUES);
-    var numbersLength = numbers.length;
-    var sanitizedString = rgbChunk.match(R_RGB_PREFIX)[0];
-
-    for (var i = 0; i < numbersLength; i++) {
-      sanitizedString += parseInt(numbers[i], 10) + ',';
-    }
-
-    sanitizedString = sanitizedString.slice(0, -1) + ')';
-
-    return sanitizedString;
-  }
-
-  /**
-   * @param {Object} stateObject
-   *
-   * @return {Object} An Object of formatManifests that correspond to
-   * the string properties of stateObject
-   * @private
-   */
-  function getFormatManifests (stateObject) {
-    var manifestAccumulator = {};
-
-    NGTweenable.each(stateObject, function (prop) {
-      var currentProp = stateObject[prop];
-
-      if (typeof currentProp === 'string') {
-        var rawValues = getValuesFrom(currentProp);
-
-        manifestAccumulator[prop] = {
-          'formatString': getFormatStringFrom(currentProp)
-          ,'chunkNames': getFormatChunksFrom(rawValues, prop)
-        };
-      }
-    });
-
-    return manifestAccumulator;
-  }
-
-  /**
-   * @param {Object} stateObject
-   * @param {Object} formatManifests
-   * @private
-   */
-  function expandFormattedProperties (stateObject, formatManifests) {
-    NGTweenable.each(formatManifests, function (prop) {
-      var currentProp = stateObject[prop];
-      var rawValues = getValuesFrom(currentProp);
-      var rawValuesLength = rawValues.length;
-
-      for (var i = 0; i < rawValuesLength; i++) {
-        stateObject[formatManifests[prop].chunkNames[i]] = +rawValues[i];
-      }
-
-      delete stateObject[prop];
-    });
-  }
-
-  /**
-   * @param {Object} stateObject
-   * @param {Object} formatManifests
-   * @private
-   */
-  function collapseFormattedProperties (stateObject, formatManifests) {
-    NGTweenable.each(formatManifests, function (prop) {
-      var currentProp = stateObject[prop];
-      var formatChunks = extractPropertyChunks(
-        stateObject, formatManifests[prop].chunkNames);
-      var valuesList = getValuesList(
-        formatChunks, formatManifests[prop].chunkNames);
-      currentProp = getFormattedValues(
-        formatManifests[prop].formatString, valuesList);
-      stateObject[prop] = sanitizeRGBChunks(currentProp);
-    });
-  }
-
-  /**
-   * @param {Object} stateObject
-   * @param {Array.<string>} chunkNames
-   *
-   * @return {Object} The extracted value chunks.
-   * @private
-   */
-  function extractPropertyChunks (stateObject, chunkNames) {
-    var extractedValues = {};
-    var currentChunkName, chunkNamesLength = chunkNames.length;
-
-    for (var i = 0; i < chunkNamesLength; i++) {
-      currentChunkName = chunkNames[i];
-      extractedValues[currentChunkName] = stateObject[currentChunkName];
-      delete stateObject[currentChunkName];
-    }
-
-    return extractedValues;
-  }
-
-  var getValuesList_accumulator = [];
-  /**
-   * @param {Object} stateObject
-   * @param {Array.<string>} chunkNames
-   *
-   * @return {Array.<number>}
-   * @private
-   */
-  function getValuesList (stateObject, chunkNames) {
-    getValuesList_accumulator.length = 0;
-    var chunkNamesLength = chunkNames.length;
-
-    for (var i = 0; i < chunkNamesLength; i++) {
-      getValuesList_accumulator.push(stateObject[chunkNames[i]]);
-    }
-
-    return getValuesList_accumulator;
-  }
-
-  /**
-   * @param {string} formatString
-   * @param {Array.<number>} rawValues
-   *
-   * @return {string}
-   * @private
-   */
-  function getFormattedValues (formatString, rawValues) {
-    var formattedValueString = formatString;
-    var rawValuesLength = rawValues.length;
-
-    for (var i = 0; i < rawValuesLength; i++) {
-      formattedValueString = formattedValueString.replace(
-        VALUE_PLACEHOLDER, +rawValues[i].toFixed(4));
-    }
-
-    return formattedValueString;
-  }
-
-  /**
-   * Note: It's the duty of the caller to convert the Array elements of the
-   * return value into numbers.  This is a performance optimization.
-   *
-   * @param {string} formattedString
-   *
-   * @return {Array.<string>|null}
-   * @private
-   */
-  function getValuesFrom (formattedString) {
-    return formattedString.match(R_UNFORMATTED_VALUES);
-  }
-
-  /**
-   * @param {Object} easingObject
-   * @param {Object} tokenData
-   * @private
-   */
-  function expandEasingObject (easingObject, tokenData) {
-    NGTweenable.each(tokenData, function (prop) {
-      var currentProp = tokenData[prop];
-      var chunkNames = currentProp.chunkNames;
-      var chunkLength = chunkNames.length;
-
-      var easing = easingObject[prop];
-      var i;
-
-      if (typeof easing === 'string') {
-        var easingChunks = easing.split(' ');
-        var lastEasingChunk = easingChunks[easingChunks.length - 1];
-
-        for (i = 0; i < chunkLength; i++) {
-          easingObject[chunkNames[i]] = easingChunks[i] || lastEasingChunk;
-        }
-
-      } else {
-        for (i = 0; i < chunkLength; i++) {
-          easingObject[chunkNames[i]] = easing;
-        }
-      }
-
-      delete easingObject[prop];
-    });
-  }
-
-  /**
-   * @param {Object} easingObject
-   * @param {Object} tokenData
-   * @private
-   */
-  function collapseEasingObject (easingObject, tokenData) {
-    NGTweenable.each(tokenData, function (prop) {
-      var currentProp = tokenData[prop];
-      var chunkNames = currentProp.chunkNames;
-      var chunkLength = chunkNames.length;
-
-      var firstEasing = easingObject[chunkNames[0]];
-      var typeofEasings = typeof firstEasing;
-
-      if (typeofEasings === 'string') {
-        var composedEasingString = '';
-
-        for (var i = 0; i < chunkLength; i++) {
-          composedEasingString += ' ' + easingObject[chunkNames[i]];
-          delete easingObject[chunkNames[i]];
-        }
-
-        easingObject[prop] = composedEasingString.substr(1);
-      } else {
-        easingObject[prop] = firstEasing;
-      }
-    });
-  }
-
-  NGTweenable.prototype.filter.token = {
-    'tweenCreated': function (currentState, fromState, toState, easingObject) {
-      sanitizeObjectForHexProps(currentState);
-      sanitizeObjectForHexProps(fromState);
-      sanitizeObjectForHexProps(toState);
-      this._tokenData = getFormatManifests(currentState);
-    },
-
-    'beforeTween': function (currentState, fromState, toState, easingObject) {
-      expandEasingObject(easingObject, this._tokenData);
-      expandFormattedProperties(currentState, this._tokenData);
-      expandFormattedProperties(fromState, this._tokenData);
-      expandFormattedProperties(toState, this._tokenData);
-    },
-
-    'afterTween': function (currentState, fromState, toState, easingObject) {
-      collapseFormattedProperties(currentState, this._tokenData);
-      collapseFormattedProperties(fromState, this._tokenData);
-      collapseFormattedProperties(toState, this._tokenData);
-      collapseEasingObject(easingObject, this._tokenData);
-    }
-  };
-
-} (NGTweenable));
-
-}).call(null);
-
-
-
-
-//##########################################################################################################################
-//## HAMMER.JS #############################################################################################################
-//##########################################################################################################################
-
-// HAMMER.JS
-
-// external module EMBEDED in nanogallery
-// NGY BUILD:
-// replace "Hammer" with "NGHammer" (case sensitive)
-// replace "var SUPPORT_POINTER_EVENTS = prefixed(window, 'PointerEvent') !== undefined;" with "var SUPPORT_POINTER_EVENTS = false;"
-// replace "define.amd" with "define.amdDISABLED"
-
-
-
-/*! NGHammer.JS - v2.0.7 - 2016-04-22
- * http://hammerjs.github.io/
- *
- * Copyright (c) 2016 Jorik Tangelder;
- * Licensed under the MIT license */
-(function(window, document, exportName, undefined) {
-  'use strict';
-
-var VENDOR_PREFIXES = ['', 'webkit', 'Moz', 'MS', 'ms', 'o'];
-var TEST_ELEMENT = document.createElement('div');
-
-var TYPE_FUNCTION = 'function';
-
-var round = Math.round;
-var abs = Math.abs;
-var now = Date.now;
-
-/**
- * set a timeout with a given scope
- * @param {Function} fn
- * @param {Number} timeout
- * @param {Object} context
- * @returns {number}
- */
-function setTimeoutContext(fn, timeout, context) {
-    return setTimeout(bindFn(fn, context), timeout);
-}
-
-/**
- * if the argument is an array, we want to execute the fn on each entry
- * if it aint an array we don't want to do a thing.
- * this is used by all the methods that accept a single and array argument.
- * @param {*|Array} arg
- * @param {String} fn
- * @param {Object} [context]
- * @returns {Boolean}
- */
-function invokeArrayArg(arg, fn, context) {
-    if (Array.isArray(arg)) {
-        each(arg, context[fn], context);
-        return true;
-    }
-    return false;
-}
-
-/**
- * walk objects and arrays
- * @param {Object} obj
- * @param {Function} iterator
- * @param {Object} context
- */
-function each(obj, iterator, context) {
-    var i;
-
-    if (!obj) {
-        return;
-    }
-
-    if (obj.forEach) {
-        obj.forEach(iterator, context);
-    } else if (obj.length !== undefined) {
-        i = 0;
-        while (i < obj.length) {
-            iterator.call(context, obj[i], i, obj);
-            i++;
-        }
-    } else {
-        for (i in obj) {
-            obj.hasOwnProperty(i) && iterator.call(context, obj[i], i, obj);
-        }
-    }
-}
-
-/**
- * wrap a method with a deprecation warning and stack trace
- * @param {Function} method
- * @param {String} name
- * @param {String} message
- * @returns {Function} A new function wrapping the supplied method.
- */
-function deprecate(method, name, message) {
-    var deprecationMessage = 'DEPRECATED METHOD: ' + name + '\n' + message + ' AT \n';
-    return function() {
-        var e = new Error('get-stack-trace');
-        var stack = e && e.stack ? e.stack.replace(/^[^\(]+?[\n$]/gm, '')
-            .replace(/^\s+at\s+/gm, '')
-            .replace(/^Object.<anonymous>\s*\(/gm, '{anonymous}()@') : 'Unknown Stack Trace';
-
-        var log = window.console && (window.console.warn || window.console.log);
-        if (log) {
-            log.call(window.console, deprecationMessage, stack);
-        }
-        return method.apply(this, arguments);
-    };
-}
-
-/**
- * extend object.
- * means that properties in dest will be overwritten by the ones in src.
- * @param {Object} target
- * @param {...Object} objects_to_assign
- * @returns {Object} target
- */
-var assign;
-if (typeof Object.assign !== 'function') {
-    assign = function assign(target) {
-        if (target === undefined || target === null) {
-            throw new TypeError('Cannot convert undefined or null to object');
-        }
-
-        var output = Object(target);
-        for (var index = 1; index < arguments.length; index++) {
-            var source = arguments[index];
-            if (source !== undefined && source !== null) {
-                for (var nextKey in source) {
-                    if (source.hasOwnProperty(nextKey)) {
-                        output[nextKey] = source[nextKey];
-                    }
-                }
-            }
-        }
-        return output;
-    };
-} else {
-    assign = Object.assign;
-}
-
-/**
- * extend object.
- * means that properties in dest will be overwritten by the ones in src.
- * @param {Object} dest
- * @param {Object} src
- * @param {Boolean} [merge=false]
- * @returns {Object} dest
- */
-var extend = deprecate(function extend(dest, src, merge) {
-    var keys = Object.keys(src);
-    var i = 0;
-    while (i < keys.length) {
-        if (!merge || (merge && dest[keys[i]] === undefined)) {
-            dest[keys[i]] = src[keys[i]];
-        }
-        i++;
-    }
-    return dest;
-}, 'extend', 'Use `assign`.');
-
-/**
- * merge the values from src in the dest.
- * means that properties that exist in dest will not be overwritten by src
- * @param {Object} dest
- * @param {Object} src
- * @returns {Object} dest
- */
-var merge = deprecate(function merge(dest, src) {
-    return extend(dest, src, true);
-}, 'merge', 'Use `assign`.');
-
-/**
- * simple class inheritance
- * @param {Function} child
- * @param {Function} base
- * @param {Object} [properties]
- */
-function inherit(child, base, properties) {
-    var baseP = base.prototype,
-        childP;
-
-    childP = child.prototype = Object.create(baseP);
-    childP.constructor = child;
-    childP._super = baseP;
-
-    if (properties) {
-        assign(childP, properties);
-    }
-}
-
-/**
- * simple function bind
- * @param {Function} fn
- * @param {Object} context
- * @returns {Function}
- */
-function bindFn(fn, context) {
-    return function boundFn() {
-        return fn.apply(context, arguments);
-    };
-}
-
-/**
- * let a boolean value also be a function that must return a boolean
- * this first item in args will be used as the context
- * @param {Boolean|Function} val
- * @param {Array} [args]
- * @returns {Boolean}
- */
-function boolOrFn(val, args) {
-    if (typeof val == TYPE_FUNCTION) {
-        return val.apply(args ? args[0] || undefined : undefined, args);
-    }
-    return val;
-}
-
-/**
- * use the val2 when val1 is undefined
- * @param {*} val1
- * @param {*} val2
- * @returns {*}
- */
-function ifUndefined(val1, val2) {
-    return (val1 === undefined) ? val2 : val1;
-}
-
-/**
- * addEventListener with multiple events at once
- * @param {EventTarget} target
- * @param {String} types
- * @param {Function} handler
- */
-function addEventListeners(target, types, handler) {
-    each(splitStr(types), function(type) {
-        target.addEventListener(type, handler, false);
-    });
-}
-
-/**
- * removeEventListener with multiple events at once
- * @param {EventTarget} target
- * @param {String} types
- * @param {Function} handler
- */
-function removeEventListeners(target, types, handler) {
-    each(splitStr(types), function(type) {
-        target.removeEventListener(type, handler, false);
-    });
-}
-
-/**
- * find if a node is in the given parent
- * @method hasParent
- * @param {HTMLElement} node
- * @param {HTMLElement} parent
- * @return {Boolean} found
- */
-function hasParent(node, parent) {
-    while (node) {
-        if (node == parent) {
-            return true;
-        }
-        node = node.parentNode;
-    }
-    return false;
-}
-
-/**
- * small indexOf wrapper
- * @param {String} str
- * @param {String} find
- * @returns {Boolean} found
- */
-function inStr(str, find) {
-    return str.indexOf(find) > -1;
-}
-
-/**
- * split string on whitespace
- * @param {String} str
- * @returns {Array} words
- */
-function splitStr(str) {
-    return str.trim().split(/\s+/g);
-}
-
-/**
- * find if a array contains the object using indexOf or a simple polyFill
- * @param {Array} src
- * @param {String} find
- * @param {String} [findByKey]
- * @return {Boolean|Number} false when not found, or the index
- */
-function inArray(src, find, findByKey) {
-    if (src.indexOf && !findByKey) {
-        return src.indexOf(find);
-    } else {
-        var i = 0;
-        while (i < src.length) {
-            if ((findByKey && src[i][findByKey] == find) || (!findByKey && src[i] === find)) {
-                return i;
-            }
-            i++;
-        }
-        return -1;
-    }
-}
-
-/**
- * convert array-like objects to real arrays
- * @param {Object} obj
- * @returns {Array}
- */
-function toArray(obj) {
-    return Array.prototype.slice.call(obj, 0);
-}
-
-/**
- * unique array with objects based on a key (like 'id') or just by the array's value
- * @param {Array} src [{id:1},{id:2},{id:1}]
- * @param {String} [key]
- * @param {Boolean} [sort=False]
- * @returns {Array} [{id:1},{id:2}]
- */
-function uniqueArray(src, key, sort) {
-    var results = [];
-    var values = [];
-    var i = 0;
-
-    while (i < src.length) {
-        var val = key ? src[i][key] : src[i];
-        if (inArray(values, val) < 0) {
-            results.push(src[i]);
-        }
-        values[i] = val;
-        i++;
-    }
-
-    if (sort) {
-        if (!key) {
-            results = results.sort();
-        } else {
-            results = results.sort(function sortUniqueArray(a, b) {
-                return a[key] > b[key];
-            });
-        }
-    }
-
-    return results;
-}
-
-/**
- * get the prefixed property
- * @param {Object} obj
- * @param {String} property
- * @returns {String|Undefined} prefixed
- */
-function prefixed(obj, property) {
-    var prefix, prop;
-    var camelProp = property[0].toUpperCase() + property.slice(1);
-
-    var i = 0;
-    while (i < VENDOR_PREFIXES.length) {
-        prefix = VENDOR_PREFIXES[i];
-        prop = (prefix) ? prefix + camelProp : property;
-
-        if (prop in obj) {
-            return prop;
-        }
-        i++;
-    }
-    return undefined;
-}
-
-/**
- * get a unique id
- * @returns {number} uniqueId
- */
-var _uniqueId = 1;
-function uniqueId() {
-    return _uniqueId++;
-}
-
-/**
- * get the window object of an element
- * @param {HTMLElement} element
- * @returns {DocumentView|Window}
- */
-function getWindowForElement(element) {
-    var doc = element.ownerDocument || element;
-    return (doc.defaultView || doc.parentWindow || window);
-}
-
-var MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android/i;
-
-var SUPPORT_TOUCH = ('ontouchstart' in window);
-// var SUPPORT_POINTER_EVENTS = prefixed(window, 'PointerEvent') !== undefined;
-var SUPPORT_POINTER_EVENTS = false;
-var SUPPORT_ONLY_TOUCH = SUPPORT_TOUCH && MOBILE_REGEX.test(navigator.userAgent);
-
-var INPUT_TYPE_TOUCH = 'touch';
-var INPUT_TYPE_PEN = 'pen';
-var INPUT_TYPE_MOUSE = 'mouse';
-var INPUT_TYPE_KINECT = 'kinect';
-
-var COMPUTE_INTERVAL = 25;
-
-var INPUT_START = 1;
-var INPUT_MOVE = 2;
-var INPUT_END = 4;
-var INPUT_CANCEL = 8;
-
-var DIRECTION_NONE = 1;
-var DIRECTION_LEFT = 2;
-var DIRECTION_RIGHT = 4;
-var DIRECTION_UP = 8;
-var DIRECTION_DOWN = 16;
-
-var DIRECTION_HORIZONTAL = DIRECTION_LEFT | DIRECTION_RIGHT;
-var DIRECTION_VERTICAL = DIRECTION_UP | DIRECTION_DOWN;
-var DIRECTION_ALL = DIRECTION_HORIZONTAL | DIRECTION_VERTICAL;
-
-var PROPS_XY = ['x', 'y'];
-var PROPS_CLIENT_XY = ['clientX', 'clientY'];
-
-/**
- * create new input type manager
- * @param {Manager} manager
- * @param {Function} callback
- * @returns {Input}
- * @constructor
- */
-function Input(manager, callback) {
-    var self = this;
-    this.manager = manager;
-    this.callback = callback;
-    this.element = manager.element;
-    this.target = manager.options.inputTarget;
-
-    // smaller wrapper around the handler, for the scope and the enabled state of the manager,
-    // so when disabled the input events are completely bypassed.
-    this.domHandler = function(ev) {
-        if (boolOrFn(manager.options.enable, [manager])) {
-            self.handler(ev);
-        }
-    };
-
-    this.init();
-
-}
-
-Input.prototype = {
-    /**
-     * should handle the inputEvent data and trigger the callback
-     * @virtual
-     */
-    handler: function() { },
-
-    /**
-     * bind the events
-     */
-    init: function() {
-        this.evEl && addEventListeners(this.element, this.evEl, this.domHandler);
-        this.evTarget && addEventListeners(this.target, this.evTarget, this.domHandler);
-        this.evWin && addEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler);
-    },
-
-    /**
-     * unbind the events
-     */
-    destroy: function() {
-        this.evEl && removeEventListeners(this.element, this.evEl, this.domHandler);
-        this.evTarget && removeEventListeners(this.target, this.evTarget, this.domHandler);
-        this.evWin && removeEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler);
-    }
-};
-
-/**
- * create new input type manager
- * called by the Manager constructor
- * @param {NGHammer} manager
- * @returns {Input}
- */
-function createInputInstance(manager) {
-    var Type;
-    var inputClass = manager.options.inputClass;
-
-    if (inputClass) {
-        Type = inputClass;
-    } else if (SUPPORT_POINTER_EVENTS) {
-        Type = PointerEventInput;
-    } else if (SUPPORT_ONLY_TOUCH) {
-        Type = TouchInput;
-    } else if (!SUPPORT_TOUCH) {
-        Type = MouseInput;
-    } else {
-        Type = TouchMouseInput;
-    }
-    return new (Type)(manager, inputHandler);
-}
-
-/**
- * handle input events
- * @param {Manager} manager
- * @param {String} eventType
- * @param {Object} input
- */
-function inputHandler(manager, eventType, input) {
-    var pointersLen = input.pointers.length;
-    var changedPointersLen = input.changedPointers.length;
-    var isFirst = (eventType & INPUT_START && (pointersLen - changedPointersLen === 0));
-    var isFinal = (eventType & (INPUT_END | INPUT_CANCEL) && (pointersLen - changedPointersLen === 0));
-
-    input.isFirst = !!isFirst;
-    input.isFinal = !!isFinal;
-
-    if (isFirst) {
-        manager.session = {};
-    }
-
-    // source event is the normalized value of the domEvents
-    // like 'touchstart, mouseup, pointerdown'
-    input.eventType = eventType;
-
-    // compute scale, rotation etc
-    computeInputData(manager, input);
-
-    // emit secret event
-    manager.emit('hammer.input', input);
-
-    manager.recognize(input);
-    manager.session.prevInput = input;
-}
-
-/**
- * extend the data with some usable properties like scale, rotate, velocity etc
- * @param {Object} manager
- * @param {Object} input
- */
-function computeInputData(manager, input) {
-    var session = manager.session;
-    var pointers = input.pointers;
-    var pointersLength = pointers.length;
-
-    // store the first input to calculate the distance and direction
-    if (!session.firstInput) {
-        session.firstInput = simpleCloneInputData(input);
-    }
-
-    // to compute scale and rotation we need to store the multiple touches
-    if (pointersLength > 1 && !session.firstMultiple) {
-        session.firstMultiple = simpleCloneInputData(input);
-    } else if (pointersLength === 1) {
-        session.firstMultiple = false;
-    }
-
-    var firstInput = session.firstInput;
-    var firstMultiple = session.firstMultiple;
-    var offsetCenter = firstMultiple ? firstMultiple.center : firstInput.center;
-
-    var center = input.center = getCenter(pointers);
-    input.timeStamp = now();
-    input.deltaTime = input.timeStamp - firstInput.timeStamp;
-
-    input.angle = getAngle(offsetCenter, center);
-    input.distance = getDistance(offsetCenter, center);
-
-    computeDeltaXY(session, input);
-    input.offsetDirection = getDirection(input.deltaX, input.deltaY);
-
-    var overallVelocity = getVelocity(input.deltaTime, input.deltaX, input.deltaY);
-    input.overallVelocityX = overallVelocity.x;
-    input.overallVelocityY = overallVelocity.y;
-    input.overallVelocity = (abs(overallVelocity.x) > abs(overallVelocity.y)) ? overallVelocity.x : overallVelocity.y;
-
-    input.scale = firstMultiple ? getScale(firstMultiple.pointers, pointers) : 1;
-    input.rotation = firstMultiple ? getRotation(firstMultiple.pointers, pointers) : 0;
-
-    input.maxPointers = !session.prevInput ? input.pointers.length : ((input.pointers.length >
-        session.prevInput.maxPointers) ? input.pointers.length : session.prevInput.maxPointers);
-
-    computeIntervalInputData(session, input);
-
-    // find the correct target
-    var target = manager.element;
-    if (hasParent(input.srcEvent.target, target)) {
-        target = input.srcEvent.target;
-    }
-    input.target = target;
-}
-
-function computeDeltaXY(session, input) {
-    var center = input.center;
-    var offset = session.offsetDelta || {};
-    var prevDelta = session.prevDelta || {};
-    var prevInput = session.prevInput || {};
-
-    if (input.eventType === INPUT_START || prevInput.eventType === INPUT_END) {
-        prevDelta = session.prevDelta = {
-            x: prevInput.deltaX || 0,
-            y: prevInput.deltaY || 0
-        };
-
-        offset = session.offsetDelta = {
-            x: center.x,
-            y: center.y
-        };
-    }
-
-    input.deltaX = prevDelta.x + (center.x - offset.x);
-    input.deltaY = prevDelta.y + (center.y - offset.y);
-}
-
-/**
- * velocity is calculated every x ms
- * @param {Object} session
- * @param {Object} input
- */
-function computeIntervalInputData(session, input) {
-    var last = session.lastInterval || input,
-        deltaTime = input.timeStamp - last.timeStamp,
-        velocity, velocityX, velocityY, direction;
-
-    if (input.eventType != INPUT_CANCEL && (deltaTime > COMPUTE_INTERVAL || last.velocity === undefined)) {
-        var deltaX = input.deltaX - last.deltaX;
-        var deltaY = input.deltaY - last.deltaY;
-
-        var v = getVelocity(deltaTime, deltaX, deltaY);
-        velocityX = v.x;
-        velocityY = v.y;
-        velocity = (abs(v.x) > abs(v.y)) ? v.x : v.y;
-        direction = getDirection(deltaX, deltaY);
-
-        session.lastInterval = input;
-    } else {
-        // use latest velocity info if it doesn't overtake a minimum period
-        velocity = last.velocity;
-        velocityX = last.velocityX;
-        velocityY = last.velocityY;
-        direction = last.direction;
-    }
-
-    input.velocity = velocity;
-    input.velocityX = velocityX;
-    input.velocityY = velocityY;
-    input.direction = direction;
-}
-
-/**
- * create a simple clone from the input used for storage of firstInput and firstMultiple
- * @param {Object} input
- * @returns {Object} clonedInputData
- */
-function simpleCloneInputData(input) {
-    // make a simple copy of the pointers because we will get a reference if we don't
-    // we only need clientXY for the calculations
-    var pointers = [];
-    var i = 0;
-    while (i < input.pointers.length) {
-        pointers[i] = {
-            clientX: round(input.pointers[i].clientX),
-            clientY: round(input.pointers[i].clientY)
-        };
-        i++;
-    }
-
-    return {
-        timeStamp: now(),
-        pointers: pointers,
-        center: getCenter(pointers),
-        deltaX: input.deltaX,
-        deltaY: input.deltaY
-    };
-}
-
-/**
- * get the center of all the pointers
- * @param {Array} pointers
- * @return {Object} center contains `x` and `y` properties
- */
-function getCenter(pointers) {
-    var pointersLength = pointers.length;
-
-    // no need to loop when only one touch
-    if (pointersLength === 1) {
-        return {
-            x: round(pointers[0].clientX),
-            y: round(pointers[0].clientY)
-        };
-    }
-
-    var x = 0, y = 0, i = 0;
-    while (i < pointersLength) {
-        x += pointers[i].clientX;
-        y += pointers[i].clientY;
-        i++;
-    }
-
-    return {
-        x: round(x / pointersLength),
-        y: round(y / pointersLength)
-    };
-}
-
-/**
- * calculate the velocity between two points. unit is in px per ms.
- * @param {Number} deltaTime
- * @param {Number} x
- * @param {Number} y
- * @return {Object} velocity `x` and `y`
- */
-function getVelocity(deltaTime, x, y) {
-    return {
-        x: x / deltaTime || 0,
-        y: y / deltaTime || 0
-    };
-}
-
-/**
- * get the direction between two points
- * @param {Number} x
- * @param {Number} y
- * @return {Number} direction
- */
-function getDirection(x, y) {
-    if (x === y) {
-        return DIRECTION_NONE;
-    }
-
-    if (abs(x) >= abs(y)) {
-        return x < 0 ? DIRECTION_LEFT : DIRECTION_RIGHT;
-    }
-    return y < 0 ? DIRECTION_UP : DIRECTION_DOWN;
-}
-
-/**
- * calculate the absolute distance between two points
- * @param {Object} p1 {x, y}
- * @param {Object} p2 {x, y}
- * @param {Array} [props] containing x and y keys
- * @return {Number} distance
- */
-function getDistance(p1, p2, props) {
-    if (!props) {
-        props = PROPS_XY;
-    }
-    var x = p2[props[0]] - p1[props[0]],
-        y = p2[props[1]] - p1[props[1]];
-
-    return Math.sqrt((x * x) + (y * y));
-}
-
-/**
- * calculate the angle between two coordinates
- * @param {Object} p1
- * @param {Object} p2
- * @param {Array} [props] containing x and y keys
- * @return {Number} angle
- */
-function getAngle(p1, p2, props) {
-    if (!props) {
-        props = PROPS_XY;
-    }
-    var x = p2[props[0]] - p1[props[0]],
-        y = p2[props[1]] - p1[props[1]];
-    return Math.atan2(y, x) * 180 / Math.PI;
-}
-
-/**
- * calculate the rotation degrees between two pointersets
- * @param {Array} start array of pointers
- * @param {Array} end array of pointers
- * @return {Number} rotation
- */
-function getRotation(start, end) {
-    return getAngle(end[1], end[0], PROPS_CLIENT_XY) + getAngle(start[1], start[0], PROPS_CLIENT_XY);
-}
-
-/**
- * calculate the scale factor between two pointersets
- * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
- * @param {Array} start array of pointers
- * @param {Array} end array of pointers
- * @return {Number} scale
- */
-function getScale(start, end) {
-    return getDistance(end[0], end[1], PROPS_CLIENT_XY) / getDistance(start[0], start[1], PROPS_CLIENT_XY);
-}
-
-var MOUSE_INPUT_MAP = {
-    mousedown: INPUT_START,
-    mousemove: INPUT_MOVE,
-    mouseup: INPUT_END
-};
-
-var MOUSE_ELEMENT_EVENTS = 'mousedown';
-var MOUSE_WINDOW_EVENTS = 'mousemove mouseup';
-
-/**
- * Mouse events input
- * @constructor
- * @extends Input
- */
-function MouseInput() {
-    this.evEl = MOUSE_ELEMENT_EVENTS;
-    this.evWin = MOUSE_WINDOW_EVENTS;
-
-    this.pressed = false; // mousedown state
-
-    Input.apply(this, arguments);
-}
-
-inherit(MouseInput, Input, {
-    /**
-     * handle mouse events
-     * @param {Object} ev
-     */
-    handler: function MEhandler(ev) {
-        var eventType = MOUSE_INPUT_MAP[ev.type];
-
-        // on start we want to have the left mouse button down
-        if (eventType & INPUT_START && ev.button === 0) {
-            this.pressed = true;
-        }
-
-        if (eventType & INPUT_MOVE && ev.which !== 1) {
-            eventType = INPUT_END;
-        }
-
-        // mouse must be down
-        if (!this.pressed) {
-            return;
-        }
-
-        if (eventType & INPUT_END) {
-            this.pressed = false;
-        }
-
-        this.callback(this.manager, eventType, {
-            pointers: [ev],
-            changedPointers: [ev],
-            pointerType: INPUT_TYPE_MOUSE,
-            srcEvent: ev
-        });
-    }
-});
-
-var POINTER_INPUT_MAP = {
-    pointerdown: INPUT_START,
-    pointermove: INPUT_MOVE,
-    pointerup: INPUT_END,
-    pointercancel: INPUT_CANCEL,
-    pointerout: INPUT_CANCEL
-};
-
-// in IE10 the pointer types is defined as an enum
-var IE10_POINTER_TYPE_ENUM = {
-    2: INPUT_TYPE_TOUCH,
-    3: INPUT_TYPE_PEN,
-    4: INPUT_TYPE_MOUSE,
-    5: INPUT_TYPE_KINECT // see https://twitter.com/jacobrossi/status/480596438489890816
-};
-
-var POINTER_ELEMENT_EVENTS = 'pointerdown';
-var POINTER_WINDOW_EVENTS = 'pointermove pointerup pointercancel';
-
-// IE10 has prefixed support, and case-sensitive
-if (window.MSPointerEvent && !window.PointerEvent) {
-    POINTER_ELEMENT_EVENTS = 'MSPointerDown';
-    POINTER_WINDOW_EVENTS = 'MSPointerMove MSPointerUp MSPointerCancel';
-}
-
-/**
- * Pointer events input
- * @constructor
- * @extends Input
- */
-function PointerEventInput() {
-    this.evEl = POINTER_ELEMENT_EVENTS;
-    this.evWin = POINTER_WINDOW_EVENTS;
-
-    Input.apply(this, arguments);
-
-    this.store = (this.manager.session.pointerEvents = []);
-}
-
-inherit(PointerEventInput, Input, {
-    /**
-     * handle mouse events
-     * @param {Object} ev
-     */
-    handler: function PEhandler(ev) {
-        var store = this.store;
-        var removePointer = false;
-
-        var eventTypeNormalized = ev.type.toLowerCase().replace('ms', '');
-        var eventType = POINTER_INPUT_MAP[eventTypeNormalized];
-        var pointerType = IE10_POINTER_TYPE_ENUM[ev.pointerType] || ev.pointerType;
-
-        var isTouch = (pointerType == INPUT_TYPE_TOUCH);
-
-        // get index of the event in the store
-        var storeIndex = inArray(store, ev.pointerId, 'pointerId');
-
-        // start and mouse must be down
-        if (eventType & INPUT_START && (ev.button === 0 || isTouch)) {
-            if (storeIndex < 0) {
-                store.push(ev);
-                storeIndex = store.length - 1;
-            }
-        } else if (eventType & (INPUT_END | INPUT_CANCEL)) {
-            removePointer = true;
-        }
-
-        // it not found, so the pointer hasn't been down (so it's probably a hover)
-        if (storeIndex < 0) {
-            return;
-        }
-
-        // update the event in the store
-        store[storeIndex] = ev;
-
-        this.callback(this.manager, eventType, {
-            pointers: store,
-            changedPointers: [ev],
-            pointerType: pointerType,
-            srcEvent: ev
-        });
-
-        if (removePointer) {
-            // remove from the store
-            store.splice(storeIndex, 1);
-        }
-    }
-});
-
-var SINGLE_TOUCH_INPUT_MAP = {
-    touchstart: INPUT_START,
-    touchmove: INPUT_MOVE,
-    touchend: INPUT_END,
-    touchcancel: INPUT_CANCEL
-};
-
-var SINGLE_TOUCH_TARGET_EVENTS = 'touchstart';
-var SINGLE_TOUCH_WINDOW_EVENTS = 'touchstart touchmove touchend touchcancel';
-
-/**
- * Touch events input
- * @constructor
- * @extends Input
- */
-function SingleTouchInput() {
-    this.evTarget = SINGLE_TOUCH_TARGET_EVENTS;
-    this.evWin = SINGLE_TOUCH_WINDOW_EVENTS;
-    this.started = false;
-
-    Input.apply(this, arguments);
-}
-
-inherit(SingleTouchInput, Input, {
-    handler: function TEhandler(ev) {
-        var type = SINGLE_TOUCH_INPUT_MAP[ev.type];
-
-        // should we handle the touch events?
-        if (type === INPUT_START) {
-            this.started = true;
-        }
-
-        if (!this.started) {
-            return;
-        }
-
-        var touches = normalizeSingleTouches.call(this, ev, type);
-
-        // when done, reset the started state
-        if (type & (INPUT_END | INPUT_CANCEL) && touches[0].length - touches[1].length === 0) {
-            this.started = false;
-        }
-
-        this.callback(this.manager, type, {
-            pointers: touches[0],
-            changedPointers: touches[1],
-            pointerType: INPUT_TYPE_TOUCH,
-            srcEvent: ev
-        });
-    }
-});
-
-/**
- * @this {TouchInput}
- * @param {Object} ev
- * @param {Number} type flag
- * @returns {undefined|Array} [all, changed]
- */
-function normalizeSingleTouches(ev, type) {
-    var all = toArray(ev.touches);
-    var changed = toArray(ev.changedTouches);
-
-    if (type & (INPUT_END | INPUT_CANCEL)) {
-        all = uniqueArray(all.concat(changed), 'identifier', true);
-    }
-
-    return [all, changed];
-}
-
-var TOUCH_INPUT_MAP = {
-    touchstart: INPUT_START,
-    touchmove: INPUT_MOVE,
-    touchend: INPUT_END,
-    touchcancel: INPUT_CANCEL
-};
-
-var TOUCH_TARGET_EVENTS = 'touchstart touchmove touchend touchcancel';
-
-/**
- * Multi-user touch events input
- * @constructor
- * @extends Input
- */
-function TouchInput() {
-    this.evTarget = TOUCH_TARGET_EVENTS;
-    this.targetIds = {};
-
-    Input.apply(this, arguments);
-}
-
-inherit(TouchInput, Input, {
-    handler: function MTEhandler(ev) {
-        var type = TOUCH_INPUT_MAP[ev.type];
-        var touches = getTouches.call(this, ev, type);
-        if (!touches) {
-            return;
-        }
-
-        this.callback(this.manager, type, {
-            pointers: touches[0],
-            changedPointers: touches[1],
-            pointerType: INPUT_TYPE_TOUCH,
-            srcEvent: ev
-        });
-    }
-});
-
-/**
- * @this {TouchInput}
- * @param {Object} ev
- * @param {Number} type flag
- * @returns {undefined|Array} [all, changed]
- */
-function getTouches(ev, type) {
-    var allTouches = toArray(ev.touches);
-    var targetIds = this.targetIds;
-
-    // when there is only one touch, the process can be simplified
-    if (type & (INPUT_START | INPUT_MOVE) && allTouches.length === 1) {
-        targetIds[allTouches[0].identifier] = true;
-        return [allTouches, allTouches];
-    }
-
-    var i,
-        targetTouches,
-        changedTouches = toArray(ev.changedTouches),
-        changedTargetTouches = [],
-        target = this.target;
-
-    // get target touches from touches
-    targetTouches = allTouches.filter(function(touch) {
-        return hasParent(touch.target, target);
-    });
-
-    // collect touches
-    if (type === INPUT_START) {
-        i = 0;
-        while (i < targetTouches.length) {
-            targetIds[targetTouches[i].identifier] = true;
-            i++;
-        }
-    }
-
-    // filter changed touches to only contain touches that exist in the collected target ids
-    i = 0;
-    while (i < changedTouches.length) {
-        if (targetIds[changedTouches[i].identifier]) {
-            changedTargetTouches.push(changedTouches[i]);
-        }
-
-        // cleanup removed touches
-        if (type & (INPUT_END | INPUT_CANCEL)) {
-            delete targetIds[changedTouches[i].identifier];
-        }
-        i++;
-    }
-
-    if (!changedTargetTouches.length) {
-        return;
-    }
-
-    return [
-        // merge targetTouches with changedTargetTouches so it contains ALL touches, including 'end' and 'cancel'
-        uniqueArray(targetTouches.concat(changedTargetTouches), 'identifier', true),
-        changedTargetTouches
-    ];
-}
-
-/**
- * Combined touch and mouse input
- *
- * Touch has a higher priority then mouse, and while touching no mouse events are allowed.
- * This because touch devices also emit mouse events while doing a touch.
- *
- * @constructor
- * @extends Input
- */
-
-var DEDUP_TIMEOUT = 2500;
-var DEDUP_DISTANCE = 25;
-
-function TouchMouseInput() {
-    Input.apply(this, arguments);
-
-    var handler = bindFn(this.handler, this);
-    this.touch = new TouchInput(this.manager, handler);
-    this.mouse = new MouseInput(this.manager, handler);
-
-    this.primaryTouch = null;
-    this.lastTouches = [];
-}
-
-inherit(TouchMouseInput, Input, {
-    /**
-     * handle mouse and touch events
-     * @param {NGHammer} manager
-     * @param {String} inputEvent
-     * @param {Object} inputData
-     */
-    handler: function TMEhandler(manager, inputEvent, inputData) {
-        var isTouch = (inputData.pointerType == INPUT_TYPE_TOUCH),
-            isMouse = (inputData.pointerType == INPUT_TYPE_MOUSE);
-
-        if (isMouse && inputData.sourceCapabilities && inputData.sourceCapabilities.firesTouchEvents) {
-            return;
-        }
-
-        // when we're in a touch event, record touches to  de-dupe synthetic mouse event
-        if (isTouch) {
-            recordTouches.call(this, inputEvent, inputData);
-        } else if (isMouse && isSyntheticEvent.call(this, inputData)) {
-            return;
-        }
-
-        this.callback(manager, inputEvent, inputData);
-    },
-
-    /**
-     * remove the event listeners
-     */
-    destroy: function destroy() {
-        this.touch.destroy();
-        this.mouse.destroy();
-    }
-});
-
-function recordTouches(eventType, eventData) {
-    if (eventType & INPUT_START) {
-        this.primaryTouch = eventData.changedPointers[0].identifier;
-        setLastTouch.call(this, eventData);
-    } else if (eventType & (INPUT_END | INPUT_CANCEL)) {
-        setLastTouch.call(this, eventData);
-    }
-}
-
-function setLastTouch(eventData) {
-    var touch = eventData.changedPointers[0];
-
-    if (touch.identifier === this.primaryTouch) {
-        var lastTouch = {x: touch.clientX, y: touch.clientY};
-        this.lastTouches.push(lastTouch);
-        var lts = this.lastTouches;
-        var removeLastTouch = function() {
-            var i = lts.indexOf(lastTouch);
-            if (i > -1) {
-                lts.splice(i, 1);
-            }
-        };
-        setTimeout(removeLastTouch, DEDUP_TIMEOUT);
-    }
-}
-
-function isSyntheticEvent(eventData) {
-    var x = eventData.srcEvent.clientX, y = eventData.srcEvent.clientY;
-    for (var i = 0; i < this.lastTouches.length; i++) {
-        var t = this.lastTouches[i];
-        var dx = Math.abs(x - t.x), dy = Math.abs(y - t.y);
-        if (dx <= DEDUP_DISTANCE && dy <= DEDUP_DISTANCE) {
-            return true;
-        }
-    }
-    return false;
-}
-
-var PREFIXED_TOUCH_ACTION = prefixed(TEST_ELEMENT.style, 'touchAction');
-var NATIVE_TOUCH_ACTION = PREFIXED_TOUCH_ACTION !== undefined;
-
-// magical touchAction value
-var TOUCH_ACTION_COMPUTE = 'compute';
-var TOUCH_ACTION_AUTO = 'auto';
-var TOUCH_ACTION_MANIPULATION = 'manipulation'; // not implemented
-var TOUCH_ACTION_NONE = 'none';
-var TOUCH_ACTION_PAN_X = 'pan-x';
-var TOUCH_ACTION_PAN_Y = 'pan-y';
-var TOUCH_ACTION_MAP = getTouchActionProps();
-
-/**
- * Touch Action
- * sets the touchAction property or uses the js alternative
- * @param {Manager} manager
- * @param {String} value
- * @constructor
- */
-function TouchAction(manager, value) {
-    this.manager = manager;
-    this.set(value);
-}
-
-TouchAction.prototype = {
-    /**
-     * set the touchAction value on the element or enable the polyfill
-     * @param {String} value
-     */
-    set: function(value) {
-        // find out the touch-action by the event handlers
-        if (value == TOUCH_ACTION_COMPUTE) {
-            value = this.compute();
-        }
-
-        if (NATIVE_TOUCH_ACTION && this.manager.element.style && TOUCH_ACTION_MAP[value]) {
-            this.manager.element.style[PREFIXED_TOUCH_ACTION] = value;
-        }
-        this.actions = value.toLowerCase().trim();
-    },
-
-    /**
-     * just re-set the touchAction value
-     */
-    update: function() {
-        this.set(this.manager.options.touchAction);
-    },
-
-    /**
-     * compute the value for the touchAction property based on the recognizer's settings
-     * @returns {String} value
-     */
-    compute: function() {
-        var actions = [];
-        each(this.manager.recognizers, function(recognizer) {
-            if (boolOrFn(recognizer.options.enable, [recognizer])) {
-                actions = actions.concat(recognizer.getTouchAction());
-            }
-        });
-        return cleanTouchActions(actions.join(' '));
-    },
-
-    /**
-     * this method is called on each input cycle and provides the preventing of the browser behavior
-     * @param {Object} input
-     */
-    preventDefaults: function(input) {
-        var srcEvent = input.srcEvent;
-        var direction = input.offsetDirection;
-
-        // if the touch action did prevented once this session
-        if (this.manager.session.prevented) {
-            srcEvent.preventDefault();
-            return;
-        }
-
-        var actions = this.actions;
-        var hasNone = inStr(actions, TOUCH_ACTION_NONE) && !TOUCH_ACTION_MAP[TOUCH_ACTION_NONE];
-        var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_Y];
-        var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_X];
-
-        if (hasNone) {
-            //do not prevent defaults if this is a tap gesture
-
-            var isTapPointer = input.pointers.length === 1;
-            var isTapMovement = input.distance < 2;
-            var isTapTouchTime = input.deltaTime < 250;
-
-            if (isTapPointer && isTapMovement && isTapTouchTime) {
-                return;
-            }
-        }
-
-        if (hasPanX && hasPanY) {
-            // `pan-x pan-y` means browser handles all scrolling/panning, do not prevent
-            return;
-        }
-
-        if (hasNone ||
-            (hasPanY && direction & DIRECTION_HORIZONTAL) ||
-            (hasPanX && direction & DIRECTION_VERTICAL)) {
-            return this.preventSrc(srcEvent);
-        }
-    },
-
-    /**
-     * call preventDefault to prevent the browser's default behavior (scrolling in most cases)
-     * @param {Object} srcEvent
-     */
-    preventSrc: function(srcEvent) {
-        this.manager.session.prevented = true;
-        srcEvent.preventDefault();
-    }
-};
-
-/**
- * when the touchActions are collected they are not a valid value, so we need to clean things up. *
- * @param {String} actions
- * @returns {*}
- */
-function cleanTouchActions(actions) {
-    // none
-    if (inStr(actions, TOUCH_ACTION_NONE)) {
-        return TOUCH_ACTION_NONE;
-    }
-
-    var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X);
-    var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y);
-
-    // if both pan-x and pan-y are set (different recognizers
-    // for different directions, e.g. horizontal pan but vertical swipe?)
-    // we need none (as otherwise with pan-x pan-y combined none of these
-    // recognizers will work, since the browser would handle all panning
-    if (hasPanX && hasPanY) {
-        return TOUCH_ACTION_NONE;
-    }
-
-    // pan-x OR pan-y
-    if (hasPanX || hasPanY) {
-        return hasPanX ? TOUCH_ACTION_PAN_X : TOUCH_ACTION_PAN_Y;
-    }
-
-    // manipulation
-    if (inStr(actions, TOUCH_ACTION_MANIPULATION)) {
-        return TOUCH_ACTION_MANIPULATION;
-    }
-
-    return TOUCH_ACTION_AUTO;
-}
-
-function getTouchActionProps() {
-    if (!NATIVE_TOUCH_ACTION) {
-        return false;
-    }
-    var touchMap = {};
-    var cssSupports = window.CSS && window.CSS.supports;
-    ['auto', 'manipulation', 'pan-y', 'pan-x', 'pan-x pan-y', 'none'].forEach(function(val) {
-
-        // If css.supports is not supported but there is native touch-action assume it supports
-        // all values. This is the case for IE 10 and 11.
-        touchMap[val] = cssSupports ? window.CSS.supports('touch-action', val) : true;
-    });
-    return touchMap;
-}
-
-/**
- * Recognizer flow explained; *
- * All recognizers have the initial state of POSSIBLE when a input session starts.
- * The definition of a input session is from the first input until the last input, with all it's movement in it. *
- * Example session for mouse-input: mousedown -> mousemove -> mouseup
- *
- * On each recognizing cycle (see Manager.recognize) the .recognize() method is executed
- * which determines with state it should be.
- *
- * If the recognizer has the state FAILED, CANCELLED or RECOGNIZED (equals ENDED), it is reset to
- * POSSIBLE to give it another change on the next cycle.
- *
- *               Possible
- *                  |
- *            +-----+---------------+
- *            |                     |
- *      +-----+-----+               |
- *      |           |               |
- *   Failed      Cancelled          |
- *                          +-------+------+
- *                          |              |
- *                      Recognized       Began
- *                                         |
- *                                      Changed
- *                                         |
- *                                  Ended/Recognized
- */
-var STATE_POSSIBLE = 1;
-var STATE_BEGAN = 2;
-var STATE_CHANGED = 4;
-var STATE_ENDED = 8;
-var STATE_RECOGNIZED = STATE_ENDED;
-var STATE_CANCELLED = 16;
-var STATE_FAILED = 32;
-
-/**
- * Recognizer
- * Every recognizer needs to extend from this class.
- * @constructor
- * @param {Object} options
- */
-function Recognizer(options) {
-    this.options = assign({}, this.defaults, options || {});
-
-    this.id = uniqueId();
-
-    this.manager = null;
-
-    // default is enable true
-    this.options.enable = ifUndefined(this.options.enable, true);
-
-    this.state = STATE_POSSIBLE;
-
-    this.simultaneous = {};
-    this.requireFail = [];
-}
-
-Recognizer.prototype = {
-    /**
-     * @virtual
-     * @type {Object}
-     */
-    defaults: {},
-
-    /**
-     * set options
-     * @param {Object} options
-     * @return {Recognizer}
-     */
-    set: function(options) {
-        assign(this.options, options);
-
-        // also update the touchAction, in case something changed about the directions/enabled state
-        this.manager && this.manager.touchAction.update();
-        return this;
-    },
-
-    /**
-     * recognize simultaneous with an other recognizer.
-     * @param {Recognizer} otherRecognizer
-     * @returns {Recognizer} this
-     */
-    recognizeWith: function(otherRecognizer) {
-        if (invokeArrayArg(otherRecognizer, 'recognizeWith', this)) {
-            return this;
-        }
-
-        var simultaneous = this.simultaneous;
-        otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
-        if (!simultaneous[otherRecognizer.id]) {
-            simultaneous[otherRecognizer.id] = otherRecognizer;
-            otherRecognizer.recognizeWith(this);
-        }
-        return this;
-    },
-
-    /**
-     * drop the simultaneous link. it doesnt remove the link on the other recognizer.
-     * @param {Recognizer} otherRecognizer
-     * @returns {Recognizer} this
-     */
-    dropRecognizeWith: function(otherRecognizer) {
-        if (invokeArrayArg(otherRecognizer, 'dropRecognizeWith', this)) {
-            return this;
-        }
-
-        otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
-        delete this.simultaneous[otherRecognizer.id];
-        return this;
-    },
-
-    /**
-     * recognizer can only run when an other is failing
-     * @param {Recognizer} otherRecognizer
-     * @returns {Recognizer} this
-     */
-    requireFailure: function(otherRecognizer) {
-        if (invokeArrayArg(otherRecognizer, 'requireFailure', this)) {
-            return this;
-        }
-
-        var requireFail = this.requireFail;
-        otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
-        if (inArray(requireFail, otherRecognizer) === -1) {
-            requireFail.push(otherRecognizer);
-            otherRecognizer.requireFailure(this);
-        }
-        return this;
-    },
-
-    /**
-     * drop the requireFailure link. it does not remove the link on the other recognizer.
-     * @param {Recognizer} otherRecognizer
-     * @returns {Recognizer} this
-     */
-    dropRequireFailure: function(otherRecognizer) {
-        if (invokeArrayArg(otherRecognizer, 'dropRequireFailure', this)) {
-            return this;
-        }
-
-        otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
-        var index = inArray(this.requireFail, otherRecognizer);
-        if (index > -1) {
-            this.requireFail.splice(index, 1);
-        }
-        return this;
-    },
-
-    /**
-     * has require failures boolean
-     * @returns {boolean}
-     */
-    hasRequireFailures: function() {
-        return this.requireFail.length > 0;
-    },
-
-    /**
-     * if the recognizer can recognize simultaneous with an other recognizer
-     * @param {Recognizer} otherRecognizer
-     * @returns {Boolean}
-     */
-    canRecognizeWith: function(otherRecognizer) {
-        return !!this.simultaneous[otherRecognizer.id];
-    },
-
-    /**
-     * You should use `tryEmit` instead of `emit` directly to check
-     * that all the needed recognizers has failed before emitting.
-     * @param {Object} input
-     */
-    emit: function(input) {
-        var self = this;
-        var state = this.state;
-
-        function emit(event) {
-            self.manager.emit(event, input);
-        }
-
-        // 'panstart' and 'panmove'
-        if (state < STATE_ENDED) {
-            emit(self.options.event + stateStr(state));
-        }
-
-        emit(self.options.event); // simple 'eventName' events
-
-        if (input.additionalEvent) { // additional event(panleft, panright, pinchin, pinchout...)
-            emit(input.additionalEvent);
-        }
-
-        // panend and pancancel
-        if (state >= STATE_ENDED) {
-            emit(self.options.event + stateStr(state));
-        }
-    },
-
-    /**
-     * Check that all the require failure recognizers has failed,
-     * if true, it emits a gesture event,
-     * otherwise, setup the state to FAILED.
-     * @param {Object} input
-     */
-    tryEmit: function(input) {
-        if (this.canEmit()) {
-            return this.emit(input);
-        }
-        // it's failing anyway
-        this.state = STATE_FAILED;
-    },
-
-    /**
-     * can we emit?
-     * @returns {boolean}
-     */
-    canEmit: function() {
-        var i = 0;
-        while (i < this.requireFail.length) {
-            if (!(this.requireFail[i].state & (STATE_FAILED | STATE_POSSIBLE))) {
-                return false;
-            }
-            i++;
-        }
-        return true;
-    },
-
-    /**
-     * update the recognizer
-     * @param {Object} inputData
-     */
-    recognize: function(inputData) {
-        // make a new copy of the inputData
-        // so we can change the inputData without messing up the other recognizers
-        var inputDataClone = assign({}, inputData);
-
-        // is is enabled and allow recognizing?
-        if (!boolOrFn(this.options.enable, [this, inputDataClone])) {
-            this.reset();
-            this.state = STATE_FAILED;
-            return;
-        }
-
-        // reset when we've reached the end
-        if (this.state & (STATE_RECOGNIZED | STATE_CANCELLED | STATE_FAILED)) {
-            this.state = STATE_POSSIBLE;
-        }
-
-        this.state = this.process(inputDataClone);
-
-        // the recognizer has recognized a gesture
-        // so trigger an event
-        if (this.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED | STATE_CANCELLED)) {
-            this.tryEmit(inputDataClone);
-        }
-    },
-
-    /**
-     * return the state of the recognizer
-     * the actual recognizing happens in this method
-     * @virtual
-     * @param {Object} inputData
-     * @returns {Const} STATE
-     */
-    process: function(inputData) { }, // jshint ignore:line
-
-    /**
-     * return the preferred touch-action
-     * @virtual
-     * @returns {Array}
-     */
-    getTouchAction: function() { },
-
-    /**
-     * called when the gesture isn't allowed to recognize
-     * like when another is being recognized or it is disabled
-     * @virtual
-     */
-    reset: function() { }
-};
-
-/**
- * get a usable string, used as event postfix
- * @param {Const} state
- * @returns {String} state
- */
-function stateStr(state) {
-    if (state & STATE_CANCELLED) {
-        return 'cancel';
-    } else if (state & STATE_ENDED) {
-        return 'end';
-    } else if (state & STATE_CHANGED) {
-        return 'move';
-    } else if (state & STATE_BEGAN) {
-        return 'start';
-    }
-    return '';
-}
-
-/**
- * direction cons to string
- * @param {Const} direction
- * @returns {String}
- */
-function directionStr(direction) {
-    if (direction == DIRECTION_DOWN) {
-        return 'down';
-    } else if (direction == DIRECTION_UP) {
-        return 'up';
-    } else if (direction == DIRECTION_LEFT) {
-        return 'left';
-    } else if (direction == DIRECTION_RIGHT) {
-        return 'right';
-    }
-    return '';
-}
-
-/**
- * get a recognizer by name if it is bound to a manager
- * @param {Recognizer|String} otherRecognizer
- * @param {Recognizer} recognizer
- * @returns {Recognizer}
- */
-function getRecognizerByNameIfManager(otherRecognizer, recognizer) {
-    var manager = recognizer.manager;
-    if (manager) {
-        return manager.get(otherRecognizer);
-    }
-    return otherRecognizer;
-}
-
-/**
- * This recognizer is just used as a base for the simple attribute recognizers.
- * @constructor
- * @extends Recognizer
- */
-function AttrRecognizer() {
-    Recognizer.apply(this, arguments);
-}
-
-inherit(AttrRecognizer, Recognizer, {
-    /**
-     * @namespace
-     * @memberof AttrRecognizer
-     */
-    defaults: {
-        /**
-         * @type {Number}
-         * @default 1
-         */
-        pointers: 1
-    },
-
-    /**
-     * Used to check if it the recognizer receives valid input, like input.distance > 10.
-     * @memberof AttrRecognizer
-     * @param {Object} input
-     * @returns {Boolean} recognized
-     */
-    attrTest: function(input) {
-        var optionPointers = this.options.pointers;
-        return optionPointers === 0 || input.pointers.length === optionPointers;
-    },
-
-    /**
-     * Process the input and return the state for the recognizer
-     * @memberof AttrRecognizer
-     * @param {Object} input
-     * @returns {*} State
-     */
-    process: function(input) {
-        var state = this.state;
-        var eventType = input.eventType;
-
-        var isRecognized = state & (STATE_BEGAN | STATE_CHANGED);
-        var isValid = this.attrTest(input);
-
-        // on cancel input and we've recognized before, return STATE_CANCELLED
-        if (isRecognized && (eventType & INPUT_CANCEL || !isValid)) {
-            return state | STATE_CANCELLED;
-        } else if (isRecognized || isValid) {
-            if (eventType & INPUT_END) {
-                return state | STATE_ENDED;
-            } else if (!(state & STATE_BEGAN)) {
-                return STATE_BEGAN;
-            }
-            return state | STATE_CHANGED;
-        }
-        return STATE_FAILED;
-    }
-});
-
-/**
- * Pan
- * Recognized when the pointer is down and moved in the allowed direction.
- * @constructor
- * @extends AttrRecognizer
- */
-function PanRecognizer() {
-    AttrRecognizer.apply(this, arguments);
-
-    this.pX = null;
-    this.pY = null;
-}
-
-inherit(PanRecognizer, AttrRecognizer, {
-    /**
-     * @namespace
-     * @memberof PanRecognizer
-     */
-    defaults: {
-        event: 'pan',
-        threshold: 10,
-        pointers: 1,
-        direction: DIRECTION_ALL
-    },
-
-    getTouchAction: function() {
-        var direction = this.options.direction;
-        var actions = [];
-        if (direction & DIRECTION_HORIZONTAL) {
-            actions.push(TOUCH_ACTION_PAN_Y);
-        }
-        if (direction & DIRECTION_VERTICAL) {
-            actions.push(TOUCH_ACTION_PAN_X);
-        }
-        return actions;
-    },
-
-    directionTest: function(input) {
-        var options = this.options;
-        var hasMoved = true;
-        var distance = input.distance;
-        var direction = input.direction;
-        var x = input.deltaX;
-        var y = input.deltaY;
-
-        // lock to axis?
-        if (!(direction & options.direction)) {
-            if (options.direction & DIRECTION_HORIZONTAL) {
-                direction = (x === 0) ? DIRECTION_NONE : (x < 0) ? DIRECTION_LEFT : DIRECTION_RIGHT;
-                hasMoved = x != this.pX;
-                distance = Math.abs(input.deltaX);
-            } else {
-                direction = (y === 0) ? DIRECTION_NONE : (y < 0) ? DIRECTION_UP : DIRECTION_DOWN;
-                hasMoved = y != this.pY;
-                distance = Math.abs(input.deltaY);
-            }
-        }
-        input.direction = direction;
-        return hasMoved && distance > options.threshold && direction & options.direction;
-    },
-
-    attrTest: function(input) {
-        return AttrRecognizer.prototype.attrTest.call(this, input) &&
-            (this.state & STATE_BEGAN || (!(this.state & STATE_BEGAN) && this.directionTest(input)));
-    },
-
-    emit: function(input) {
-
-        this.pX = input.deltaX;
-        this.pY = input.deltaY;
-
-        var direction = directionStr(input.direction);
-
-        if (direction) {
-            input.additionalEvent = this.options.event + direction;
-        }
-        this._super.emit.call(this, input);
-    }
-});
-
-/**
- * Pinch
- * Recognized when two or more pointers are moving toward (zoom-in) or away from each other (zoom-out).
- * @constructor
- * @extends AttrRecognizer
- */
-function PinchRecognizer() {
-    AttrRecognizer.apply(this, arguments);
-}
-
-inherit(PinchRecognizer, AttrRecognizer, {
-    /**
-     * @namespace
-     * @memberof PinchRecognizer
-     */
-    defaults: {
-        event: 'pinch',
-        threshold: 0,
-        pointers: 2
-    },
-
-    getTouchAction: function() {
-        return [TOUCH_ACTION_NONE];
-    },
-
-    attrTest: function(input) {
-        return this._super.attrTest.call(this, input) &&
-            (Math.abs(input.scale - 1) > this.options.threshold || this.state & STATE_BEGAN);
-    },
-
-    emit: function(input) {
-        if (input.scale !== 1) {
-            var inOut = input.scale < 1 ? 'in' : 'out';
-            input.additionalEvent = this.options.event + inOut;
-        }
-        this._super.emit.call(this, input);
-    }
-});
-
-/**
- * Press
- * Recognized when the pointer is down for x ms without any movement.
- * @constructor
- * @extends Recognizer
- */
-function PressRecognizer() {
-    Recognizer.apply(this, arguments);
-
-    this._timer = null;
-    this._input = null;
-}
-
-inherit(PressRecognizer, Recognizer, {
-    /**
-     * @namespace
-     * @memberof PressRecognizer
-     */
-    defaults: {
-        event: 'press',
-        pointers: 1,
-        time: 251, // minimal time of the pointer to be pressed
-        threshold: 9 // a minimal movement is ok, but keep it low
-    },
-
-    getTouchAction: function() {
-        return [TOUCH_ACTION_AUTO];
-    },
-
-    process: function(input) {
-        var options = this.options;
-        var validPointers = input.pointers.length === options.pointers;
-        var validMovement = input.distance < options.threshold;
-        var validTime = input.deltaTime > options.time;
-
-        this._input = input;
-
-        // we only allow little movement
-        // and we've reached an end event, so a tap is possible
-        if (!validMovement || !validPointers || (input.eventType & (INPUT_END | INPUT_CANCEL) && !validTime)) {
-            this.reset();
-        } else if (input.eventType & INPUT_START) {
-            this.reset();
-            this._timer = setTimeoutContext(function() {
-                this.state = STATE_RECOGNIZED;
-                this.tryEmit();
-            }, options.time, this);
-        } else if (input.eventType & INPUT_END) {
-            return STATE_RECOGNIZED;
-        }
-        return STATE_FAILED;
-    },
-
-    reset: function() {
-        clearTimeout(this._timer);
-    },
-
-    emit: function(input) {
-        if (this.state !== STATE_RECOGNIZED) {
-            return;
-        }
-
-        if (input && (input.eventType & INPUT_END)) {
-            this.manager.emit(this.options.event + 'up', input);
-        } else {
-            this._input.timeStamp = now();
-            this.manager.emit(this.options.event, this._input);
-        }
-    }
-});
-
-/**
- * Rotate
- * Recognized when two or more pointer are moving in a circular motion.
- * @constructor
- * @extends AttrRecognizer
- */
-function RotateRecognizer() {
-    AttrRecognizer.apply(this, arguments);
-}
-
-inherit(RotateRecognizer, AttrRecognizer, {
-    /**
-     * @namespace
-     * @memberof RotateRecognizer
-     */
-    defaults: {
-        event: 'rotate',
-        threshold: 0,
-        pointers: 2
-    },
-
-    getTouchAction: function() {
-        return [TOUCH_ACTION_NONE];
-    },
-
-    attrTest: function(input) {
-        return this._super.attrTest.call(this, input) &&
-            (Math.abs(input.rotation) > this.options.threshold || this.state & STATE_BEGAN);
-    }
-});
-
-/**
- * Swipe
- * Recognized when the pointer is moving fast (velocity), with enough distance in the allowed direction.
- * @constructor
- * @extends AttrRecognizer
- */
-function SwipeRecognizer() {
-    AttrRecognizer.apply(this, arguments);
-}
-
-inherit(SwipeRecognizer, AttrRecognizer, {
-    /**
-     * @namespace
-     * @memberof SwipeRecognizer
-     */
-    defaults: {
-        event: 'swipe',
-        threshold: 10,
-        velocity: 0.3,
-        direction: DIRECTION_HORIZONTAL | DIRECTION_VERTICAL,
-        pointers: 1
-    },
-
-    getTouchAction: function() {
-        return PanRecognizer.prototype.getTouchAction.call(this);
-    },
-
-    attrTest: function(input) {
-        var direction = this.options.direction;
-        var velocity;
-
-        if (direction & (DIRECTION_HORIZONTAL | DIRECTION_VERTICAL)) {
-            velocity = input.overallVelocity;
-        } else if (direction & DIRECTION_HORIZONTAL) {
-            velocity = input.overallVelocityX;
-        } else if (direction & DIRECTION_VERTICAL) {
-            velocity = input.overallVelocityY;
-        }
-
-        return this._super.attrTest.call(this, input) &&
-            direction & input.offsetDirection &&
-            input.distance > this.options.threshold &&
-            input.maxPointers == this.options.pointers &&
-            abs(velocity) > this.options.velocity && input.eventType & INPUT_END;
-    },
-
-    emit: function(input) {
-        var direction = directionStr(input.offsetDirection);
-        if (direction) {
-            this.manager.emit(this.options.event + direction, input);
-        }
-
-        this.manager.emit(this.options.event, input);
-    }
-});
-
-/**
- * A tap is ecognized when the pointer is doing a small tap/click. Multiple taps are recognized if they occur
- * between the given interval and position. The delay option can be used to recognize multi-taps without firing
- * a single tap.
- *
- * The eventData from the emitted event contains the property `tapCount`, which contains the amount of
- * multi-taps being recognized.
- * @constructor
- * @extends Recognizer
- */
-function TapRecognizer() {
-    Recognizer.apply(this, arguments);
-
-    // previous time and center,
-    // used for tap counting
-    this.pTime = false;
-    this.pCenter = false;
-
-    this._timer = null;
-    this._input = null;
-    this.count = 0;
-}
-
-inherit(TapRecognizer, Recognizer, {
-    /**
-     * @namespace
-     * @memberof PinchRecognizer
-     */
-    defaults: {
-        event: 'tap',
-        pointers: 1,
-        taps: 1,
-        interval: 300, // max time between the multi-tap taps
-        time: 250, // max time of the pointer to be down (like finger on the screen)
-        threshold: 9, // a minimal movement is ok, but keep it low
-        posThreshold: 10 // a multi-tap can be a bit off the initial position
-    },
-
-    getTouchAction: function() {
-        return [TOUCH_ACTION_MANIPULATION];
-    },
-
-    process: function(input) {
-        var options = this.options;
-
-        var validPointers = input.pointers.length === options.pointers;
-        var validMovement = input.distance < options.threshold;
-        var validTouchTime = input.deltaTime < options.time;
-
-        this.reset();
-
-        if ((input.eventType & INPUT_START) && (this.count === 0)) {
-            return this.failTimeout();
-        }
-
-        // we only allow little movement
-        // and we've reached an end event, so a tap is possible
-        if (validMovement && validTouchTime && validPointers) {
-            if (input.eventType != INPUT_END) {
-                return this.failTimeout();
-            }
-
-            var validInterval = this.pTime ? (input.timeStamp - this.pTime < options.interval) : true;
-            var validMultiTap = !this.pCenter || getDistance(this.pCenter, input.center) < options.posThreshold;
-
-            this.pTime = input.timeStamp;
-            this.pCenter = input.center;
-
-            if (!validMultiTap || !validInterval) {
-                this.count = 1;
-            } else {
-                this.count += 1;
-            }
-
-            this._input = input;
-
-            // if tap count matches we have recognized it,
-            // else it has began recognizing...
-            var tapCount = this.count % options.taps;
-            if (tapCount === 0) {
-                // no failing requirements, immediately trigger the tap event
-                // or wait as long as the multitap interval to trigger
-                if (!this.hasRequireFailures()) {
-                    return STATE_RECOGNIZED;
-                } else {
-                    this._timer = setTimeoutContext(function() {
-                        this.state = STATE_RECOGNIZED;
-                        this.tryEmit();
-                    }, options.interval, this);
-                    return STATE_BEGAN;
-                }
-            }
-        }
-        return STATE_FAILED;
-    },
-
-    failTimeout: function() {
-        this._timer = setTimeoutContext(function() {
-            this.state = STATE_FAILED;
-        }, this.options.interval, this);
-        return STATE_FAILED;
-    },
-
-    reset: function() {
-        clearTimeout(this._timer);
-    },
-
-    emit: function() {
-        if (this.state == STATE_RECOGNIZED) {
-            this._input.tapCount = this.count;
-            this.manager.emit(this.options.event, this._input);
-        }
-    }
-});
-
-/**
- * Simple way to create a manager with a default set of recognizers.
- * @param {HTMLElement} element
- * @param {Object} [options]
- * @constructor
- */
-function NGHammer(element, options) {
-    options = options || {};
-    options.recognizers = ifUndefined(options.recognizers, NGHammer.defaults.preset);
-    return new Manager(element, options);
-}
-
-/**
- * @const {string}
- */
-NGHammer.VERSION = '2.0.7';
-
-/**
- * default settings
- * @namespace
- */
-NGHammer.defaults = {
-    /**
-     * set if DOM events are being triggered.
-     * But this is slower and unused by simple implementations, so disabled by default.
-     * @type {Boolean}
-     * @default false
-     */
-    domEvents: false,
-
-    /**
-     * The value for the touchAction property/fallback.
-     * When set to `compute` it will magically set the correct value based on the added recognizers.
-     * @type {String}
-     * @default compute
-     */
-    touchAction: TOUCH_ACTION_COMPUTE,
-
-    /**
-     * @type {Boolean}
-     * @default true
-     */
-    enable: true,
-
-    /**
-     * EXPERIMENTAL FEATURE -- can be removed/changed
-     * Change the parent input target element.
-     * If Null, then it is being set the to main element.
-     * @type {Null|EventTarget}
-     * @default null
-     */
-    inputTarget: null,
-
-    /**
-     * force an input class
-     * @type {Null|Function}
-     * @default null
-     */
-    inputClass: null,
-
-    /**
-     * Default recognizer setup when calling `NGHammer()`
-     * When creating a new Manager these will be skipped.
-     * @type {Array}
-     */
-    preset: [
-        // RecognizerClass, options, [recognizeWith, ...], [requireFailure, ...]
-        [RotateRecognizer, {enable: false}],
-        [PinchRecognizer, {enable: false}, ['rotate']],
-        [SwipeRecognizer, {direction: DIRECTION_HORIZONTAL}],
-        [PanRecognizer, {direction: DIRECTION_HORIZONTAL}, ['swipe']],
-        [TapRecognizer],
-        [TapRecognizer, {event: 'doubletap', taps: 2}, ['tap']],
-        [PressRecognizer]
-    ],
-
-    /**
-     * Some CSS properties can be used to improve the working of NGHammer.
-     * Add them to this method and they will be set when creating a new Manager.
-     * @namespace
-     */
-    cssProps: {
-        /**
-         * Disables text selection to improve the dragging gesture. Mainly for desktop browsers.
-         * @type {String}
-         * @default 'none'
-         */
-        userSelect: 'none',
-
-        /**
-         * Disable the Windows Phone grippers when pressing an element.
-         * @type {String}
-         * @default 'none'
-         */
-        touchSelect: 'none',
-
-        /**
-         * Disables the default callout shown when you touch and hold a touch target.
-         * On iOS, when you touch and hold a touch target such as a link, Safari displays
-         * a callout containing information about the link. This property allows you to disable that callout.
-         * @type {String}
-         * @default 'none'
-         */
-        touchCallout: 'none',
-
-        /**
-         * Specifies whether zooming is enabled. Used by IE10>
-         * @type {String}
-         * @default 'none'
-         */
-        contentZooming: 'none',
-
-        /**
-         * Specifies that an entire element should be draggable instead of its contents. Mainly for desktop browsers.
-         * @type {String}
-         * @default 'none'
-         */
-        userDrag: 'none',
-
-        /**
-         * Overrides the highlight color shown when the user taps a link or a JavaScript
-         * clickable element in iOS. This property obeys the alpha value, if specified.
-         * @type {String}
-         * @default 'rgba(0,0,0,0)'
-         */
-        tapHighlightColor: 'rgba(0,0,0,0)'
-    }
-};
-
-var STOP = 1;
-var FORCED_STOP = 2;
-
-/**
- * Manager
- * @param {HTMLElement} element
- * @param {Object} [options]
- * @constructor
- */
-function Manager(element, options) {
-    this.options = assign({}, NGHammer.defaults, options || {});
-
-    this.options.inputTarget = this.options.inputTarget || element;
-
-    this.handlers = {};
-    this.session = {};
-    this.recognizers = [];
-    this.oldCssProps = {};
-
-    this.element = element;
-    this.input = createInputInstance(this);
-    this.touchAction = new TouchAction(this, this.options.touchAction);
-
-    toggleCssProps(this, true);
-
-    each(this.options.recognizers, function(item) {
-        var recognizer = this.add(new (item[0])(item[1]));
-        item[2] && recognizer.recognizeWith(item[2]);
-        item[3] && recognizer.requireFailure(item[3]);
-    }, this);
-}
-
-Manager.prototype = {
-    /**
-     * set options
-     * @param {Object} options
-     * @returns {Manager}
-     */
-    set: function(options) {
-        assign(this.options, options);
-
-        // Options that need a little more setup
-        if (options.touchAction) {
-            this.touchAction.update();
-        }
-        if (options.inputTarget) {
-            // Clean up existing event listeners and reinitialize
-            this.input.destroy();
-            this.input.target = options.inputTarget;
-            this.input.init();
-        }
-        return this;
-    },
-
-    /**
-     * stop recognizing for this session.
-     * This session will be discarded, when a new [input]start event is fired.
-     * When forced, the recognizer cycle is stopped immediately.
-     * @param {Boolean} [force]
-     */
-    stop: function(force) {
-        this.session.stopped = force ? FORCED_STOP : STOP;
-    },
-
-    /**
-     * run the recognizers!
-     * called by the inputHandler function on every movement of the pointers (touches)
-     * it walks through all the recognizers and tries to detect the gesture that is being made
-     * @param {Object} inputData
-     */
-    recognize: function(inputData) {
-        var session = this.session;
-        if (session.stopped) {
-            return;
-        }
-
-        // run the touch-action polyfill
-        this.touchAction.preventDefaults(inputData);
-
-        var recognizer;
-        var recognizers = this.recognizers;
-
-        // this holds the recognizer that is being recognized.
-        // so the recognizer's state needs to be BEGAN, CHANGED, ENDED or RECOGNIZED
-        // if no recognizer is detecting a thing, it is set to `null`
-        var curRecognizer = session.curRecognizer;
-
-        // reset when the last recognizer is recognized
-        // or when we're in a new session
-        if (!curRecognizer || (curRecognizer && curRecognizer.state & STATE_RECOGNIZED)) {
-            curRecognizer = session.curRecognizer = null;
-        }
-
-        var i = 0;
-        while (i < recognizers.length) {
-            recognizer = recognizers[i];
-
-            // find out if we are allowed try to recognize the input for this one.
-            // 1.   allow if the session is NOT forced stopped (see the .stop() method)
-            // 2.   allow if we still haven't recognized a gesture in this session, or the this recognizer is the one
-            //      that is being recognized.
-            // 3.   allow if the recognizer is allowed to run simultaneous with the current recognized recognizer.
-            //      this can be setup with the `recognizeWith()` method on the recognizer.
-            if (session.stopped !== FORCED_STOP && ( // 1
-                    !curRecognizer || recognizer == curRecognizer || // 2
-                    recognizer.canRecognizeWith(curRecognizer))) { // 3
-                recognizer.recognize(inputData);
-            } else {
-                recognizer.reset();
-            }
-
-            // if the recognizer has been recognizing the input as a valid gesture, we want to store this one as the
-            // current active recognizer. but only if we don't already have an active recognizer
-            if (!curRecognizer && recognizer.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED)) {
-                curRecognizer = session.curRecognizer = recognizer;
-            }
-            i++;
-        }
-    },
-
-    /**
-     * get a recognizer by its event name.
-     * @param {Recognizer|String} recognizer
-     * @returns {Recognizer|Null}
-     */
-    get: function(recognizer) {
-        if (recognizer instanceof Recognizer) {
-            return recognizer;
-        }
-
-        var recognizers = this.recognizers;
-        for (var i = 0; i < recognizers.length; i++) {
-            if (recognizers[i].options.event == recognizer) {
-                return recognizers[i];
-            }
-        }
-        return null;
-    },
-
-    /**
-     * add a recognizer to the manager
-     * existing recognizers with the same event name will be removed
-     * @param {Recognizer} recognizer
-     * @returns {Recognizer|Manager}
-     */
-    add: function(recognizer) {
-        if (invokeArrayArg(recognizer, 'add', this)) {
-            return this;
-        }
-
-        // remove existing
-        var existing = this.get(recognizer.options.event);
-        if (existing) {
-            this.remove(existing);
-        }
-
-        this.recognizers.push(recognizer);
-        recognizer.manager = this;
-
-        this.touchAction.update();
-        return recognizer;
-    },
-
-    /**
-     * remove a recognizer by name or instance
-     * @param {Recognizer|String} recognizer
-     * @returns {Manager}
-     */
-    remove: function(recognizer) {
-        if (invokeArrayArg(recognizer, 'remove', this)) {
-            return this;
-        }
-
-        recognizer = this.get(recognizer);
-
-        // let's make sure this recognizer exists
-        if (recognizer) {
-            var recognizers = this.recognizers;
-            var index = inArray(recognizers, recognizer);
-
-            if (index !== -1) {
-                recognizers.splice(index, 1);
-                this.touchAction.update();
-            }
-        }
-
-        return this;
-    },
-
-    /**
-     * bind event
-     * @param {String} events
-     * @param {Function} handler
-     * @returns {EventEmitter} this
-     */
-    on: function(events, handler) {
-        if (events === undefined) {
-            return;
-        }
-        if (handler === undefined) {
-            return;
-        }
-
-        var handlers = this.handlers;
-        each(splitStr(events), function(event) {
-            handlers[event] = handlers[event] || [];
-            handlers[event].push(handler);
-        });
-        return this;
-    },
-
-    /**
-     * unbind event, leave emit blank to remove all handlers
-     * @param {String} events
-     * @param {Function} [handler]
-     * @returns {EventEmitter} this
-     */
-    off: function(events, handler) {
-        if (events === undefined) {
-            return;
-        }
-
-        var handlers = this.handlers;
-        each(splitStr(events), function(event) {
-            if (!handler) {
-                delete handlers[event];
-            } else {
-                handlers[event] && handlers[event].splice(inArray(handlers[event], handler), 1);
-            }
-        });
-        return this;
-    },
-
-    /**
-     * emit event to the listeners
-     * @param {String} event
-     * @param {Object} data
-     */
-    emit: function(event, data) {
-        // we also want to trigger dom events
-        if (this.options.domEvents) {
-            triggerDomEvent(event, data);
-        }
-
-        // no handlers, so skip it all
-        var handlers = this.handlers[event] && this.handlers[event].slice();
-        if (!handlers || !handlers.length) {
-            return;
-        }
-
-        data.type = event;
-        data.preventDefault = function() {
-            data.srcEvent.preventDefault();
-        };
-
-        var i = 0;
-        while (i < handlers.length) {
-            handlers[i](data);
-            i++;
-        }
-    },
-
-    /**
-     * destroy the manager and unbinds all events
-     * it doesn't unbind dom events, that is the user own responsibility
-     */
-    destroy: function() {
-        this.element && toggleCssProps(this, false);
-
-        this.handlers = {};
-        this.session = {};
-        this.input.destroy();
-        this.element = null;
-    }
-};
-
-/**
- * add/remove the css properties as defined in manager.options.cssProps
- * @param {Manager} manager
- * @param {Boolean} add
- */
-function toggleCssProps(manager, add) {
-    var element = manager.element;
-    if (!element.style) {
-        return;
-    }
-    var prop;
-    each(manager.options.cssProps, function(value, name) {
-        prop = prefixed(element.style, name);
-        if (add) {
-            manager.oldCssProps[prop] = element.style[prop];
-            element.style[prop] = value;
-        } else {
-            element.style[prop] = manager.oldCssProps[prop] || '';
-        }
-    });
-    if (!add) {
-        manager.oldCssProps = {};
-    }
-}
-
-/**
- * trigger dom event
- * @param {String} event
- * @param {Object} data
- */
-function triggerDomEvent(event, data) {
-    var gestureEvent = document.createEvent('Event');
-    gestureEvent.initEvent(event, true, true);
-    gestureEvent.gesture = data;
-    data.target.dispatchEvent(gestureEvent);
-}
-
-assign(NGHammer, {
-    INPUT_START: INPUT_START,
-    INPUT_MOVE: INPUT_MOVE,
-    INPUT_END: INPUT_END,
-    INPUT_CANCEL: INPUT_CANCEL,
-
-    STATE_POSSIBLE: STATE_POSSIBLE,
-    STATE_BEGAN: STATE_BEGAN,
-    STATE_CHANGED: STATE_CHANGED,
-    STATE_ENDED: STATE_ENDED,
-    STATE_RECOGNIZED: STATE_RECOGNIZED,
-    STATE_CANCELLED: STATE_CANCELLED,
-    STATE_FAILED: STATE_FAILED,
-
-    DIRECTION_NONE: DIRECTION_NONE,
-    DIRECTION_LEFT: DIRECTION_LEFT,
-    DIRECTION_RIGHT: DIRECTION_RIGHT,
-    DIRECTION_UP: DIRECTION_UP,
-    DIRECTION_DOWN: DIRECTION_DOWN,
-    DIRECTION_HORIZONTAL: DIRECTION_HORIZONTAL,
-    DIRECTION_VERTICAL: DIRECTION_VERTICAL,
-    DIRECTION_ALL: DIRECTION_ALL,
-
-    Manager: Manager,
-    Input: Input,
-    TouchAction: TouchAction,
-
-    TouchInput: TouchInput,
-    MouseInput: MouseInput,
-    PointerEventInput: PointerEventInput,
-    TouchMouseInput: TouchMouseInput,
-    SingleTouchInput: SingleTouchInput,
-
-    Recognizer: Recognizer,
-    AttrRecognizer: AttrRecognizer,
-    Tap: TapRecognizer,
-    Pan: PanRecognizer,
-    Swipe: SwipeRecognizer,
-    Pinch: PinchRecognizer,
-    Rotate: RotateRecognizer,
-    Press: PressRecognizer,
-
-    on: addEventListeners,
-    off: removeEventListeners,
-    each: each,
-    merge: merge,
-    extend: extend,
-    assign: assign,
-    inherit: inherit,
-    bindFn: bindFn,
-    prefixed: prefixed
-});
-
-// this prevents errors when NGHammer is loaded in the presence of an AMD
-//  style loader but by script tag, not by the loader.
-var freeGlobal = (typeof window !== 'undefined' ? window : (typeof self !== 'undefined' ? self : {})); // jshint ignore:line
-freeGlobal.NGHammer = NGHammer;
-
-if (typeof define === 'function' && define.amdDISABLED) {
-    define(function() {
-        return NGHammer;
-    });
-} else if (typeof module != 'undefined' && module.exports) {
-    module.exports = NGHammer;
-} else {
-    window[exportName] = NGHammer;
-}
-
-})(window, document, 'NGHammer');
-
-
-
-
-
-
-// END NANOGALLERY2
-// }( jQuery )));
-}));
-
-
-//##########################################################################################################################
-//##########################################################################################################################
-//##########################################################################################################################
-//##########################################################################################################################
-//##########################################################################################################################
-
-// nanogallery2 auto start whithout javascript call
-(function(){
-  'use strict';
-	
-	function document_ready(callback){
-		// in case the document is already rendered
-		if (document.readyState!='loading') callback();
-		// modern browsers
-		else if (document.addEventListener) document.addEventListener('DOMContentLoaded', callback);
-		// IE <= 8
-		else document.attachEvent('onreadystatechange', function(){
-				if (document.readyState=='complete') callback();
-		});
-	}
-
-	document_ready(function(){
-		
-		// retrieve GALLERIES
-		var t=document.querySelectorAll('[data-nanogallery2]');
-		for( var i=0; i < t.length; i++ ) {
-			jQuery( t[i] ).nanogallery2( jQuery(t[i]).data('nanogallery2') );
-		}
-		
-		// retrieve SINGLE ELEMENTS -> ONLY LIGHTBOX / NO GALLERY
-		var t = document.querySelectorAll('[data-nanogallery2-lightbox]');
-		for( var i=0; i < t.length; i++ ) {
-			
-      // set mouse pointer
-      t[i].classList.add('NGY2ThumbnailLightbox');
-
-      // add click event
-      t[i].addEventListener('click', function(e) {
-        // disable link tag if A element
-        e.preventDefault();
-      
-        // default options for standalone lightbox
-        var options = { 
-          lightboxStandalone: true,
-          viewerToolbar: { display: false }
-        };
-
-        // group of images
-        var g = this.dataset.nanogallery2Lgroup;
-
-        // Retrieve the lightbox configuration
-        // it just need to be defined on one of the elements, which will be displayed in the lightbox
-        var t = document.querySelectorAll('[data-nanogallery2-lightbox]');
-        for( var i=0; i < t.length; i++ ) {
-          if( t[i].dataset.nanogallery2Lgroup == g ) {
-            if( t[i].dataset.nanogallery2Lightbox !== "" ) {
-              options = jQuery.extend(true, {}, options, jQuery(t[i]).data('nanogallery2Lightbox'));
-              break;
-            }
-          }
-        }
-        jQuery( this ).nanogallery2( options );
-
-      });
-
-		}
-	});
-	
-	
-	
-  // jQuery(document).ready(function () {
-  
-    // var t=document.querySelectorAll('[data-nanogallery2-portable]');
-    // if( t.length > 0 ) {
-      // portable mode
-      // var link = document.createElement('link');
-      // link.setAttribute("rel", "stylesheet");
-      // link.setAttribute("type", "text/css");
-      // link.onload = function(){
-        // for( var i=0; i < t.length; i++ ) {
-          // jQuery(t[i]).nanogallery2(jQuery(t[i]).data('nanogallery2-portable'));
-        // }
-      // }
-      // link.setAttribute("href", '//nano.gallery/css/nanogallery2.css');
-      // document.getElementsByTagName("head")[0].appendChild(link);
-    // }
-    // else {
-      // standard mode
-			
-			// GALLERIES
-      // var t=document.querySelectorAll('[data-nanogallery2]');
-      // for( var i=0; i < t.length; i++ ) {
-        // jQuery( t[i] ).nanogallery2( jQuery(t[i]).data('nanogallery2') );
-      // }
-			
-		
-    // }
-    
-  // });
-}).call(null);
-
+              att.t.isDisplayed=false;
+            }
+          });
+        }
+      }
+    }
+    
+    
+    // Color schemes - Gallery
+    // Gradient generator: https://www.grabient.com/
+    G.galleryTheme_dark = {
+      navigationBar :         { background: 'none', borderTop: '', borderBottom: '', borderRight: '', borderLeft: '' },
+      navigationBreadcrumb :  { background: '#111', color: '#fff', colorHover: '#ccc', borderRadius: '4px' },
+      navigationFilter :      { color: '#ddd', background: '#111', colorSelected: '#fff', backgroundSelected: '#111', borderRadius: '4px' },
+      navigationPagination :  { background: '#111', color: '#fff', colorHover: '#ccc', borderRadius: '4px' },
+      thumbnail :             { background: '#444', backgroundImage: 'linear-gradient(315deg, #111 0%, #445 90%)', borderColor: '#000', borderRadius: '0px', labelOpacity : 1, labelBackground: 'rgba(34, 34, 34, 0)', titleColor: '#fff', titleBgColor: 'transparent', titleShadow: '', descriptionColor: '#ccc', descriptionBgColor: 'transparent', descriptionShadow: '', stackBackground: '#aaa' },
+      thumbnailIcon :         { padding: '5px', color: '#fff', shadow:'' },
+      pagination :            { background: '#181818', backgroundSelected: '#666', color: '#fff', borderRadius: '2px', shapeBorder: '3px solid #666', shapeColor: '#444', shapeSelectedColor: '#aaa'}
+    };
+
+    G.galleryTheme_light = {
+      navigationBar :         { background: 'none', borderTop: '', borderBottom: '', borderRight: '', borderLeft: '' },
+      navigationBreadcrumb :  { background: '#eee', color: '#000', colorHover: '#333', borderRadius: '4px' },
+      navigationFilter :      { background: '#eee', color: '#222', colorSelected: '#000', backgroundSelected: '#eee', borderRadius: '4px' },
+      navigationPagination :  { background: '#eee', color: '#000', colorHover: '#333', borderRadius: '4px' },
+      thumbnail :             { background: '#444', backgroundImage: 'linear-gradient(315deg, #111 0%, #445 90%)', borderColor: '#000', labelOpacity : 1, labelBackground: 'rgba(34, 34, 34, 0)', titleColor: '#fff', titleBgColor: 'transparent', titleShadow: '', descriptionColor: '#ccc', descriptionBgColor: 'transparent', descriptionShadow: '', stackBackground: '#888' },
+      thumbnailIcon :         { padding: '5px', color: '#fff' },
+      pagination :            { background: '#eee', backgroundSelected: '#aaa', color: '#000', borderRadius: '2px', shapeBorder: '3px solid #666', shapeColor: '#444', shapeSelectedColor: '#aaa'}
+    };
+
+    // Color schemes - lightbox
+    G.viewerTheme_dark = {
+      background:             '#000',
+      barBackground:          'rgba(4, 4, 4, 0.2)',
+      barBorder:              '0px solid #111',
+      barColor:               '#fff',
+      barDescriptionColor:    '#ccc'
+    };
+    G.viewerTheme_light = {
+      background:             '#f8f8f8',
+      barBackground:          'rgba(4, 4, 4, 0.7)',
+      barBorder:              '0px solid #111',
+      barColor:               '#fff',
+      barDescriptionColor:    '#ccc'
+    };
+
+      
+    
+    // shortcut with G context to NGY2TOOLS
+    // var NanoAlert =           NGY2Tools.NanoAlert.bind(G);
+    // var NanoConsoleLog =      NGY2Tools.NanoConsoleLog.bind(G);
+    var NanoAlert =           NGY2Tools.NanoAlert;
+    var NanoConsoleLog =      NGY2Tools.NanoConsoleLog;
+
+    
+    /** @function initiateGallery2 */
+    this.initiateGallery2 = function( element, params ) {
+
+      // GLOBAL OPTIONS
+      G.O =               params;
+      // Base element
+      G.$E.base =         jQuery(element);
+      G.baseEltID =       G.$E.base.attr('id');
+      if( G.baseEltID == undefined ) {
+        // set a default ID to the root container
+        var base_id = 'my_nanogallery';
+        var c = '';
+        var f = true;
+        while( f ) {
+          if (document.getElementById(base_id + c)) {
+            // ID already exists
+            if( c == '' ) {
+              c = 1;
+            }
+            else {
+              c++;
+            }
+          }
+          else {
+            f = false;
+            G.baseEltID = 'my_nanogallery' + c;
+          }
+        }
+        G.$E.base.attr('id', G.baseEltID)
+      }
+      G.O.$markup =       [];
+      
+      DefineVariables();
+      SetPolyFills();
+      BuildSkeleton();
+      G.GOM.firstDisplayTime = Date.now();
+      
+      SetGlobalEvents();
+
+      // check if only one specific album will be used
+      if( !G.O.lightboxStandalone ) {
+				var albumToDisplay = G.O.album;
+				if( albumToDisplay == '' && G.O.photoset != '' ) {
+					albumToDisplay = G.O.photoset;
+					G.O.album = G.O.photoset;
+				}
+				if( albumToDisplay != '' ) {
+					G.O.displayBreadcrumb = false;    // no breadcrumb since only 1 album
+					if( albumToDisplay.toUpperCase() != 'NONE' ) {
+						// open a public album
+						if( G.O.kind == "nano_photos_provider2") {
+							if( albumToDisplay == decodeURIComponent(albumToDisplay)) {
+								// album ID must be encoded
+								albumToDisplay = encodeURIComponent(albumToDisplay);
+								G.O.album = albumToDisplay;
+							}
+						}
+						NGY2Item.New( G, '', '', albumToDisplay, '-1', 'album' );
+						if( !ProcessLocationHash() ) {
+							DisplayAlbum('-1', albumToDisplay);
+						}
+						return;
+					}
+				}
+      }
+     
+      // use full content
+      // add base album
+      NGY2Item.New( G, G.i18nTranslations.breadcrumbHome, '', '0', '-1', 'album' );
+
+      processStartOptions();
+
+    }
+
+
+    /** @function processStartOptions */
+    function processStartOptions() {
+      // open image or album
+      // 1. load hidden albums
+      // 2. check if location hash set (deep linking)
+      // 3. check openOnStart parameter
+      // 4. open root album (ID=-1)
+
+      // hidden/private albums are loaded on plugin start (Picasa) --> no more available in Google Photos
+      // if( G.albumListHidden.length > 0 ) {
+      //  jQuery.nanogallery2['data_'+G.O.kind](G, 'GetHiddenAlbums', G.albumListHidden, processStartOptionsPart2);
+      //  return;
+      //}
+      
+      if( !ProcessLocationHash() ) {
+        processStartOptionsPart2();
+      }
+    }
+
+    /** @function processStartOptionsPart2 */
+    function processStartOptionsPart2() {
+  
+      // Check location hash + start parameters -> determine what to do on start
+      if( G.O.lightboxStandalone ) {
+        LightboxStandaloneFindContent();
+			}
+			else {
+				// openOnStart parameter
+				if( G.O.openOnStart != '' ) {
+					var IDs = parseIDs(G.O.openOnStart);
+					if( IDs.imageID != '0' ) {
+						DisplayPhoto(IDs.imageID, IDs.albumID);
+					}
+					else {
+						DisplayAlbum('-1', IDs.albumID);
+					}
+				}
+				else {
+					// open root album (ID = -1)
+					DisplayAlbum('-1', 0);
+				}
+      }
+    }
+    
+    
+    // Lightbox standaone -> retrieve the items to display
+    // Each item needs at least a thumbnail image and a big image
+    // ONLY IMAGES SUPPORTED
+    function LightboxStandaloneFindContent() {
+
+      G.GOM.curNavLevel = 'l1';
+      
+      // retrieve all element having "data-nanogallery2-lightbox" and from the same group if defined
+      var elts = jQuery('[data-nanogallery2-Lightbox');
+      
+      // element group
+      var g = G.$E.base[0].dataset.nanogallery2Lgroup;
+
+      GetContentMarkup( elts, g );
+
+      LightboxStandaloneDisplay();
+        
+    }
+    
+    
+    // Populate G.VOM.items + open the lightbox
+    function LightboxStandaloneDisplay() {
+
+      G.VOM.items = [];
+      G.VOM.albumID = '0';
+      G.GOM.curNavLevel = 'l1';
+      var vcnt = 0;
+
+      var srct = G.$E.base[0].src;
+      var displayIdx = undefined;
+      for( var idx = 0; idx < G.I.length; idx++ ) {
+        if( G.I[idx].kind == 'image' ) {
+          var vimg = new VImg(idx);
+          G.VOM.items.push(vimg);
+
+          if( G.I[idx].thumbImg().src == srct ) {
+            // same thumbnail URL
+            displayIdx = vcnt;
+          }
+          vcnt++;
+        }
+        
+      }
+      if( G.VOM.items.length > 0 ) {
+        LightboxOpen( displayIdx );
+      }
+      else {
+        NanoConsoleLog(G, 'No content for Lightbox standalone.');
+      }
+    }
+   
+    
+    
+    // Parse string to extract albumID and imageID (format albumID/imageID)
+    function parseIDs( IDs ) {
+      var r = { albumID: '0', imageID: '0' };
+      
+      var t = IDs.split('/');
+      if( t.length > 0 ) {
+        r.albumID = t[0];
+        if( t.length > 1 ) {
+          r.imageID = t[1];
+        }
+      }
+      return r;
+    }
+    
+
+    /** @function DisplayAlbum */
+    function DisplayAlbum( imageID, albumID ) {
+      // close viewer if already displayed
+      if( G.VOM.viewerDisplayed ) {
+        LightboxClose(null);
+      }
+    
+      // set current navigation level (l1 or lN)
+      var albumIdx = NGY2Item.GetIdx(G, albumID);
+      G.GOM.curNavLevel = 'lN';
+      if( albumIdx == 0 ) {
+        G.GOM.curNavLevel = 'l1';
+      }
+      G.layout.SetEngine();
+      G.galleryResizeEventEnabled = false;
+
+      if( albumIdx == -1 ) {
+        NGY2Item.New( G, '', '', albumID, '0', 'album' );    // create empty album
+        albumIdx = G.I.length - 1;
+      }
+    
+      if( !G.I[albumIdx].contentIsLoaded ) {
+        // get content of the album if not already loaded
+        AlbumGetContent( albumID, DisplayAlbum, imageID, albumID );
+        return;
+      }
+    
+      ThumbnailSelectionClear();
+    
+      G.GOM.pagination.currentPage = 0;
+      SetLocationHash( albumID, '' );
+      GalleryRender( albumIdx );
+    
+    }
+
+
+    //----- manage the bottom area of the gallery -> "pagination" or "more button"
+    function GalleryBottomManage() {
+
+      switch( G.galleryDisplayMode.Get() ) {
+        case 'PAGINATION':
+          if( G.layout.support.rows && G.galleryMaxRows.Get() > 0 ) {
+            ManagePagination( G.GOM.albumIdx );
+          }
+          break;
+        case 'MOREBUTTON':
+          G.$E.conTnBottom.off('click');
+          var nb = G.GOM.items.length-G.GOM.itemsDisplayed;
+          if( nb == 0 ) {
+            G.$E.conTnBottom.empty();
+          }
+          else {
+            G.$E.conTnBottom.html('<div class="nGY2GalleryMoreButton"><div class="nGY2GalleryMoreButtonAnnotation">+'+nb+' ' + G.O.icons.galleryMoreButton +'</div></div>');
+            G.$E.conTnBottom.on('click', function(e) {
+              G.GOM.displayedMoreSteps++;
+              GalleryResize();
+            });
+          }
+          break;
+        case 'FULLCONTENT':
+        default:
+          break;
+      }
+    }
+    
+    
+    // add one album/folder to the breadcrumb
+    function breadcrumbAdd( albumIdx ) {
+
+      var ic='';
+      if( !G.O.breadcrumbHideIcons ) {
+        ic=G.O.icons.breadcrumbAlbum;
+        if( albumIdx == 0 ) {
+          ic=G.O.icons.breadcrumbHome;
+        }
+      }
+      var $newDiv =jQuery('<div class="oneItem">'+ic + G.I[albumIdx].title+'</div>').appendTo(G.GOM.navigationBar.$newContent.find('.nGY2Breadcrumb'));
+      if( G.O.breadcrumbOnlyCurrentLevel ) {
+        // link to parent folder (only 1 level is displayed in the breadcrumb)
+        if( albumIdx == 0 ) {
+          // no parent level -> stay on current one
+          jQuery($newDiv).data('albumID','0');
+        }
+        else {
+          jQuery($newDiv).data('albumID',G.I[albumIdx].albumID);
+        }
+      }
+      else {
+        // link to current folder
+        jQuery($newDiv).data('albumID',G.I[albumIdx].GetID());
+      }
+      $newDiv.click(function() {
+        var cAlbumID = jQuery(this).data('albumID');
+        DisplayAlbum('-1', cAlbumID);
+        return;
+      });
+    }
+
+    // add one separator to breadcrumb
+    function breadcrumbAddSeparator( lastAlbumID ) {
+      var $newSep=jQuery('<div class="oneItem">'+(G.O.RTL ? G.O.icons.breadcrumbSeparatorRtl : G.O.icons.breadcrumbSeparator)+'</div>').appendTo(G.GOM.navigationBar.$newContent.find('.nGY2Breadcrumb'));
+      jQuery($newSep).data('albumIdx',lastAlbumID);
+      $newSep.click(function() {
+        var sepAlbumIdx=jQuery(this).data('albumIdx');
+        DisplayAlbum('-1', G.I[sepAlbumIdx].GetID());
+        return;
+      });
+    }
+
+    
+    
+    // Manage the gallery toolbar (breadcrumb + tag filter + pagination next/previous)
+    function GalleryNavigationBar( albumIdx ) {
+
+      // Title + background image
+      // var bgImage='';
+      // var l=G.I.length;
+      // var albumID = G.I[albumIdx].GetID();
+      // for( var idx=0; idx<l ; idx++) {
+        // var item=G.I[idx];
+        // if( item.kind == 'image' && item.isToDisplay(albumID) ) {
+          // bgImage='<div id="pipo" class="pipo" style="height: 150px; width:100%; background-image: url("' + item.responsiveURL() + '"); background-size: cover; background-position: center center; filter:blur(2px)">pipo</div>';
+          // break;
+        // }
+      // }
+
+      //console.log(bgImage);
+    
+      // new navigation bar items are not build in the DOM, but in memory
+      G.GOM.navigationBar.$newContent=jQuery('<div class="nGY2Navigationbar"></div>');
+      //G.GOM.navigationBar.$newContent = jQuery(bgImage );
+      //console.log(G.GOM.navigationBar.$newContent);
+
+      //-- manage breadcrumb
+      if( G.O.displayBreadcrumb == true && !G.O.thumbnailAlbumDisplayImage) {
+        // retrieve new folder level
+        var newLevel = 0,
+        lstItems=[];
+        if( albumIdx != 0 ) {
+          var l=G.I.length,
+          parentID=0;
+          
+          lstItems.push(albumIdx);
+          var curIdx=albumIdx;
+          newLevel++;
+          
+          while( G.I[curIdx].albumID != 0 && G.I[curIdx].albumID != -1) {
+            for(var i=1; i < l; i++ ) {
+              if( G.I[i].GetID() == G.I[curIdx].albumID ) {
+                curIdx=i;
+                lstItems.push(curIdx);
+                newLevel++;
+                break;
+              }
+            }
+          }
+        }
+        
+        // build breadcrumb
+        if( !(G.O.breadcrumbAutoHideTopLevel && newLevel == 0) ) {
+          BreadcrumbBuild( lstItems );
+        }
+      }
+
+      
+      //-- manage and build tag filters
+      if( G.galleryFilterTags.Get() != false ) {
+        var nTags = G.I[albumIdx].albumTagList.length;
+        if( nTags > 0 ) {
+          for(var i = 0; i < nTags; i++ ) {
+            var s = G.I[albumIdx].albumTagList[i];
+            var ic = G.O.icons.navigationFilterUnselected;
+            var tagClass = 'Unselected';
+            if( jQuery.inArray(s, G.I[albumIdx].albumTagListSel) >= 0 ) {
+              tagClass = 'Selected';
+              ic = G.O.icons.navigationFilterSelected;
+            }
+            
+            var $newTag = jQuery('<div class="nGY2NavigationbarItem nGY2NavFilter' + tagClass + '">'+ ic +' '+ s +'</div>').appendTo(G.GOM.navigationBar.$newContent);
+
+            $newTag.click(function() {
+  
+              var $this = jQuery(this);
+              var tag = $this.text().replace(/^\s*|\s*$/, '');  //trim trailing/leading whitespace
+  
+              if( G.galleryFilterTagsMode.Get() == 'single' ) {
+                // single TAG selection
+                G.I[albumIdx].albumTagListSel = [];
+                G.I[albumIdx].albumTagListSel.push(tag);
+              }
+              else {
+                // multiple selection of TAGS
+                // if( $this.hasClass('oneTagUnselected') ){
+                if( $this.hasClass('nGY2NavFilterUnselected') ){
+                  G.I[albumIdx].albumTagListSel.push(tag);
+                }
+                else {
+                  var tidx=jQuery.inArray(tag,G.I[albumIdx].albumTagListSel);
+                  if( tidx != -1 ) {
+                    G.I[albumIdx].albumTagListSel.splice(tidx,1);
+                  }
+                }
+                $this.toggleClass('nGY2NavFilters-oneTagUnselected nGY2NavFilters-oneTagSelected');
+              }
+              
+              DisplayAlbum('-1', G.I[albumIdx].GetID());
+            });
+          }
+          
+          // clear/reset TAGS selection
+          var $newClearFilter=jQuery('<div class="nGY2NavigationbarItem nGY2NavFilterSelectAll">'+ G.O.icons.navigationFilterSelectedAll +'</div>').appendTo(G.GOM.navigationBar.$newContent);
+          $newClearFilter.click(function() {
+            // var nTags = G.I[albumIdx].albumTagList.length;
+            G.I[albumIdx].albumTagListSel = [];
+            // for(var i = 0; i < nTags; i++ ) {
+            //  var s = G.I[albumIdx].albumTagList[i];
+            //  G.I[albumIdx].albumTagListSel.push(s);
+            // }
+            DisplayAlbum('-1', G.I[albumIdx].GetID());
+          });
+        }
+      }
+      
+      // --- Gallery pagination next/previous
+      if( G.galleryDisplayMode.Get() == "PAGINATION" && G.O.galleryPaginationTopButtons ) {
+          if( G.layout.support.rows && G.galleryMaxRows.Get() > 0 ) {
+            // ManagePagination( G.GOM.albumIdx );
+            var $newTag = jQuery('<div class="nGY2NavigationbarItem nGY2NavPagination">'+G.O.icons.navigationPaginationPrevious+'</div>').appendTo(G.GOM.navigationBar.$newContent);
+            $newTag.click(function() {
+              paginationPreviousPage();
+            });
+            var $newTag = jQuery('<div class="nGY2NavigationbarItem nGY2NavPagination">'+G.O.icons.navigationPaginationNext+'</div>').appendTo(G.GOM.navigationBar.$newContent);
+            $newTag.click(function() {
+              paginationNextPage();
+            });
+          }
+      }
+
+    }
+    
+    function BreadcrumbBuild(lstItems) {
+
+      // console.log(G.GOM.navigationBar.$newContent);
+      jQuery('<div class="nGY2NavigationbarItem nGY2Breadcrumb"></div>').appendTo(G.GOM.navigationBar.$newContent);
+      // console.log(G.GOM.navigationBar.$newContent);
+      
+      if( G.O.breadcrumbOnlyCurrentLevel ) {
+        // display only 1 separator and the current folder level
+        if( lstItems.length == 0 ) {
+          breadcrumbAdd(0);
+        }
+        else {
+          var last=lstItems.length-1;
+          if( lstItems.length == 1 ) {
+            breadcrumbAddSeparator(0);    // root level
+          }
+          else {
+            breadcrumbAddSeparator(lstItems[0]);
+          }
+          breadcrumbAdd(lstItems[0]);
+        }
+      }
+      else {
+        // display the full breadcrum (full folder levels including root level)
+        breadcrumbAdd(0);
+        if( lstItems.length > 0 ) {
+          breadcrumbAddSeparator(0);
+          for(var i=lstItems.length-1; i>=0 ; i-- ) {
+            breadcrumbAdd(lstItems[i]);
+            if( i > 0 ) {
+              breadcrumbAddSeparator(lstItems[i-1]);
+            }
+          }
+        }
+      }
+    
+    }
+    
+
+    // Display gallery pagination
+    function ManagePagination( albumIdx ) {
+
+      G.$E.conTnBottom.css('opacity', 0);
+      G.$E.conTnBottom.children().remove();
+
+      if( G.GOM.items.length == 0 ) { return; }   // no thumbnail to display
+
+      // calculate the number of pages
+      var nbPages=Math.ceil((G.GOM.items[G.GOM.items.length - 1].row + 1)/G.galleryMaxRows.Get());
+
+      // only one page -> do not display pagination
+      if( nbPages == 1 ) { return; }
+
+      // check if current page still exists (for example after a resize)
+      if( G.GOM.pagination.currentPage > (nbPages-1) ) {
+        G.GOM.pagination.currentPage = nbPages-1;
+      }
+      
+      GalleryRenderGetInterval();
+      // nothing to display --> exit
+      if( G.GOM.displayInterval.len == 0 ) { return; }
+      
+      // display "previous"
+      if( G.O.galleryPaginationMode == 'NUMBERS' && G.GOM.pagination.currentPage > 0 ) {
+        var $eltPrev = jQuery('<div class="nGY2PaginationPrev">'+G.O.icons.paginationPrevious+'</div>').appendTo(G.$E.conTnBottom);
+        $eltPrev.click(function(e) {
+          paginationPreviousPage();
+        });
+      }
+
+      var firstPage = 0;
+      var lastPage = nbPages;
+      if( G.O.galleryPaginationMode != 'NUMBERS' ) {
+        // no 'previous'/'next' and no max number of pagination items
+        firstPage = 0;
+      }
+      else {
+        // display pagination numbers and previous/next
+        var vp = G.O.paginationVisiblePages;
+        var numberOfPagesToDisplay = G.O.paginationVisiblePages;
+        if( numberOfPagesToDisplay >= nbPages ) {
+          firstPage = 0;
+        }
+        else {
+          // we have more pages than we want to display
+          var nbBeforeAfter = 0;
+          if( isOdd(numberOfPagesToDisplay) ) {
+            nbBeforeAfter = (numberOfPagesToDisplay + 1) / 2;
+          }
+          else {
+            nbBeforeAfter = numberOfPagesToDisplay / 2;
+          }
+          
+          if( G.GOM.pagination.currentPage < nbBeforeAfter ) {
+            firstPage = 0;
+            lastPage = numberOfPagesToDisplay - 1;
+            if( lastPage > nbPages ) {
+              lastPage = nbPages - 1;
+            }
+          }
+          else {
+            firstPage = G.GOM.pagination.currentPage - nbBeforeAfter;
+            lastPage = firstPage + numberOfPagesToDisplay;
+            if( lastPage > nbPages ) {
+              lastPage = nbPages - 1;
+            }
+          }
+          
+          if( (lastPage - firstPage) < numberOfPagesToDisplay ) {
+            firstPage = lastPage - numberOfPagesToDisplay;
+            if( firstPage < 0 ) {
+              firstPage = 0;
+            }
+          }
+
+        }
+      }
+
+      // render pagination items
+      for(var i = firstPage; i < lastPage; i++ ) {
+        var c = '';
+        var p = '';
+
+        switch( G.O.galleryPaginationMode ) {
+          case 'NUMBERS':
+            c = 'nGY2paginationItem';
+            p = i + 1;
+            break;
+          case 'DOTS':
+            c = 'nGY2paginationDot';
+            break;
+          case 'RECTANGLES':
+            c = 'nGY2paginationRectangle';
+            break;
+        }
+        if( i == G.GOM.pagination.currentPage ) {
+          c += 'CurrentPage';
+        }
+
+        var elt$ = jQuery('<div class="' + c + '">' + p + '</div>').appendTo(G.$E.conTnBottom);
+        elt$.data('pageNumber', i );
+        elt$.click( function(e) {
+          G.GOM.pagination.currentPage = jQuery(this).data('pageNumber');
+          TriggerCustomEvent('pageChanged');
+
+          // scroll to top of gallery if not displayed
+          G.GOM.ScrollToTop();
+ 
+          GalleryDisplayPart1( true );
+          GalleryDisplayPart2( true );
+        });
+
+      }
+
+      // display "next"
+      if( G.O.galleryPaginationMode == 'NUMBERS' && (G.GOM.pagination.currentPage + 1) < nbPages ) {
+        var $eltNext = jQuery('<div class="nGY2PaginationNext">' + G.O.icons.paginationNext + '</div>').appendTo(G.$E.conTnBottom);
+        $eltNext.click( function(e) {
+          paginationNextPage();
+        });
+      }
+
+      G.$E.conTnBottom.css('opacity', 1);
+
+    }
+    function isOdd(num) { return (num % 2) == 1;}
+    
+    // pagination - next page
+    function paginationNextPage() {
+      var aIdx = G.GOM.albumIdx,
+      n1 = 0;
+      ThumbnailHoverOutAll();
+      
+      // pagination - max lines per page mode
+      if( G.galleryMaxRows.Get() > 0 ) {
+        // number of pages
+        n1 = (G.GOM.items[G.GOM.items.length - 1].row + 1) / G.galleryMaxRows.Get();
+      }
+      var n2 = Math.ceil(n1);
+      var pn = G.GOM.pagination.currentPage;
+      if( pn < (n2-1) ) {
+        pn++;
+      }
+      else {
+        pn = 0;
+      }
+      
+      G.GOM.pagination.currentPage = pn;
+      TriggerCustomEvent('pageChanged');
+
+      // scroll to top of gallery if not displayed
+      G.GOM.ScrollToTop();
+
+      GalleryDisplayPart1( true );
+      GalleryDisplayPart2( true );
+    }
+    
+    // pagination - previous page
+    function paginationPreviousPage() {
+      // var aIdx=G.$E.conTnBottom.data('galleryIdx'),
+      var aIdx = G.GOM.albumIdx,
+      n1 = 0;
+
+      ThumbnailHoverOutAll();
+      
+      // pagination - max lines per page mode
+      if( G.galleryMaxRows.Get() > 0 ) {
+        // number of pages
+        n1 = (G.GOM.items[G.GOM.items.length - 1].row + 1) / G.galleryMaxRows.Get();
+      }
+      var n2 = Math.ceil(n1);
+      
+      // var pn=G.$E.conTnBottom.data('currentPageNumber');
+      var pn = G.GOM.pagination.currentPage;
+      if( pn > 0 ) {
+        pn--;
+      }
+      else {
+        pn = n2 - 1;
+      }
+
+      G.GOM.pagination.currentPage = pn;
+      TriggerCustomEvent('pageChanged');
+
+      // scroll to top of gallery if not displayed
+      G.GOM.ScrollToTop();
+
+      GalleryDisplayPart1( true );
+      GalleryDisplayPart2( true );
+    }
+
+    // retrieve the from/to intervall for gallery thumbnail render
+    function GalleryRenderGetInterval() {
+      G.GOM.displayInterval.from = 0;
+      G.GOM.displayInterval.len = G.I.length;
+      
+      switch( G.galleryDisplayMode.Get() ) {
+        case 'PAGINATION':
+          if( G.layout.support.rows ) {
+            var nbTn = G.GOM.items.length;
+            var firstRow = G.GOM.pagination.currentPage * G.galleryMaxRows.Get();
+            var lastRow = firstRow + G.galleryMaxRows.Get();
+            var firstTn = -1;
+            G.GOM.displayInterval.len = 0;
+            for( var i = 0; i < nbTn ; i++ ) {
+              var curTn = G.GOM.items[i];
+              if( curTn.row >= firstRow && curTn.row < lastRow ) {
+                if( firstTn == -1 ) {
+                  G.GOM.displayInterval.from = i;
+                  firstTn = i;
+                }
+                G.GOM.displayInterval.len++;
+              }
+            }
+          }
+          break;
+        case 'MOREBUTTON':
+          if( G.layout.support.rows ) {
+            var nbTn = G.GOM.items.length;
+            var lastRow = G.O.galleryDisplayMoreStep * (G.GOM.displayedMoreSteps+1);
+            G.GOM.displayInterval.len = 0;
+            for( var i = 0; i < nbTn ; i++ ) {
+              var curTn = G.GOM.items[i];
+              if( curTn.row < lastRow ) {
+                G.GOM.displayInterval.len++;
+              }
+            }
+          }
+          break;
+        case 'ROWS':
+          if( G.layout.support.rows ) {
+            var nbTn = G.GOM.items.length;
+            var lastRow = G.galleryMaxRows.Get();
+            if( G.galleryLastRowFull.Get() && G.GOM.lastFullRow != -1 ) {
+              if( lastRow > (G.GOM.lastFullRow + 1) ) {
+                lastRow = G.GOM.lastFullRow + 1;
+              }
+            }
+            G.GOM.displayInterval.len = 0;
+            for( var i = 0; i < nbTn ; i++ ) {
+              var curTn = G.GOM.items[i];
+              if( curTn.row < lastRow ) {
+                G.GOM.displayInterval.len++;
+              }
+            }
+          }
+          break;
+        default:
+        case 'FULLCONTENT':
+        if( G.layout.support.rows && G.galleryLastRowFull.Get() && G.GOM.lastFullRow != -1 ) {
+            var nbTn = G.GOM.items.length;
+            var lastRow = G.GOM.lastFullRow + 1;
+            G.GOM.displayInterval.len = 0;
+            for( var i = 0; i < nbTn ; i++ ) {
+              var curTn = G.GOM.items[i];
+              if( curTn.row < lastRow ) {
+                G.GOM.displayInterval.len++;
+              }
+            }
+          }
+          break;
+      }
+    }
+    
+    
+    // RENDER THE GALLERY
+    function GalleryRender( albumIdx ) {
+      TriggerCustomEvent('galleryRenderStart');
+      clearTimeout(G.GOM.slider.timerID);
+      G.GOM.slider.hostIdx = -1;      // disabled slider on thumbnail
+      
+      var fu=G.O.fnGalleryRenderStart;
+      if( fu !== null ) {
+        typeof fu == 'function' ? fu(albumIdx) : window[fu](albumIdx);
+      }
+
+      G.layout.SetEngine();
+      G.galleryResizeEventEnabled = false;
+      G.GOM.albumIdx = -1;
+      G.GOM.lastDisplayedIdx = -1;
+
+      // pagination
+      if( G.$E.conTnBottom !== undefined ) {
+        // G.$E.conTnBottom.children().remove();
+        G.$E.conTnBottom.empty();
+      }
+
+      // navigation toolbar (breadcrumb + tag filters + pagination next/previous)
+      GalleryNavigationBar(albumIdx);
+      
+      if( G.GOM.firstDisplay ) {
+				// first gallery display
+        G.GOM.firstDisplay = false;
+        var d = Date.now()-G.GOM.firstDisplayTime;
+        if( d < G.O.galleryRenderDelay ) {
+					// display after defined delay
+          // setTimeout( function() { GalleryRenderPart1( albumIdx )}, G.O.galleryRenderDelay-d );
+          requestTimeout( function() { GalleryRenderPart1( albumIdx )}, G.O.galleryRenderDelay-d );
+        }
+        else {
+          GalleryRenderPart1( albumIdx );
+        }
+        G.O.galleryRenderDelay = 0;
+        
+      }
+      else {
+        var hideNavigationBar = false;
+        if( G.GOM.navigationBar.$newContent.children().length == 0 ) {
+          hideNavigationBar = true;
+        }
+
+        // hide everything
+        var tweenable = new NGTweenable();
+        tweenable.tween({
+          from:         { 'opacity': 1 },
+          to:           { 'opacity': 0 },
+          duration:     300,
+          easing:       'easeInQuart',
+          attachment:   { h: hideNavigationBar },
+          step:         function (state, att) {
+            G.$E.conTnParent.css({'opacity': state.opacity });
+            if( att.h ) {
+              G.$E.conNavigationBar.css({ 'opacity': state.opacity });
+            }
+          },
+          finish:       function (state, att) {
+            if( att.h ) {
+              G.$E.conNavigationBar.css({ 'opacity': 0, 'display': 'none' });
+            }
+            // scroll to top of the gallery if needed
+            G.GOM.ScrollToTop();
+
+            GalleryRenderPart1( albumIdx );
+          }
+        });
+      }
+    }
+
+
+    function GalleryRenderPart1( albumIdx ) {
+      // display new navigation bar
+      var oldN = G.$E.conNavigationBar.children().length;
+      G.$E.conNavigationBar.empty();
+      G.GOM.navigationBar.$newContent.children().clone(true,true).appendTo(G.$E.conNavigationBar);
+      // G.GOM.navigationBar.$newContent.appendTo(G.$E.conNavigationBar);
+      if( G.$E.conNavigationBar.children().length > 0 && oldN == 0 ) {
+        G.$E.conNavigationBar.css({ 'opacity': 0, 'display': 'block' });
+        var tweenable = new NGTweenable();
+        tweenable.tween({
+          from:     { opacity: 0 },
+          to:       { opacity: 1 },
+          duration: 200,
+          easing:   'easeInQuart',
+          step:     function (state) {
+            // window.ng_draf( function() {
+              G.$E.conNavigationBar.css( state );
+            // });
+          },
+          finish:   function (state) {
+            // window.ng_draf( function() {
+              G.$E.conNavigationBar.css({ 'opacity': 1 });
+              // display gallery
+              // GalleryRenderPart2( albumIdx );
+              // setTimeout(function(){ GalleryRenderPart2(albumIdx) }, 60);
+              requestTimeout(function(){ GalleryRenderPart2(albumIdx) }, 60);
+            // });
+          }
+        });
+      }
+      else {
+        requestTimeout(function(){ GalleryRenderPart2(albumIdx) }, 60);
+      }
+
+    }
+    
+    // Gallery render part 2 -> remove all existing thumbnails
+    function GalleryRenderPart2(albumIdx) {
+
+			G.GOM.lastZIndex = parseInt(G.$E.base.css('z-index'));
+      if( isNaN(G.GOM.lastZIndex) ) {
+        G.GOM.lastZIndex=0;
+      }
+      G.$E.conTnParent.css({ 'opacity': 0 });
+      G.$E.conTn.off().empty();
+      var l = G.I.length;
+      for( var i = 0; i < l ; i++ ) {
+        // reset each item
+        var item = G.I[i];
+        item.hovered = false;
+        item.$elt = null;
+        item.$Elts = [];
+        item.eltTransform = [];
+        item.eltFilter = [];
+        item.width = 0;
+        item.height = 0;
+        item.left = 0;
+        item.top = 0;
+        item.resizedContentWidth = 0;
+        item.resizedContentHeight = 0;
+        item.thumbnailImgRevealed = false;
+      }
+
+      if( G.CSStransformName == null ) {
+        G.$E.conTn.css('left', '0px');
+      }
+      else {
+        // G.$E.conTn.css( G.CSStransformName, 'translateX(0px)');
+        G.$E.conTn.css( G.CSStransformName, 'none');
+      }
+      
+      // setTimeout(function(){ GalleryRenderPart3(albumIdx) }, 60);
+      requestTimeout(function(){ GalleryRenderPart3(albumIdx) }, 60);
+      // GalleryRenderPart3(albumIdx);
+
+    }
+    
+    // Gallery render part 3 -> start building the new gallery
+    function GalleryRenderPart3(albumIdx) {
+      var d = new Date();      
+      
+      G.$E.conTnParent.css( 'opacity', 1);
+
+      G.GOM.items = [];
+      G.GOM.displayedMoreSteps = 0;
+      // retrieve label height      
+      if( G.O.thumbnailLabel.get('position') == 'onBottom' ) {
+        // retrieve height each time because size can change depending on thumbnail's settings
+        G.tn.labelHeight[G.GOM.curNavLevel] = ThumbnailGetLabelHeight();
+      }
+      else {
+        G.tn.labelHeight[G.GOM.curNavLevel] = 0;
+      }
+      G.GOM.albumIdx=albumIdx;
+
+      TriggerCustomEvent('galleryRenderEnd');
+      var fu=G.O.fnGalleryRenderEnd;
+      if( fu !== null ) {
+        typeof fu == 'function' ? fu(albumIdx) : window[fu](albumIdx);
+      }
+
+      // Step 1: populate GOM
+      if( GalleryPopulateGOM() ) {
+        // step 2: calculate layout
+        GallerySetLayout();
+
+        // step 3: display whole gallery 
+        GalleryAppear();
+        
+        // step 4: display thumbnails
+        GalleryDisplayPart1( false );
+        requestTimeout(function(){ GalleryDisplayPart2( false ) }, 120);
+      }
+      else {
+        G.galleryResizeEventEnabled = true;
+      }
+      
+      if( G.O.debugMode ) { console.log('GalleryRenderPart3: '+ (new Date()-d)); }
+
+    }
+    
+    
+    // Resize the gallery
+    function GalleryResize() {
+      var d = new Date();
+      G.galleryResizeEventEnabled = false;
+      // G.GOM.cache.areaWidth=G.$E.conTnParent.width();
+      if( GallerySetLayout() == false ) {
+        G.galleryResizeEventEnabled = true;
+        if( G.O.debugMode ) { console.log('GalleryResize1: '+ (new Date()-d)); }
+        return;
+      }
+      if( G.O.debugMode ) { console.log('GalleryResizeSetLayout: '+ (new Date()-d)); }
+
+      GalleryDisplayPart1( false );
+      GalleryDisplayPart2( false );
+
+      if( G.O.debugMode ) { console.log('GalleryResizeFull: '+ (new Date()-d)); }
+    }
+    
+    
+    
+    // copy items (album content) to GOM
+		// returns:
+		//		true: thumbnail image size is needed for the layout, but not set -> retrieve the sizes and display gallery
+    function GalleryPopulateGOM() {
+      
+      var preloadImages = '';
+      var imageSizeRequested = false;
+      var albumID = G.I[G.GOM.albumIdx].GetID();
+      var l = G.I.length;
+      var cnt = 0;
+
+      for( var idx = 0; idx < l; idx++ ) {
+        var item = G.I[idx];
+        // check album
+        if( item.isToDisplay(albumID) ) {
+          var w = item.thumbImg().width;
+          var h = item.thumbImg().height;
+          // if unknown image size and layout is not grid --> we need to retrieve the size of the images
+          if( G.layout.prerequisite.imageSize && ( w == 0 || h == 0) ) {
+          // if( true ) {
+            imageSizeRequested = true;
+            preloadImages += '<img src="'+item.thumbImg().src+'" data-idx="'+cnt+'" data-albumidx="'+G.GOM.albumIdx+'">';
+          }
+          
+          // set default size if required
+          if( h == 0 ) {
+            h = G.tn.defaultSize.getHeight();
+          }
+          if( w == 0 ) {
+            w = G.tn.defaultSize.getWidth();
+          }
+          var tn = new G.GOM.GTn(idx, w, h);
+          G.GOM.items.push(tn);
+          cnt++;
+        }
+      }
+
+      TriggerCustomEvent('galleryObjectModelBuilt');
+      var fu = G.O.fnGalleryObjectModelBuilt;
+      if( fu !== null ) {
+        typeof fu == 'function' ? fu() : window[fu]();
+      }
+      
+      if( imageSizeRequested ) {
+        // preload images to retrieve their size and then resize the gallery (=GallerySetLayout()+ GalleryDisplay())
+        var $newImg = jQuery(preloadImages);
+        var gi_imgLoad = ngimagesLoaded( $newImg );
+        $newImg = null;
+        gi_imgLoad.on( 'progress', function( instance, image ) {
+        
+          if( image.isLoaded ) {
+            var idx = image.img.getAttribute('data-idx');
+            var albumIdx = image.img.getAttribute('data-albumidx');
+            if( albumIdx == G.GOM.albumIdx ) {
+              // ignore event if not on current album
+              var curTn = G.GOM.items[idx];
+              curTn.imageWidth = image.img.naturalWidth;
+              curTn.imageHeight = image.img.naturalHeight;
+              var item = G.I[curTn.thumbnailIdx];
+              item.thumbs.width[G.GOM.curNavLevel][G.GOM.curWidth] = curTn.imageWidth;
+              item.thumbs.height[G.GOM.curNavLevel][G.GOM.curWidth] = curTn.imageHeight;
+ 
+              // resize the gallery
+              G.GalleryResizeThrottled();
+              
+              // set the retrieved size to all levels with same configuration  
+              var object = item.thumbs.width.l1;
+              for (var property in object) {
+                if (object.hasOwnProperty(property)) {
+                  if( property != G.GOM.curWidth ) {
+                    if( G.tn.settings.width.l1[property] == G.tn.settings.getW() && G.tn.settings.height.l1[property] == G.tn.settings.getH() ) {
+                      item.thumbs.width.l1[property] = curTn.imageWidth;
+                      item.thumbs.height.l1[property] = curTn.imageHeight;
+                    }
+                  }
+                }
+              }
+              object = item.thumbs.width.lN;
+              for (var property in object) {
+                if (object.hasOwnProperty(property)) {
+                  if( property != G.GOM.curWidth ) {
+                    if( G.tn.settings.width.lN[property] == G.tn.settings.getW() && G.tn.settings.height.lN[property] == G.tn.settings.getH() ) {
+                      item.thumbs.width.lN[property] = curTn.imageWidth;
+                      item.thumbs.height.lN[property] = curTn.imageHeight;
+                    }
+                  }
+                }
+              }
+            }
+          }
+        });
+        G.galleryResizeEventEnabled = true;
+        return false;
+      }
+      else {
+        return true;
+      }
+      
+    }
+    
+    //----- Calculate the layout of the thumbnails for the full gallery
+    function GallerySetLayout() {
+      var r = true;
+      // width of the available area
+      G.GOM.cache.areaWidth = G.$E.conTnParent.width();
+      G.GOM.displayArea = { width:0, height:0 };
+
+      switch( G.layout.engine ) {
+        case 'JUSTIFIED':
+          r = GallerySetLayoutWidthtAuto();
+          break;
+        case 'CASCADING':
+          r = GallerySetLayoutHeightAuto();
+          break;
+        case 'MOSAIC':
+          r = GallerySetLayoutMosaic();
+          break;
+        case 'GRID':
+        default:
+          r = GallerySetLayoutGrid();
+          break;
+      }
+      
+      TriggerCustomEvent('galleryLayoutApplied');
+      var fu = G.O.fnGalleryLayoutApplied;
+      if( fu !== null ) {
+        typeof fu == 'function' ? fu() : window[fu]();
+      }
+      return r;
+
+    }
+    
+    
+    //----- CASCADING LAYOUT
+    function GallerySetLayoutHeightAuto() {
+      var curCol =      0,
+      areaWidth =       G.GOM.cache.areaWidth,
+      curRow =          0,
+      colHeight =       [],
+      maxCol =          NbThumbnailsPerRow(areaWidth),
+      gutterWidth =     0,
+      gutterHeight =    G.tn.settings.GetResponsive('gutterHeight');
+      var w =           0;
+      var scaleFactor = 1;
+      var tnWidth =     G.tn.defaultSize.getOuterWidth();
+      var nbTn =        G.GOM.items.length;
+      var curPosY = 0;
+
+      if( G.O.thumbnailAlignment == 'justified' ) {
+        maxCol = Math.min(maxCol, nbTn);
+        gutterWidth = ( maxCol == 1 ? 0 : (areaWidth - (maxCol * tnWidth) ) / (maxCol - 1) );
+      }
+      else {
+        gutterWidth = G.tn.settings.GetResponsive('gutterWidth');
+      }
+
+
+      var borderWidth = G.tn.opt.Get('borderHorizontal') * 2;
+      var borderHeight = G.tn.opt.Get('borderVertical') * 2;
+
+      G.GOM.lastFullRow=-1;   // feature disabled
+
+      // Retrieve the real used width of the area (the evaluation is based on the content of the first line)
+      if( G.O.thumbnailAlignment == 'fillWidth' ) {
+        // fillWidth --> evaluate scale factor and number of columns
+        var totalGutterWidth = (maxCol - 1) * gutterWidth;
+        scaleFactor = (areaWidth - totalGutterWidth) / (maxCol * tnWidth);
+        if( scaleFactor > 1 ) {
+          maxCol++; // add one column and re-evaluate the scale factor
+        }
+        totalGutterWidth = (maxCol - 1) * gutterWidth;
+        scaleFactor = Math.min( (areaWidth - totalGutterWidth) / (maxCol*tnWidth), 1);   // no upscale
+      }
+
+      
+      tnWidth = Math.round( tnWidth * scaleFactor);
+      var contentWidth = tnWidth - borderWidth;
+
+      // loop to position the thumbnails, and set their size
+      var baseHeight = Math.round( G.tn.opt.Get('baseGridHeight') * scaleFactor );
+      for( var i = 0; i < nbTn ; i++ ) {
+        var curTn = G.GOM.items[i];
+        if( curTn.deleted == true ) { break; }    // item is logically deleted
+        if( curTn.imageHeight > 0 && curTn.imageWidth > 0 ) {
+          var curPosX = 0,
+          curPosY = 0;
+          var imageRatio = curTn.imageHeight / curTn.imageWidth;
+          // curTn.resizedContentWidth = tnWidth - borderWidth;
+          curTn.resizedContentWidth = contentWidth;
+          curTn.resizedContentHeight = curTn.resizedContentWidth * imageRatio;
+          if( baseHeight > 0 ) {
+            // grid based vertical position
+            var t = Math.max( Math.trunc(curTn.resizedContentHeight/baseHeight), 1) ;
+            curTn.resizedContentHeight = baseHeight * t + ((t-1)*(borderHeight+gutterHeight));
+          }
+          
+          curTn.height = curTn.resizedContentHeight + borderHeight + G.tn.labelHeight.get();
+          curTn.width = tnWidth;
+          curTn.row = 0;
+          
+          if( curRow == 0 ) {
+            // first row
+            curPosX = curCol * (tnWidth + gutterWidth);
+            colHeight[curCol] = curTn.height + gutterHeight;
+            
+            curCol++;
+            if( curCol >= maxCol ) {
+              curCol = 0;
+              curRow++;
+            }
+          }
+          else {
+            var c=0,
+            minColHeight=colHeight[0];
+            for( var j = 1; j < maxCol; j++) {
+              if( (colHeight[j] + 5) < minColHeight ) {     // +5 --> threshold
+                minColHeight = colHeight[j];
+                c = j;
+                //break;
+              }
+            }
+            curPosY = colHeight[c];
+            curPosX = c * (tnWidth + gutterWidth);
+            colHeight[c] = curPosY + curTn.height + gutterHeight;
+          }
+
+          var x = curPosX;
+          if( G.O.RTL) {
+            x= w - curPosX - tnWidth;
+          }
+
+          curTn.left = x;
+          curTn.top = curPosY;
+        }
+      }
+
+      G.GOM.displayArea.width= maxCol * (tnWidth + gutterWidth) - gutterWidth;
+      return true;
+    }
+    
+    
+    //----- JUSTIFIED LAYOUT
+    function GallerySetLayoutWidthtAuto() {
+      var curWidth =               0,
+      areaWidth =                  G.GOM.cache.areaWidth,
+      lastPosX =                   0,
+      curPosY =                    0,
+      rowLastItem =                [],
+      rowNum =                     0,
+      rowHeight =                  [],
+      bNewRow =                    false,
+      cnt =                        0,
+      gutterWidth =                G.tn.settings.GetResponsive('gutterWidth'),
+      gutterHeight =               G.tn.settings.GetResponsive('gutterHeight');
+      // by grief-of-these-days
+      var maxRowHeightVertical =   0; // max height of a row with vertical thumbs
+      var maxRowHeightHorizontal = 0; // max height of a row with horizontal thumbs
+      var rowHasVertical =         false; // current row has vertical thumbs
+      var rowHasHorizontal =       false; // current row has horizontal thumbs
+
+      var tnHeight = G.tn.defaultSize.getOuterHeight();
+      var borderWidth = G.tn.opt.Get('borderHorizontal') * 2;
+      var borderHeight = G.tn.opt.Get('borderVertical') * 2;
+      var nbTnInCurrRow = 1;
+      var nbTn = G.GOM.items.length;
+
+      // first loop --> retrieve each row image height
+      for( var i = 0; i < nbTn ; i++ ) {
+        var curTn = G.GOM.items[i];
+        if( curTn.deleted == true ) { break; }    // item is logically deleted
+        if( curTn.imageWidth > 0 ) {
+          var imageRatio = curTn.imageWidth / curTn.imageHeight;
+          var imageWidth = Math.floor( tnHeight * imageRatio );
+
+          if( bNewRow ) {
+            bNewRow = false;
+            rowNum++;
+            curWidth = 0;
+            rowHasVertical = false;
+            rowHasHorizontal = false;
+            nbTnInCurrRow = 1;
+          }
+          // by grief-of-these-days
+          if( curTn.imageHeight > curTn.imageWidth ) {
+            rowHasVertical = true;
+          }
+          else {
+            rowHasHorizontal = true;
+          }
+          
+          if( (curWidth + gutterWidth + imageWidth) < (areaWidth - (nbTnInCurrRow * borderWidth)) ) {
+            // enough place left in the current row
+            curWidth += imageWidth + gutterWidth;
+            rowHeight[rowNum] = tnHeight;
+            
+            // prevent incomplete row from being heigher than the previous ones.
+            // by grief-of-these-days
+            var rowHeightLimit = Math.max(rowHasVertical ? maxRowHeightVertical : 0, rowHasHorizontal ? maxRowHeightHorizontal : 0);
+            if( rowHeightLimit > 0 ) {
+              rowHeight[rowNum] = Math.min(rowHeight[rowNum], rowHeightLimit);
+            }
+            
+            rowLastItem[rowNum] = i;
+          }
+          else {
+            // new row after current item --> we need to adujet the row height to have enough space for the current thumbnail
+            curWidth += gutterWidth+imageWidth;
+            var ratio = (areaWidth - nbTnInCurrRow * borderWidth) / curWidth;
+            var rH = Math.floor(tnHeight * ratio);
+            rowHeight[rowNum] = rH;
+            
+            // save the max row height for each thumb orientation.
+            // by grief-of-these-days
+            if( rowHasVertical ) {
+              maxRowHeightVertical = Math.max( maxRowHeightVertical, rH );
+            }
+            if( rowHasHorizontal ) {
+              maxRowHeightHorizontal = Math.max( maxRowHeightHorizontal, rH );
+            }
+            
+            rowLastItem[rowNum] = i;
+            bNewRow = true;
+          }
+          cnt++;
+          nbTnInCurrRow++;
+        }
+      }
+
+      rowNum = 0;
+      curPosY = 0;
+      lastPosX = 0;
+      cnt = 0;
+      
+      G.GOM.lastFullRow = 0;    // display at leat 1 row (even if not full)
+      
+      // second loop --> calculate each thumbnail size
+      for( var i = 0; i < nbTn ; i++ ) {
+        var curTn = G.GOM.items[i];
+        if( curTn.imageWidth > 0 ) {
+          var imageRatio = curTn.imageWidth / curTn.imageHeight;
+          var imageWidth = Math.floor( imageRatio * rowHeight[rowNum] ); // border is already NOT included
+
+          if( i == rowLastItem[rowNum] ) {
+            // row last item --> adjust image width because of rounding problems
+            if( rowLastItem.length != (rowNum+1) ) {
+              // last item in current row -> use the full remaining width
+              imageWidth = areaWidth - lastPosX - borderWidth;
+            }
+            else {
+              // very last item (on the last row)
+              if( (lastPosX + gutterWidth + imageWidth + borderWidth ) > areaWidth ) {
+                // reduce size if image is wider as the remaining space
+                imageWidth = areaWidth - lastPosX - borderWidth;
+              }
+            }
+          }
+          
+          var rh = parseInt( rowHeight[rowNum] );
+          imageWidth = parseInt( imageWidth );
+
+          // thumbnail image size
+          curTn.resizedContentWidth = imageWidth;
+          curTn.resizedContentHeight = rh;
+          // thumbnail position and size
+          curTn.width = imageWidth + borderWidth;
+          curTn.height= rh + G.tn.labelHeight.get() + borderHeight;
+          curTn.row = rowNum;
+
+          curTn.top = curPosY;
+          var x = lastPosX;
+          if( G.O.RTL) {
+            x = areaWidth - lastPosX - curTn.width ;
+          }
+          curTn.left = x;
+
+          lastPosX += curTn.width + gutterWidth;
+
+          if( i == rowLastItem[rowNum] ) {
+            // start a new row
+            curPosY += curTn.height + gutterHeight;
+            G.GOM.lastFullRow = rowNum - 1;
+            rowNum++;
+            lastPosX = 0;
+          }
+          cnt++;
+        }
+        else {
+          return false;
+        }
+      }
+      
+      if( false ) {
+        var newTop = 0;
+        if( typeof GOMidx !== 'undefined' ) {
+          // hover effect on gallery (vs on thumbnail) --> experimental / not used
+          if( G.GOM.albumIdx != -1 ) {
+            var hoveredTn = G.GOM.items[GOMidx];
+            var item = G.I[hoveredTn.thumbnailIdx];
+            
+            // hovered thumbnail
+            hoveredTn.width += 40;
+            hoveredTn.height += 40;
+            // todo : left
+            
+            for( var i = 0; i < nbTn ; i++ ) {
+              var curTn = G.GOM.items[i];
+              if( curTn.imageWidth > 0 ) {
+                if( curTn.row == hoveredTn.row ) {
+                  // hovered row
+                  newTop = 40;
+                  if( hoveredTn.thumbnailIdx != curTn.thumbnailIdx ) {
+                    // not hovered thumbnail
+                    // curTn.resizedContentWidth+=10;
+                    // curTn.resizedContentHeight+=20;
+                    // curTn.width+=10;
+                    curTn.top += 30;
+                    curTn.width -= 20;
+                    curTn.height -= 20;
+                  }
+                }
+                else {
+                  // not hovered row
+                  if( curTn.row == 0 ) {
+                    // first row
+                  }
+                  else {
+                    curTn.top += newTop;
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+      
+      G.GOM.displayArea.width = areaWidth;
+      return true;
+    }    
+    
+
+    //----- MOSAIC LAYOUT
+    // Grid using a user defined pattern layout
+    // With this layout, a pattern definition is handeld a row
+    function GallerySetLayoutMosaic() {
+      var areaWidth =     G.GOM.cache.areaWidth;
+      var gutterHeight =  G.tn.settings.GetResponsive('gutterHeight');
+      var gutterWidth =   G.tn.settings.GetResponsive('gutterWidth');
+      var borderWidth =   G.tn.opt.Get('borderHorizontal') * 2;
+      var borderHeight =  G.tn.opt.Get('borderVertical') * 2;
+
+      var nbTn = G.GOM.items.length;
+      var row = 0;
+      var h = 0;
+      var n = 0;
+      
+      
+      // first loop: evaluate the gallery width based on the first row
+      var nbCols = 0;
+      var maxW = 0;
+      var mosaicPattern = G.tn.settings.getMosaic();
+      for( var i = 0; i < nbTn ; i++ ) {
+        var curPatternElt = mosaicPattern[n];
+
+        var cLeft = (curPatternElt.c - 1) * G.tn.defaultSize.getOuterWidth() + (curPatternElt.c - 1) * gutterWidth;
+        var cWidth = curPatternElt.w * G.tn.defaultSize.getOuterWidth() + (curPatternElt.w - 1) * gutterWidth;
+        
+        maxW = Math.max(maxW, cLeft + cWidth );
+        
+        nbCols = Math.max(nbCols, (curPatternElt.c - 1) + curPatternElt.w );
+
+        n++;
+        if( n >= mosaicPattern.length ) {
+          // end of pattern
+          break;
+        }
+      }
+      var totalGutterWidth = (nbCols - 1) * gutterWidth;
+      var scaleFactor = Math.min( (areaWidth - totalGutterWidth ) / ( maxW - totalGutterWidth ), 1);
+      
+      // second loop: position all the thumbnails based on the layout pattern
+      row = 0;
+      n = 0;
+      var mosaicPattern = G.tn.settings.getMosaic();
+      for( var i = 0; i < nbTn ; i++ ) {
+        var curTn = G.GOM.items[i];
+        var curPatternElt = mosaicPattern[n];
+        
+        curTn.top = Math.round((curPatternElt.r - 1) * G.tn.defaultSize.getOuterHeight()*scaleFactor) + (curPatternElt.r - 1) * gutterHeight + row * h + (G.tn.labelHeight.get()*(curPatternElt.r-1)) ;
+        if( row > 0 ) {
+          curTn.top += gutterHeight;
+        }
+
+        curTn.left = (curPatternElt.c - 1) * Math.round(G.tn.defaultSize.getOuterWidth()*scaleFactor) + (curPatternElt.c - 1) * gutterWidth;
+
+        curTn.height = Math.round(curPatternElt.h * G.tn.defaultSize.getOuterHeight() * scaleFactor) + (curPatternElt.h - 1) * gutterHeight + (G.tn.labelHeight.get() * curPatternElt.h);
+        curTn.resizedContentHeight = curTn.height - G.tn.labelHeight.get() - borderHeight;
+
+        curTn.width = Math.round(curPatternElt.w * G.tn.defaultSize.getOuterWidth()*scaleFactor) + (curPatternElt.w - 1) * gutterWidth;
+        curTn.resizedContentWidth = curTn.width - borderWidth ;
+
+        curTn.row = row;
+        if( row == 0 ) {
+          h=Math.max(h, curTn.top + curTn.height);
+        }
+
+        n++;
+        if( n >= mosaicPattern.length ) {
+          // end pattern -> new line
+          n = 0;
+          row++;
+        }
+      }
+      
+      G.GOM.displayArea.width = (maxW - totalGutterWidth) * scaleFactor + totalGutterWidth;
+      return true;
+    }
+    
+    
+    
+    // --- GRID LAYOUT
+    function GallerySetLayoutGrid() {
+      var curPosX=      0,
+      curPosY=          0,   
+      areaWidth=        G.GOM.cache.areaWidth,
+      gutterWidth=      0,
+      gutterHeight=     G.tn.settings.GetResponsive('gutterHeight'),
+      maxCol=           NbThumbnailsPerRow(areaWidth),
+      w=                0,
+      cols=             [],
+      curCol=           0,
+      newAreaWidth =    areaWidth,
+      tnWidth=          G.tn.defaultSize.getOuterWidth();
+      var scaleFactor = 1;
+      var nbTn=         G.GOM.items.length;
+      var borderWidth   = G.tn.opt.Get('borderHorizontal') * 2;
+      var borderHeight  = G.tn.opt.Get('borderVertical') * 2;
+      
+      // retrieve gutter width
+      if( G.O.thumbnailAlignment == 'justified' ) {
+        maxCol = Math.min( maxCol, nbTn);
+        gutterWidth = (maxCol==1 ? 0 : (areaWidth-(maxCol*tnWidth))/(maxCol-1));
+      }
+      else {
+        gutterWidth = G.tn.settings.GetResponsive('gutterWidth');
+      }
+
+      // first loop to retrieve the real used width of the area (the evaluation is based on the content of the first line)
+      // Retrieve the real used width of the area (the evaluation is based on the content of the first line)
+      if( G.O.RTL || G.O.thumbnailAlignment == 'fillWidth' ) {
+        // scaled --> evaluate scale factor and number of columns
+        var totalGutterWidth = (maxCol-1) * gutterWidth;
+        scaleFactor = (areaWidth - totalGutterWidth) / (maxCol*tnWidth);
+        if( scaleFactor > 1 ) {
+          maxCol++; // add one column and re-evaluate the scale factor
+        }
+        totalGutterWidth = (maxCol-1) * gutterWidth;
+        scaleFactor = Math.min( (areaWidth - totalGutterWidth) / (maxCol*tnWidth), 1);   // no upscale
+        newAreaWidth = (maxCol*tnWidth) + totalGutterWidth;
+      }
+
+      
+      G.GOM.lastFullRow = 0 ;    // display at leat 1 row (even if not full)
+      var lastPosY = 0;
+      var row = 0;
+      
+      tnWidth = Math.round(tnWidth * scaleFactor);
+      var contentWidth = tnWidth - borderWidth;
+      var tnHeight = Math.round(G.tn.defaultSize.getOuterHeight() * scaleFactor) + G.tn.labelHeight.get();
+      var contentHeight = Math.round( G.tn.defaultSize.getOuterHeight() * scaleFactor) - borderHeight;
+      
+      // loop to position and to set size of all thumbnails
+      for( var i = 0; i < nbTn ; i++ ) {
+        if( curPosY == 0 ) {
+          curPosX = curCol * (tnWidth + gutterWidth)
+          cols[curCol] = curPosX;
+          w = curPosX + tnWidth;
+        }
+        else {
+          curPosX = cols[curCol];
+        }
+
+        var x = curPosX;
+        if( G.O.RTL ) {
+          x = parseInt(newAreaWidth) - curPosX - tnWidth;
+        }
+        
+        // MANDATORY : set thumbnail position AND size
+        var curTn=G.GOM.items[i];
+        curTn.top = curPosY;
+        curTn.left = x;
+        curTn.height = tnHeight;
+        curTn.width = tnWidth;
+        // image size
+        if( G.O.thumbnailAlignment == 'fillWidth' ) {
+          curTn.resizedContentWidth = contentWidth;
+          curTn.resizedContentHeight = contentHeight;
+        }
+        curTn.row = row;
+        lastPosY = curPosY;
+
+        curCol++;
+        if( curCol >= maxCol ){
+          // new line
+          curCol = 0;
+          curPosY += tnHeight + gutterHeight;
+          G.GOM.lastFullRow = row;
+          row++;
+        }
+      }
+      G.GOM.displayArea.width = w;
+
+      return true;
+    }
+
+
+
+
+    //----- Display the thumbnails according to the calculated layout
+    function GalleryDisplayPart1( forceTransition ) {
+      if( G.CSStransformName == null ) {
+        G.$E.conTn.css( 'left' , '0px');
+      }
+      else {
+        G.$E.conTn.css( G.CSStransformName , 'none');
+      }
+      // CacheViewport();
+    }
+    
+    function CacheViewport() {
+      G.GOM.cache.viewport = getViewport();
+      // G.GOM.cache.areaWidth = G.$E.conTnParent.width();
+      G.GOM.cache.areaWidth = G.$E.base.width();
+			
+			// position of the gallery container
+			// we use the position of the loadingbar because :
+			//    - the gallery may be wrong positioned due to one display animation currently running
+			//    - the loadingbar is never animated and positioned just before the gallery container
+      //G.GOM.cache.containerOffset = G.$E.conTnParent.offset();
+      if( !G.O.lightboxStandalone ) {
+        G.GOM.cache.containerOffset = G.$E.conLoadingB.offset();
+      }
+    }
+
+
+    
+    function GalleryDisplayPart2( forceTransition ) {
+      CacheViewport();
+
+      var nbTn = G.GOM.items.length;
+      G.GOM.itemsDisplayed = 0;
+      var threshold = 50;
+      var cnt = 0;    // counter for delay between each thumbnail display
+      
+
+      GalleryRenderGetInterval();
+      
+      for( var i = 0; i < nbTn ; i++ ) {
+        var curTn = G.GOM.items[i];
+        if( i >= G.GOM.displayInterval.from && cnt < G.GOM.displayInterval.len ) {
+          curTn.inDisplayArea = true;
+          if( forceTransition ) {
+            curTn.neverDisplayed = true;
+          }
+          G.GOM.itemsDisplayed++;
+          cnt++;
+        }
+        else{
+          curTn.inDisplayArea = false;
+        }
+      }
+
+      // bottom of the gallery (pagination, more button...)
+      GalleryBottomManage();
+
+      var tnToDisplay = [];
+      var tnToReDisplay = [];
+      
+      CacheViewport();
+      G.GOM.clipArea.top = -1;
+      cnt = 0 ;
+      var lastTnIdx = -1;
+      G.GOM.clipArea.height = 0;
+      // NOTE: loop always the whole GOM.items --> in case an already displayed thumbnail needs to be removed
+      for( var i = 0; i < nbTn ; i++ ) {
+        var curTn = G.GOM.items[i];
+        if( curTn.inDisplayArea ) {
+          if( G.GOM.clipArea.top == -1 ) {
+            G.GOM.clipArea.top = curTn.top;
+          }
+          if( (curTn.top - G.GOM.clipArea.top) <= -1 ) {
+            // with mosaic layout, the first thumbnail may not give the top position
+            G.GOM.clipArea.top = curTn.top;
+          }
+
+          G.GOM.clipArea.height = Math.max( G.GOM.clipArea.height, curTn.top-G.GOM.clipArea.top + curTn.height);
+        
+          if( curTn.neverDisplayed ) {
+						// thumbnail is not displayed -> check if in viewport to display or not
+            var top = G.GOM.cache.containerOffset.top + (curTn.top - G.GOM.clipArea.top);
+            // var left=containerOffset.left+curTn.left;
+            if( (top + curTn.height) >= (G.GOM.cache.viewport.t - threshold) && top <= (G.GOM.cache.viewport.t + G.GOM.cache.viewport.h + threshold) ) {
+              // build thumbnail
+              var item = G.I[curTn.thumbnailIdx];
+              if( item.$elt == null ) {
+                ThumbnailBuild( item, curTn.thumbnailIdx, i, (i+1) == nbTn );
+              }
+              tnToDisplay.push({idx:i, delay:cnt});
+              cnt++;
+            }
+          }
+          else {
+            tnToReDisplay.push({idx: i, delay: 0});
+          }
+          // G.GOM.itemsDisplayed++;
+          lastTnIdx = i;
+        }
+        else {
+          curTn.displayed = false;
+          var item = G.I[curTn.thumbnailIdx];
+          if( item.$elt != null ){
+            item.$elt.css({ opacity: 0, display: 'none' });
+          }
+        }
+      }
+
+      var areaWidth = G.$E.conTnParent.width();
+
+      // set gallery area really used size
+      // if( G.GOM.displayArea.width != G.GOM.displayAreaLast.width || G.GOM.displayArea.height != G.GOM.displayAreaLast.height ) {
+      if( G.GOM.displayArea.width != G.GOM.displayAreaLast.width || G.GOM.clipArea.height != G.GOM.displayAreaLast.height ) {
+        G.$E.conTn.width( G.GOM.displayArea.width ).height( G.GOM.clipArea.height );
+        G.GOM.displayAreaLast.width = G.GOM.displayArea.width;
+        G.GOM.displayAreaLast.height = G.GOM.clipArea.height;
+        // G.GOM.displayAreaLast.height=G.GOM.displayArea.height-G.GOM.clipArea.top;
+      }
+
+      if( areaWidth != G.$E.conTnParent.width() ) {
+        // gallery area width changed since layout calculation (for example when a scrollbar appeared)
+        // so we need re-calculate the layout before displaying the thumbnails
+        G.GOM.cache.areaWidth = G.$E.conTnParent.width();
+        GallerySetLayout();
+        GalleryDisplayPart1( forceTransition );
+        GalleryDisplayPart2( forceTransition );
+        return;
+      }
+
+      // counter of not displayed images (is displayed on the last thumbnail)
+      if( G.layout.support.rows ) {
+        if( G.galleryDisplayMode.Get() == 'ROWS' || (G.galleryDisplayMode.Get() == 'FULLCONTENT' && G.galleryLastRowFull.Get() && G.GOM.lastFullRow != -1) ){
+          if( lastTnIdx < (nbTn - 1) ) {
+            G.GOM.lastDisplayedIdxNew = lastTnIdx;
+          }
+          else {
+            G.GOM.lastDisplayedIdxNew =- 1;
+          }
+          // remove last displayed counter
+          if( G.GOM.lastDisplayedIdx != -1 ) {
+            var item = G.I[G.GOM.items[G.GOM.lastDisplayedIdx].thumbnailIdx];
+            item.$getElt('.nGY2GThumbnailIconsFullThumbnail').html('');
+          }
+        }
+      }
+
+      
+      // batch set position (and display animation) to all thumbnails
+      // first display newly built thumbnails
+			if( G.tn.opt.Get('displayOrder') == 'random' ) {
+				NGY2Tools.AreaShuffle( tnToDisplay );
+			}
+      var nbBuild = tnToDisplay.length;
+      G.GOM.thumbnails2Display=[];
+      for( var i = 0; i < nbBuild ; i++ ) {
+        // ThumbnailSetPosition(tnToDisplay[i].idx, tnToDisplay[i].delay+10);
+        ThumbnailSetPosition(tnToDisplay[i].idx, i);
+      }
+      
+      // then re-position already displayed thumbnails
+			if( G.tn.opt.Get('displayOrder') == 'random' ) {
+				NGY2Tools.AreaShuffle( tnToReDisplay );
+			}
+      var n = tnToReDisplay.length;
+      for( var i = 0; i < n ; i++ ) {
+        // ThumbnailSetPosition(tnToReDisplay[i].idx, nbBuild+1);
+        ThumbnailSetPosition(tnToReDisplay[i].idx, i);
+      }
+      
+      ThumbnailDisplayAnimBatch();
+
+      if( G.tn.opt.Get('displayTransition') == 'NONE' ) {
+        G.galleryResizeEventEnabled = true;
+        // GalleryThumbnailSliderBuildAndStart();  // image slider on last displayed thumbnail
+        TriggerCustomEvent('galleryDisplayed');
+      }
+      else {
+        // setTimeout(function() {
+        requestTimeout( function() {
+          // change value after the end of the display transistion of the newly built thumbnails
+          G.galleryResizeEventEnabled = true;
+          // GalleryThumbnailSliderBuildAndStart();  // image slider on last displayed thumbnail
+          TriggerCustomEvent('galleryDisplayed');
+        }, nbBuild * G.tn.opt.Get('displayInterval'));
+      }
+      
+    }
+    
+    
+    // Thumbnail: set the new position
+    function ThumbnailSetPosition( GOMidx, cnt ) {
+      var newTop=   0;
+      var curTn=    G.GOM.items[GOMidx];
+      var idx=      G.GOM.items[GOMidx].thumbnailIdx;
+      var item=     G.I[idx];
+    
+      if( curTn.neverDisplayed ) {
+        // thumbnail is built but has never been displayed (=first display)
+        var top = curTn.top - G.GOM.clipArea.top;
+        if( G.tn.opt.Get('stacks') > 0 ) {
+          // we have stacks -> do not display them here. They will be displayed at the end of the display animation
+          item.$elt.last().css({ display: 'block'});
+          item.$elt.css({ top: top , left: curTn.left });
+        }
+        else {
+          item.$elt.css({ display: 'block', top: top , left: curTn.left });
+        }
+        newTop=top;
+        
+        // display the image of the thumbnail when fully loaded
+        if( G.O.thumbnailWaitImageLoaded === true ) {
+          var gi_imgLoad = ngimagesLoaded( item.$getElt('.nGY2TnImg2') );
+          gi_imgLoad.on( 'progress', function( instance, image ) {
+            if( image.isLoaded ) {
+              var albumIdx = image.img.getAttribute('data-albumidx');
+              if( albumIdx == G.GOM.albumIdx ) {
+                // ignore event if not on current album
+                var idx = image.img.getAttribute('data-idx');
+                G.I[idx].ThumbnailImageReveal();
+              }
+            }
+          });
+        }
+        // display the thumbnail
+        ThumbnailAppear(GOMidx, cnt);
+
+        curTn.displayed = true;
+        curTn.neverDisplayed = false;
+      }
+      else {
+        var topOld = G.GOM.cache.containerOffset.top + item.top;
+        var top = G.GOM.cache.containerOffset.top + (curTn.top - G.GOM.clipArea.top);
+        newTop = curTn.top - G.GOM.clipArea.top;
+        var vp = G.GOM.cache.viewport;
+        if( G.O.thumbnailDisplayOutsideScreen || ( ( (topOld + curTn.height) >= (vp.t - vp.h) && topOld <= (vp.t + vp.h * 4) ) ||
+              ( (top + curTn.height) >= (vp.t - vp.h) && top <= (vp.t + vp.h * 4) ) )  ) {
+          // thumbnail positioned in enlarged viewport (viewport + 4 x viewport height) (v1.5: changed from 2 to 4)
+          if( curTn.displayed ) {
+            // thumbnail is displayed
+            if( item.top != curTn.top || item.left != curTn.left ) {
+              // set position
+              if( G.O.galleryResizeAnimation == true ) {
+                // with transition
+                var tweenable = new NGTweenable();
+                tweenable.tween({
+                  from:       { top: item.top, left: item.left,  height: item.height,  width: item.width },
+                  to:         { top: newTop,   left: curTn.left, height: curTn.height, width: curTn.width },
+                  attachment: { $e: item.$elt },
+                  duration:   100,
+                  delay:      cnt * G.tn.opt.Get('displayInterval') / 5,
+                  // easing:     'easeInOutQuad',
+                  easing:     'easeOutQuart',
+                  step:       function (state, att) {
+                    // window.ng_draf( function() {
+                      att.$e.css(state);
+                    // });
+                  },
+                  finish:     function (state, att) {
+                    var _this=this;
+                    // window.ng_draf( function() {
+                      _this.dispose();
+                    // });
+                  }
+                });
+              }
+              else {
+                // set position without transition
+                // item.$elt.css({ top: curTn.top , left: curTn.left });
+                item.$elt.css({ top: newTop , left: curTn.left });
+              }
+            }
+          }
+          else {
+            // re-display thumbnail
+            curTn.displayed = true;
+            // item.$elt.css({ display: 'block', top: curTn.top , left: curTn.left, opacity:1 });
+            item.$elt.css({ display: 'block', top: newTop, left: curTn.left, opacity: 1 });
+            ThumbnailAppearFinish(item);
+          }
+        }
+        else {
+          // undisplay thumbnail if not in viewport+margin --> performance gain
+          curTn.displayed = false;
+          item.$elt.css({ display: 'none'});
+        }
+      }
+      item.left = curTn.left;
+      item.top = newTop;
+      
+      // set new size if changed
+      if( item.width != curTn.width || item.height != curTn.height ) {
+        item.$elt.css({ width: curTn.width , height: curTn.height });
+        item.width = curTn.width;
+        item.height = curTn.height;
+        
+        // if( curTn.resizedContentWidth > 0 ) {
+        // resize also the content (=image)
+        if( item.resizedContentWidth != curTn.resizedContentWidth || item.resizedContentHeight != curTn.resizedContentHeight ) {
+          if( item.kind == 'albumUp' ) {
+            // item.$getElt('.nGY2GThumbnailAlbumUp').css({'height': curTn.resizedContentHeight, 'width': curTn.resizedContentWidth});
+          }
+          else {
+            item.$getElt('.nGY2GThumbnailImage').css({'height': curTn.resizedContentHeight, 'width': curTn.resizedContentWidth});
+
+            if( G.layout.engine == 'JUSTIFIED'  ) {
+              item.$getElt('.nGY2GThumbnailImg').css({'height': curTn.resizedContentHeight, 'width': curTn.resizedContentWidth});
+            }
+          }
+          item.resizedContentWidth = curTn.resizedContentWidth;
+          item.resizedContentHeight = curTn.resizedContentHeight;
+        }
+      }
+      
+      
+      // add counter of remaining (not displayed) images 
+      if( G.GOM.lastDisplayedIdxNew == GOMidx &&  G.layout.support.rows ) {
+        if( (G.galleryDisplayMode.Get() == 'ROWS' && G.galleryMaxRows.Get() > 0) || (G.galleryDisplayMode.Get() == 'FULLCONTENT' && G.galleryLastRowFull.Get() && G.GOM.lastFullRow != -1) ){
+          // number of items
+          var nb = G.GOM.items.length - GOMidx - 1;
+          if( item.albumID != '0' && G.O.thumbnailLevelUp ) {
+            nb--;
+          }
+
+          if( nb > 0 ) {
+            // display counter
+            if( G.O.thumbnailOpenInLightox || G.O.thumbnailSliderDelay > 0  ) {
+              item.$getElt('.nGY2GThumbnailIconsFullThumbnail').html( '+' + nb);
+            }
+
+            // if( G.layout.engine == 'GRID' && G.GOM.slider.hostItem != G.GOM.NGY2Item(GOMidx) ) {
+            // image slider on last displayed thumbnail
+            if( G.O.thumbnailLabel.get('position') != 'right' && G.O.thumbnailLabel.get('position') != 'left' ) {
+              if( G.GOM.slider.hostItem != G.GOM.NGY2Item(GOMidx) ) {
+
+                // set current slider back to initial content
+                GalleryThumbnailSliderSetContent( G.GOM.slider.hostItem );
+                // new slider
+                G.GOM.slider.hostIdx = GOMidx;
+                G.GOM.slider.hostItem = G.GOM.NGY2Item(GOMidx);
+                G.GOM.slider.nextIdx = GOMidx;
+                G.GOM.slider.currentIdx = GOMidx;
+                GalleryThumbnailSliderBuildAndStart();  // image slider on last displayed thumbnail
+                // GalleryThumbnailSliderSetNextContent();
+              }
+            }
+          }
+          else {
+            // reset slider content to initial content because all thumbnails are displayed
+            GalleryThumbnailSliderSetContent( G.GOM.slider.hostItem );
+            G.GOM.slider.hostIdx = -1;
+          }
+          
+          G.GOM.lastDisplayedIdx = GOMidx;
+        }
+      }
+
+    }
+    
+    // ---------------------
+    // replace image on last thumbnails with not displayed ones (mode ROWS or FULLCONTENT with galleryLastRowFull enabled)
+    // function GalleryLastThumbnailSlideImage() {
+    function GalleryThumbnailSliderBuildAndStart() {
+
+      if( G.O.thumbnailSliderDelay == 0 || G.GOM.slider.hostIdx == -1 ) {
+        return;
+      }
+      clearTimeout(G.GOM.slider.timerID);
+      
+      var item = G.GOM.slider.hostItem;
+
+      // dupplicate image layer -> for the next image
+      if( item.$getElt('.nGY2TnImgNext').length == 0 ) {
+        item.$getElt('.nGY2TnImg').clone().removeClass('nGY2TnImg').addClass('nGY2TnImgNext').insertAfter(item.$getElt('.nGY2TnImg'));
+        item.$getElt('.nGY2TnImgBack').clone().removeClass('nGY2TnImgBack').addClass('nGY2TnImgBackNext').insertAfter(item.$getElt('.nGY2TnImg', true));
+        item.$getElt('.nGY2GThumbnailImage', true); // important -> refresh the cache
+        item.$getElt('.nGY2GThumbnailImg', true);   // important -> refresh the cache
+      }
+
+      item.CSSTransformSet('.nGY2TnImgNext', 'translateX', '100%', true);
+      item.CSSTransformApply( '.nGY2TnImgNext' );
+      item.CSSTransformSet('.nGY2TnImgBackNext', 'translateX', '100%', true);
+      item.CSSTransformApply( '.nGY2TnImgBackNext' );
+
+      GalleryThumbnailSliderSetNextContent();
+      
+      // clearTimeout(G.GOM.slider.timerID);
+      // G.GOM.slider.timerID = setTimeout(function(){ GalleryThumbnailSliderStartTransition() }, G.O.thumbnailSliderDelay);
+      G.GOM.slider.timerID = requestTimeout(function(){ GalleryThumbnailSliderStartTransition() }, G.O.thumbnailSliderDelay);
+    }
+
+    
+    function GalleryThumbnailSliderSetNextContent() {
+
+      G.GOM.slider.nextIdx++;
+      if( G.GOM.slider.nextIdx >= G.GOM.items.length ) {
+        G.GOM.slider.nextIdx = G.GOM.slider.hostIdx;
+      }
+      
+      // new image
+      var newItem = G.GOM.NGY2Item(G.GOM.slider.nextIdx);
+      var imgBlurred = G.emptyGif;
+      var bgImg = "url('" + G.emptyGif + "')";
+      if( newItem.imageDominantColors != null ) {
+        imgBlurred = newItem.imageDominantColors;
+        bgImg = "url('" + newItem.imageDominantColors + "')";
+      }
+      G.GOM.slider.hostItem.$getElt('.nGY2TnImgBackNext', true).css({'background-image': bgImg, opacity: 1 });
+      G.GOM.slider.hostItem.$getElt('.nGY2TnImgNext', true).css({ 'background-image': "url('" + newItem.thumbImg().src + "')", opacity: 1 });
+      G.GOM.slider.hostItem.$getElt('.nGY2TnImgNext .nGY2GThumbnailImg', true).attr('src', newItem.thumbImg().src );
+      
+
+    }
+    
+    // thumbnail slider - transition from one image to the next one
+    function GalleryThumbnailSliderStartTransition() {
+      
+      if( G.GOM.slider.hostItem.$getElt() != null ) {
+
+        // slider transition
+        var tweenable = new NGTweenable();
+        G.GOM.slider.tween = tweenable;
+        tweenable.tween({
+          from:         { 'left': 100 },
+          to:           { 'left': 0 },
+          duration:     800,
+          delay:        0,
+          // easing:       'easeInOutQuad',
+          easing:       'easeOutQuart',
+          
+          step: function (state) {
+            if( G.GOM.slider.hostItem.$getElt() == null ) {
+              // the thumbnail may have been destroyed since the start of the animation
+              G.GOM.slider.tween.stop(false);
+              return;
+            }
+
+            // window.ng_draf( function() {
+              // slide current content
+              G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgBack', 'translateX', -(100 - state.left) + '%');
+              G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgBack' );
+              G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImg', 'translateX', -(100 - state.left) + '%');
+              G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImg' );
+
+              // slide new content
+              G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgBackNext', 'translateX', state.left + '%');
+              G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgBackNext' );
+              G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgNext', 'translateX', state.left + '%');
+              G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgNext' );
+            // });
+
+            
+          },
+          finish: function (state) {
+            if( G.GOM.slider.hostItem.$getElt() == null ) {
+              // the thumbnail may be destroyed since the start of the animation
+              return;
+            }
+           
+            if( G.GOM.NGY2Item(G.GOM.slider.nextIdx) == null ) { return; } // item does not exist anymore
+            
+            // window.ng_draf( function() {
+              // set new content as current content
+              GalleryThumbnailSliderSetContent( G.GOM.NGY2Item(G.GOM.slider.nextIdx) );
+              G.GOM.slider.currentIdx = G.GOM.slider.nextIdx;
+              GalleryThumbnailSliderSetNextContent();
+              
+              clearTimeout(G.GOM.slider.timerID);
+              // G.GOM.slider.timerID=setTimeout(function(){ GalleryThumbnailSliderStartTransition() }, G.O.thumbnailSliderDelay);
+              G.GOM.slider.timerID = requestTimeout(function(){ GalleryThumbnailSliderStartTransition() }, G.O.thumbnailSliderDelay);
+            // });
+          }
+        });
+      }
+    }
+    
+    // set main content of the thumbnail hosting the slider
+    // hide the elements for the next content of the slider
+    function GalleryThumbnailSliderSetContent( ngy2itemContent ) {
+        if( G.GOM.slider.hostIdx == -1 ) { return; }
+        
+        if( G.GOM.slider.tween != null ) {
+          if( G.GOM.slider.tween._isTweening  == true ) {
+            G.GOM.slider.tween.stop(false);
+          }
+        }
+
+        var bgImg = "url('" + G.emptyGif + "')";
+        if( ngy2itemContent.imageDominantColors != null ) {
+          bgImg = "url('" + ngy2itemContent.imageDominantColors + "')";
+        }
+        G.GOM.slider.hostItem.$getElt('.nGY2TnImgBack').css('background-image', bgImg);
+        G.GOM.slider.hostItem.$getElt('.nGY2TnImg').css('background-image', "url('" + ngy2itemContent.thumbImg().src + "')" );
+        G.GOM.slider.hostItem.$getElt('.nGY2TnImg .nGY2GThumbnailImg').attr('src', ngy2itemContent.thumbImg().src );
+        
+        G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgBack', 'translateX', '0');
+        G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgBack' );
+        G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImg', 'translateX', '0');
+        G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImg' );
+
+        // place the containers for the next image slider outside of the thumbnail (=hidden)
+        G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgBackNext', 'translateX', '100%', true);
+        G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgBackNext' );
+        G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgNext', 'translateX', '100%', true);
+        G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgNext' );
+
+        // set new title and description
+        if( G.O.thumbnailLabel.get('display') == true ) {
+          var icons = G.O.icons.thumbnailAlbum;
+          if( ngy2itemContent.kind != 'album' ) {
+            icons = G.O.icons.thumbnailImage;
+          }
+          G.GOM.slider.hostItem.$getElt('.nGY2GThumbnailTitle').html(icons + getThumbnailTitle(ngy2itemContent));
+          G.GOM.slider.hostItem.$getElt('.nGY2GThumbnailDescription').html(icons + getTumbnailDescription(ngy2itemContent));
+        }
+      }
+    
+   
+    
+    // Compute the height of the label part of a thumbnail (title+description, both single line)
+    function ThumbnailGetLabelHeight() {
+      var newElt = [],
+      newEltIdx =  0;
+
+      // if( G.O.thumbnailLabel.get('display') == false && G.tn.toolbar.getWidth(item) <= 0 ) {
+      if( G.O.thumbnailLabel.get('display') == false  ) {
+        return 0;
+      }
+      
+      var desc='';
+      if( G.O.thumbnailLabel.get('displayDescription') == true ) {
+        desc = 'aAzZjJ';
+      }
+
+      // visibility set to hidden
+      newElt[newEltIdx++] = '<div class="nGY2GThumbnail ' + G.O.theme + '" style="display:block;visibility:hidden;position:absolute;top:-9999px;left:-9999px;" ><div class="nGY2GThumbnailSub">';
+      if( G.O.thumbnailLabel.get('display') == true ) {
+        // Labels: title and description
+        newElt[newEltIdx++] = '  <div class="nGY2GThumbnailLabel" '+ G.tn.style.getLabel() +'>';
+        newElt[newEltIdx++] = '    <div class="nGY2GThumbnailAlbumTitle" '+G.tn.style.getTitle()+'>aAzZjJ</div>';
+        if( G.O.thumbnailLabel.get('displayDescription') == true ) {
+          newElt[newEltIdx++] = '    <div class="nGY2GThumbnailDescription" '+G.tn.style.getDesc()+'>'+'aAzZjJ'+'</div>';
+        }
+        newElt[newEltIdx++] = '  </div>';
+      }
+      
+      newElt[newEltIdx++] = '</div></div>';
+    
+      var $newDiv = jQuery(newElt.join('')).appendTo(G.$E.conTn);
+      var h = $newDiv.find('.nGY2GThumbnailLabel').outerHeight(true);
+      $newDiv.remove();
+
+      return h;
+    }
+    
+    function ThumbnailBuildStacks( bgColor ) {
+      var ns=G.tn.opt.Get('stacks');
+      if( ns == 0 ) { return ''; }
+     
+      var s='';
+      for( var i=0; i<ns; i++ ) {
+        s='<div class="nGY2GThumbnailStack " style="display:none;'+bgColor+'"></div>'+s;
+      }
+      return s;
+    }
+    
+    //----- Build one UP thumbnail (=navigation thumbnail)
+    function ThumbnailBuildAlbumpUp( item, idx, GOMidx ) {
+      var newElt = [],
+      newEltIdx = 0;
+      
+      var mp = '';
+      if( G.O.thumbnailOpenInLightox === false ) {
+        mp = 'cursor:default;'
+      }
+      
+      newElt[newEltIdx++] = ThumbnailBuildStacks('') + '<div class="nGY2GThumbnail" style="display:none;opacity:0;' + mp + '" >';
+      newElt[newEltIdx++] = '  <div class="nGY2GThumbnailSub">';
+
+      var h=G.tn.defaultSize.getHeight(),
+      w=G.tn.defaultSize.getWidth();
+
+      newElt[newEltIdx++] = '    <div class="nGY2GThumbnailImage" style="width:'+w+'px;height:'+h+'px;"><img class="nGY2GThumbnailImg" src="'+G.emptyGif+'" alt="" style="max-width:'+w+'px;max-height:'+h+'px;" ></div>';
+      // newElt[newEltIdx++] = '    <div class="nGY2GThumbnailAlbumUp" style="width:'+w+'px;height:'+h+'px;">'+G.O.icons.thumbnailAlbumUp+'</div>';
+      newElt[newEltIdx++] = '    <div class="nGY2GThumbnailAlbumUp" >'+G.O.icons.thumbnailAlbumUp+'</div>';
+      newElt[newEltIdx++] = '  </div>';
+      newElt[newEltIdx++] = '</div>';
+      
+      var $newDiv = jQuery(newElt.join('')).appendTo(G.$E.conTn); //.animate({ opacity: 1},1000, 'swing');  //.show('slow'); //.fadeIn('slow').slideDown('slow');
+      
+      item.$elt = $newDiv;
+      $newDiv.data('index', GOMidx);
+      item.$getElt('.nGY2GThumbnailImg').data('index', GOMidx);
+      
+      return;
+    }
+
+    
+    //----- Build one thumbnail
+    function ThumbnailBuild( item, idx, GOMidx, lastOne ) {
+      item.eltTransform =  [];
+      item.eltFilter =     [];
+      item.hoverInitDone = false;
+      item.$Elts =         [];
+
+      if( item.kind == 'albumUp' ) {
+        ThumbnailBuildAlbumpUp( item, idx, GOMidx);
+        return;
+      }
+
+      var newElt = [],
+      newEltIdx = 0;
+
+      var mp = '';
+      if( G.O.thumbnailOpenInLightox === false ) {
+        mp = 'cursor:default;'
+      }
+
+      // var src = encodeURI(item.thumbImg().src),
+      var src = (item.thumbImg().src).replace(/'/g, "%27"),   // replace single quote with %27
+      sTitle = getThumbnailTitle(item);
+
+      // image background -> visible during image download
+      var bg = '';
+      var bgImg = "background-image: url('" + G.emptyGif + "');";
+      if( item.imageDominantColors != null ) {
+        // dominant colorS (blurred preview image)
+        bgImg = "background-image: url('" + item.imageDominantColors + "');";
+      }
+      else {
+        // dominant color -> background color
+        if( item.imageDominantColor != null ) {
+          bg = 'background-color:' + item.imageDominantColor + ';';
+        }
+        else {
+          bgImg = '';
+        }
+      }
+
+      var op = 'opacity:1;';
+      if( G.O.thumbnailWaitImageLoaded == true ) {
+        op = 'opacity:0;';
+      }
+
+      // ##### thumbnail containers  (with stacks)
+      newElt[newEltIdx++] = ThumbnailBuildStacks(bg) + '<div class="nGY2GThumbnail nGY2GThumbnail_'+G.GOM.curNavLevel+'" style="display:none;opacity:0;' + mp + '"><div class="nGY2GThumbnailSub ' + ( G.O.thumbnailSelectable && item.selected ? "nGY2GThumbnailSubSelected" : "" ) + '">';
+
+      
+      // image size
+      var w = G.tn.settings.getW();
+      var h = G.tn.settings.getH();
+      if( G.tn.settings.getMosaic() !== null ) {
+        // mosaic layout -> 
+        w = G.GOM.items[GOMidx].width;
+        h = G.GOM.items[GOMidx].height;
+      }
+
+      var bgSize = 'contain';
+      if( G.tn.opt.Get('crop') ) {
+        bgSize = 'cover';             // thumbnail image will be cropped to fit in the thumbnail (no black border)
+      }
+      
+      // ##### layer for image background (color, dominant color, blurred preview)
+      var s1 = "position: absolute; top: 0px; left: 0px; width:" + w + "px; height:" + h + "px;"+ bg + bgImg + " background-position: center center;  background-repeat: no-repeat; background-size:" + bgSize + "; overflow: hidden;";
+      newElt[newEltIdx++]='<div class="nGY2GThumbnailImage nGY2TnImgBack" style="' + s1 + '"></div>';
+      
+      // #### layer for image 
+      var s2 = op + "position: absolute; top: 0px; left: 0px; width:" + w + "px; height:" + h + "px; background-image: url('" + src + "'); background-position: center center; background-repeat: no-repeat; background-size:" + bgSize + "; overflow: hidden;";
+      newElt[newEltIdx++]='<div class="nGY2GThumbnailImage nGY2TnImg" style="' + s2 + '">';
+      newElt[newEltIdx++]='  <img class="nGY2GThumbnailImg nGY2TnImg2" src="' + src + '" alt="' + sTitle + '" style="opacity:0;" data-idx="' + idx + '" data-albumidx="' + G.GOM.albumIdx + '" >';
+      newElt[newEltIdx++]='</div>';
+      
+      // ##### layer for user customization purposes
+      newElt[newEltIdx++]='<div class="nGY2GThumbnailCustomLayer"></div>';
+
+      // ##### layer for labels (title + description and their icons)
+      if( G.O.thumbnailLabel.get('display') == true ) {
+        // Labels: title and description
+        newElt[newEltIdx++]= '  <div class="nGY2GThumbnailLabel" '+ G.tn.style.getLabel(item) + '>';
+        if( item.kind == 'album' ) {
+          // album kind
+          newElt[newEltIdx++]= '    <div class="nGY2GThumbnailTitle nGY2GThumbnailAlbumTitle" ' + G.tn.style.getTitle() + '>' + G.O.icons.thumbnailAlbum + sTitle + '</div>';
+        }
+        else {
+          // image/media kind
+          newElt[newEltIdx++]= '    <div class="nGY2GThumbnailTitle nGY2GThumbnailImageTitle" ' + G.tn.style.getTitle() + '>' + G.O.icons.thumbnailImage + sTitle + '</div>';
+        }
+        newElt[newEltIdx++]= '    <div class="nGY2GThumbnailDescription" ' + G.tn.style.getDesc() + '>' + getTumbnailDescription(item) + '</div>';
+        newElt[newEltIdx++]= '  </div>';
+      }
+
+      // ##### layer for tools
+      newElt[newEltIdx++] = ThumbnailBuildTools(item, lastOne);
+      
+      // close containers
+      newElt[newEltIdx++]='</div></div>';
+      
+      var $newDiv =jQuery(newElt.join('')).appendTo(G.$E.conTn);
+
+      item.$elt=$newDiv;
+      $newDiv.data('index',GOMidx);
+      item.$getElt('.nGY2GThumbnailImg').data('index',GOMidx);
+      
+      // Custom init function
+      var fu=G.O.fnThumbnailInit;
+      if( fu !== null ) {
+        typeof fu == 'function' ? fu($newDiv, item, GOMidx) : window[fu]($newDiv, item, GOMidx);
+      }
+
+      if( item.title != 'image gallery by nanogallery2 [build]' ) {
+        ThumbnailOverInit(GOMidx);
+      }
+      
+      return ;
+    }
+
+    
+    // Thumbnail layer for tools (toolbars and counter)
+    function ThumbnailBuildTools( item, lastThumbnail ) {
+    
+      // toolbars
+      var tb = ThumbnailBuildToolbarOne(item, 'topLeft') + ThumbnailBuildToolbarOne(item, 'topRight') + ThumbnailBuildToolbarOne(item, 'bottomLeft') + ThumbnailBuildToolbarOne(item, 'bottomRight');
+      
+      // counter of not displayed images
+      tb += '<div class="nGY2GThumbnailIconsFullThumbnail"></div>';
+
+      return tb;
+    }
+    
+    function ThumbnailBuildToolbarOne( item, position ) {
+      var toolbar = '';
+      var tb =      G.tn.toolbar.get(item);
+      var width =   { xs:0, sm:1, me:2, la:3, xl:4 };
+      var cnt =     0;
+      
+      if( tb[position] != '' ) {
+        var pos='top: 0; right: 0; text-align: right;';     // 'topRight' and default
+        switch( position ) {
+          case 'topLeft':
+            pos = 'top: 0; left: 0; text-align: left;';
+            break;
+          case 'bottomRight':
+            pos = 'bottom: 0; right: 0; text-align: right;';
+            break;
+          case 'bottomLeft':
+            pos = 'bottom: 0; left: 0; text-align: left;';
+            break;
+        }
+        
+        toolbar += '  <ul class="nGY2GThumbnailIcons" style="' + pos + '">';
+        
+        var icons = tb[position].split(',');
+        var nb = icons.length;
+        for( var i = 0; i < nb; i++ ) {
+          var icon = icons[i].replace(/^\s*|\s*$/, '');   //trim trailing/leading whitespace
+
+          var minWidth = icon.substring(0,2).toLowerCase();
+          var tIcon = icon;
+          var display = true;
+          if( /xs|sm|me|la|xl/i.test(minWidth) ) {
+            // check visbility (depending on screen width)
+            if( width[minWidth] > width[G.GOM.curWidth] ) {
+              display = false;
+            }
+            tIcon = icon.substring(2);
+          }
+          
+          if( display ) {
+            var sp=(i+1<nb ? '&nbsp;' :'');
+            switch( tIcon ) {
+              case 'COUNTER':
+                if( item.kind == 'album' ) {
+                  toolbar += '    <li class="nGY2GThumbnailIcon" data-ngy2action="">';
+                  toolbar += '      <div class="nGY2GThumbnailIconImageCounter"></div>';
+                  toolbar += '      <div class="nGY2GThumbnailIconText">' + G.O.icons.thumbnailCounter+Math.max((item.getContentLength(false)),item.numberItems) + sp + '</div>';
+                  toolbar += '    </li>';
+                  cnt++;
+                }
+                break;
+              case 'COUNTER2':
+                if( item.kind == 'album' ) {
+                  toolbar += '    <li class="nGY2GThumbnailIcon" data-ngy2action="">';
+                  toolbar += '      <div class="nGY2GThumbnailIconTextBadge">' + G.O.icons.thumbnailCounter+Math.max((item.getContentLength(false)),item.numberItems) + sp + '</div>';
+                  toolbar += '    </li>';
+                  cnt++;
+                }
+                break;
+              case 'SHARE':
+                toolbar += '    <li class="nGY2GThumbnailIcon" data-ngy2action="' + tIcon + '">';
+                toolbar += '      <div>' + G.O.icons.thumbnailShare + '</div>';
+                toolbar += '    </li>';
+                cnt++;
+                break;
+              case 'DOWNLOAD':
+                toolbar += '    <li class="nGY2GThumbnailIcon" data-ngy2action="' + tIcon + '">';
+                toolbar += '      <div>' + G.O.icons.thumbnailDownload + '</div>';
+                toolbar += '    </li>';
+                cnt++;
+                break;
+              case 'INFO':
+                toolbar += '    <li class="nGY2GThumbnailIcon" data-ngy2action="' + tIcon + '">';
+                toolbar += '      <div>' + G.O.icons.thumbnailInfo + '</div>';
+                toolbar += '    </li>';
+                cnt++;
+                break;
+              case 'SHOPPINGCART':
+                toolbar += '    <li class="nGY2GThumbnailIcon" data-ngy2action="' + tIcon + '">';
+                // toolbar += '      <div>' + G.O.icons.thumbnailShoppingcart + '</div>';
+                toolbar += ThumbnailBuildToolbarOneCart( item );
+                
+                toolbar += '    </li>';
+                cnt++;
+                break;
+              case 'DISPLAY':
+                toolbar += '    <li class="nGY2GThumbnailIcon" data-ngy2action="DISPLAY">';
+                toolbar += '      <div class="nGY2GThumbnailIconImageShare">' + G.O.icons.thumbnailDisplay + '</div>';
+                toolbar += '    </li>';
+                cnt++;
+                break;
+              case 'CUSTOM1':
+              case 'CUSTOM2':
+              case 'CUSTOM3':
+              case 'CUSTOM4':
+              case 'CUSTOM5':
+              case 'CUSTOM6':
+              case 'CUSTOM7':
+              case 'CUSTOM8':
+              case 'CUSTOM9':
+              case 'CUSTOM10':
+                var cust = tIcon.replace('CUSTOM', '');
+                toolbar += '    <li class="nGY2GThumbnailIcon" data-ngy2action="' + tIcon.toLowerCase() + '">';
+                toolbar += '      <div class="nGY2GThumbnailIconImageShare">' + G.O.icons['thumbnailCustomTool' + cust] + '</div>';
+                toolbar += '    </li>';
+                cnt++;
+                break;
+              case 'FEATURED':
+                if( item.featured === true ) {
+                  toolbar += '    <li class="nGY2GThumbnailIcon" data-ngy2action="">';
+                  toolbar += '      <div class="nGY2GThumbnailIconImageFeatured">' + G.O.icons.thumbnailFeatured + '</div>';
+                  toolbar += '    </li>';
+                  cnt++;
+                }
+                break;
+              case 'SELECT':
+                if( G.O.thumbnailSelectable == true ) {
+                  toolbar += '    <li class="nGY2GThumbnailIcon" data-ngy2action="TOGGLESELECT">';
+                  if( item.selected === true ) {
+                    toolbar += '      <div class="nGY2GThumbnailIconImageSelect nGY2ThumbnailSelected">' + G.O.icons.thumbnailSelected + '</div>';
+                  }
+                  else {
+                    toolbar += '      <div class="nGY2GThumbnailIconImageSelect nGY2ThumbnailUnselected">' + G.O.icons.thumbnailUnselected + '</div>';
+                  }
+                  toolbar += '    </li>';
+                  cnt++;
+                }
+                break;
+            }
+          }
+        }
+        toolbar += '  </ul>';
+      }
+      
+      if( cnt > 0 ) {
+        return toolbar;
+      }
+      else {
+        return '';
+      }
+    }
+    
+    // CART ICON AND COUNTER
+    function ThumbnailBuildToolbarOneCart( item ) {
+      var q = 0;
+      
+      var id = item.GetID()
+      for( var i=0; i<G.shoppingCart.length; i++ ) {
+        if( G.I[G.shoppingCart[i].idx].GetID() == id ) {
+          q = G.shoppingCart[i].qty;
+        }
+      }
+      if( q == 0 ) {
+        q = '';
+      }
+
+      return '      <div>' + G.O.icons.thumbnailShoppingcart + q + '</div>';
+    }
+    function ThumbnailBuildToolbarOneCartUpdate( item ) {
+      var $e = item.$elt;
+
+      if( $e != null ) {
+        var $q = $e.find('*[data-ngy2action="SHOPPINGCART"]');
+        if( $q !== undefined ) {
+          $q.html( ThumbnailBuildToolbarOneCart( item ) );
+        }
+      }
+    }
+    
+    function getThumbnailTitle( item ) {
+    
+      var sTitle = item.title;
+      if( G.O.thumbnailLabel.get('display') == true ) {
+        if( sTitle === undefined || sTitle.length == 0 ) { sTitle = '&nbsp;'; }
+
+        if( G.i18nTranslations.thumbnailImageTitle != '' ) {
+          sTitle = G.i18nTranslations.thumbnailImageTitle;
+        }
+        var ml = G.O.thumbnailLabel.get('titleMaxLength');
+        if( ml > 3 && sTitle.length > ml ){
+          sTitle = sTitle.substring(0, ml) + '...';
+        }
+      }
+      
+      return sTitle;
+    }
+
+    function getTumbnailDescription( item ) {
+      var sDesc = '';
+      if( G.O.thumbnailLabel.get('displayDescription') == true ) { 
+        if( item.kind == 'album' ) {
+          if( G.i18nTranslations.thumbnailImageDescription != '' ) {
+            sDesc = G.i18nTranslations.thumbnailAlbumDescription;
+          }
+          else {
+            sDesc = item.description;
+          }
+        }
+        else {
+          if( G.i18nTranslations.thumbnailImageDescription != '' ) {
+            sDesc = G.i18nTranslations.thumbnailImageDescription;
+          }
+          else {
+            sDesc = item.description;
+          }
+        }
+        var ml = G.O.thumbnailLabel.get('descriptionMaxLength');
+        if( ml > 3 && sDesc.length > ml ){
+          sDesc = sDesc.substring(0, ml) + '...';
+        }
+        if( sDesc.length == 0 ) {
+          sDesc = '&nbsp;';
+        }
+      }
+      
+      return sDesc;
+    }
+
+    
+    
+    // Retrieve the maximum number of thumbnails that fits in one row
+    function NbThumbnailsPerRow( areaWidth ) {
+      var tnW = G.tn.defaultSize.getOuterWidth();
+      
+      var nbMaxTn = 0;
+      if( G.O.thumbnailAlignment == 'justified' ) {
+        nbMaxTn = Math.floor((areaWidth)/(tnW));
+      }
+      else {
+        nbMaxTn = Math.floor((areaWidth + G.tn.settings.GetResponsive('gutterWidth'))/(tnW + G.tn.settings.GetResponsive('gutterWidth')));
+      }
+      
+      if(  G.O.maxItemsPerLine >0 && nbMaxTn >  G.O.maxItemsPerLine ) {
+        nbMaxTn = G.O.maxItemsPerLine;
+      }
+      
+      if( nbMaxTn < 1 ) { nbMaxTn = 1; }
+      
+      return nbMaxTn
+    }
+  
+    // Thumbnail display animation
+    function ThumbnailAppear( n, cnt ) {
+      var curTn = G.GOM.items[n];
+      var item = G.I[curTn.thumbnailIdx];
+
+    
+      if( G.tn.opt.Get('displayTransition') == 'NONE' ) {
+        item.$elt.css({ opacity: 1 });
+        ThumbnailAppearFinish( item );
+      }
+      else {
+        if( item.$elt == null ) { return; }
+        var top = G.GOM.cache.containerOffset.top + ( curTn.top - G.GOM.clipArea.top );
+        var vp = G.GOM.cache.viewport;
+        if( (top + (curTn.top - G.GOM.clipArea.top)) >= (vp.t - 50) && top <= (vp.t + vp.h + 50) ) {
+          // display animation only if in the current viewport
+          var delay = cnt * G.tn.opt.Get('displayInterval');
+          if( G.tn.opt.Get('displayTransition') == 'CUSTOM' ) {
+            if( G.GOM.curNavLevel == 'lN' ) {
+              G.O.fnThumbnailDisplayEffect(item.$elt, item, n, delay);
+            }
+            else {
+              G.O.fnThumbnailL1DisplayEffect(item.$elt, item, n, delay);
+            }
+          }
+          else {
+            G.GOM.thumbnails2Display.push({itm: item, d: delay});
+            // ThumbnailDisplayAnim2(item, delay);
+          }
+          return;
+        }
+        else {
+          item.$elt.css({ opacity: 1 });
+          ThumbnailAppearFinish(item);
+        }
+      }
+    }
+    
+    
+    // displays thumbnail stacks at the end of the display animation
+    function ThumbnailAppearFinish( item ) {
+    
+      // add stacks
+      var ns = G.tn.opt.Get('stacks');
+      if( ns > 0 ) {
+        // display stacks
+        item.$elt.css({ display: 'block'});
+        var o = 0.9;
+        // set stack opacity
+        for( var i = ns-1; i>=0; i-- ) {
+          item.$elt.eq(i).css('opacity', o);
+          o = o - 0.2;
+        }
+
+      }
+    }
+    
+
+    function ThumbnailDisplayAnim2( item, delay ) {
+      function randomIntFromInterval(min,max) {
+        return Math.floor(Math.random()*(max-min+1)+min);
+      }
+      var oFrom = {};
+      var oTo = {};
+    
+      switch (G.tn.opt.Get('displayTransition')) {
+        case 'RANDOMSCALE':
+          var scales = [0.95, 1, 1.05, 1.1];
+          var zi = [1, 2, 3, 4];
+          
+          var r = randomIntFromInterval(0,3);
+          while( r == G.GOM.lastRandomValue ) {
+            r = randomIntFromInterval(0,3);
+          }
+          G.GOM.lastRandomValue = r;
+          var f = scales[r];
+          // item.$elt.css({ 'z-index': G.GOM.lastZIndex+zi[r], 'box-shadow': '-1px 2px 5px 1px rgba(0, 0, 0, 0.7)' });
+          item.$elt.css({ 'z-index': G.GOM.lastZIndex+zi[r], 'box-shadow': '0px 0px 5px 3px rgba(0,0,0,0.74)' });
+          
+          oFrom = { scale: 0.5, opacity:0 };
+          oTo =   { scale: f,   opacity:1 };
+          break;
+
+        case 'SCALEUP':
+          var f = G.tn.opt.Get('displayTransitionStartVal');
+          if( f == 0 ) { f = 0.6; }   // default value
+          oFrom = { scale: f, opacity: 0 };
+          oTo =   { scale: 1, opacity: 1 };
+          break;
+
+        case 'SCALEDOWN':
+          var f = G.tn.opt.Get('displayTransitionStartVal');
+          if( f == 0 ) { f=1.3; }   // default value
+          oFrom = { scale: f, opacity: 0 };
+          oTo =   { scale: 1, opacity: 1 };
+          break;
+        case 'SLIDEUP':
+          var f = G.tn.opt.Get('displayTransitionStartVal');
+          if( f == 0 ) { f=50; }   // default value
+          oFrom = { opacity: 0, translateY: f };
+          oTo =   { opacity: 1, translateY: 0 };
+          break;
+        case 'SLIDEDOWN':
+          var f=G.tn.opt.Get('displayTransitionStartVal');
+          if( f == 0 ) { f=-50; }   // default value
+          oFrom = { opacity: 0, translateY: f };
+          oTo =   { opacity: 1, translateY: 0 };
+          break;
+        case 'FLIPUP':
+          var f=G.tn.opt.Get('displayTransitionStartVal');
+          if( f == 0 ) { f=100; }   // default value
+          oFrom = { opacity: 0, translateY: f, rotateX: 45 };
+          oTo =   { opacity: 1, translateY: 0, rotateX: 0  };
+          break;
+          
+        case 'FLIPDOWN':
+          var f=G.tn.opt.Get('displayTransitionStartVal');
+          if( f == 0 ) { f=-100; }   // default value
+          oFrom = { opacity: 0, translateY: f, rotateX: -45 };
+          oTo =   { opacity: 1, translateY: 0, rotateX: 0 };
+          break;
+        case 'SLIDEUP2':
+          var f = G.tn.opt.Get('displayTransitionStartVal');
+          if( f == 0 ) { f=100; }   // default value
+          oFrom = { opacity: 0, translateY: f, rotateY: 40 };
+          oTo =   { opacity: 1, translateY: 0, rotateY: 0  };
+          break;
+        case 'IMAGESLIDEUP':
+          var f = G.tn.opt.Get('displayTransitionStartVal');
+          if( f == 0 ) { f=100; }   // default value
+          oFrom = { opacity: 0, top: '100%' };
+          oTo =   { opacity: 1, top: '0%'  };
+          break;
+        case 'SLIDEDOWN2':
+          var f=G.tn.opt.Get('displayTransitionStartVal');
+          if( f == 0 ) { f=-100; }   // default value
+          oFrom = { opacity: 0, translateY: f, rotateY: 40 };
+          oTo =   { opacity: 1, translateY: 0, rotateY: 0  };
+          break;
+        case 'SLIDERIGHT':
+          var f=G.tn.opt.Get('displayTransitionStartVal');
+          if( f == 0 ) { f=-150; }   // default value
+          oFrom = { opacity: 0, translateX: f };
+          oTo =   { opacity: 1, translateX: 0 };
+          break;
+
+        case 'SLIDELEFT':
+          var f=G.tn.opt.Get('displayTransitionStartVal');
+          if( f == 0 ) { f=150; }   // default value
+          oFrom = { opacity: 0, translateX: f };
+          oTo =   { opacity: 1, translateX: 0 };
+          break;
+          
+        case 'FADEIN':
+          oFrom = { opacity: 0 };
+          oTo =   { opacity: 1 };
+          break;
+          
+          
+      }
+    
+      var tweenable = new NGTweenable();
+      tweenable.tween({
+        from:         oFrom,
+        to:           oTo,
+        attachment:   { $e:item.$elt, item: item, tw: tweenable },
+        delay:        delay,
+        duration:     G.tn.opt.Get('displayTransitionDuration'),
+        easing:       G.tn.opt.Get('displayTransitionEasing'),
+        step:         function (state, att) {
+          window.requestAnimationFrame( function() {
+            if( att.item.$elt === null ) {  // the thumbnail may have been destroyed since the start of the animation
+              att.tw.stop(false);
+              return;
+            }
+            switch (G.tn.opt.Get('displayTransition')) {
+              case 'RANDOMSCALE':
+                att.$e.css( G.CSStransformName , 'scale(' + state.scale + ')').css('opacity', state.opacity);
+                break;
+              case 'SCALEUP':
+                att.$e.css( G.CSStransformName , 'scale('+state.scale+')').css('opacity',state.opacity);
+                break;
+              case 'SCALEDOWN':
+                att.item.$elt.last().css('opacity', state.opacity);
+                att.item.CSSTransformSet('.nGY2GThumbnail', 'scale', state.scale);
+                att.item.CSSTransformApply('.nGY2GThumbnail');
+                break;
+              case 'SLIDEUP':
+                att.item.$elt.css('opacity', state.opacity);
+                att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px, '+state.translateY + 'px');
+                att.item.CSSTransformApply('.nGY2GThumbnail');
+                break;
+              case 'SLIDEDOWN':
+                att.item.$elt.css('opacity', state.opacity);
+                att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px,'+state.translateY+'px');
+                att.item.CSSTransformApply('.nGY2GThumbnail');
+                break;
+              case 'FLIPUP':
+                att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px,'+state.translateY+'px');
+                att.item.CSSTransformSet('.nGY2GThumbnail', 'rotateX', state.rotateX+'deg');
+                att.item.$elt.css('opacity', state.opacity);
+                att.item.CSSTransformApply('.nGY2GThumbnail');
+                break;
+              case 'FLIPDOWN':
+                att.item.$elt.css('opacity', state.opacity);
+                att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px,' + state.translateY + 'px');
+                att.item.CSSTransformSet('.nGY2GThumbnail', 'rotateX', state.rotateX + 'deg');
+                att.item.CSSTransformApply('.nGY2GThumbnail');
+                break;
+              case 'SLIDEUP2':
+                att.item.$elt.css('opacity', state.opacity);
+                att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px,' + state.translateY + 'px');
+                att.item.CSSTransformSet('.nGY2GThumbnail', 'rotateY', state.rotateY + 'deg');
+                att.item.CSSTransformApply('.nGY2GThumbnail');
+                break;
+              case 'IMAGESLIDEUP':
+                att.item.$elt.css('opacity', state.opacity);
+								att.item.$Elts['.nGY2GThumbnailImage'].css('top', state.top);
+                break;
+              case 'SLIDEDOWN2':
+                att.item.$elt.css('opacity', state.opacity);
+                att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px, ' + state.translateY + 'px');
+                att.item.CSSTransformSet('.nGY2GThumbnail', 'rotateY', state.rotateY + 'deg');
+                att.item.CSSTransformApply('.nGY2GThumbnail');
+                break;
+              case 'SLIDERIGHT':
+                att.item.$elt.css('opacity', state.opacity);
+                att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', state.translateX + 'px, 0px');
+                att.item.CSSTransformApply('.nGY2GThumbnail');
+                break;
+              case 'SLIDELEFT':
+                att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', state.translateX + 'px, 0px');
+                att.item.$elt.css('opacity', state.opacity);
+                att.item.CSSTransformApply('.nGY2GThumbnail');
+                break;
+              case 'FADEIN':
+                att.$e.css(state);
+                break;
+            }
+          });
+          // att.$e.css( G.CSStransformName , 'scale('+state.scale+')').css('opacity',state.opacity);
+        },
+        finish:       function (state, att) {
+          window.requestAnimationFrame( function() {
+            if( att.item.$elt === null ) { return; }
+            
+            switch (G.tn.opt.Get('displayTransition')) {
+              case 'RANDOMSCALE':
+                att.$e.css( G.CSStransformName , 'scale('+state.scale+')').css('opacity', '');
+                break;
+              case 'SCALEUP':
+                att.$e.css( G.CSStransformName , '').css('opacity', '');
+                break;
+              case 'SCALEDOWN':
+                att.item.$elt.last().css('opacity', '');
+                att.item.CSSTransformSet('.nGY2GThumbnail', 'scale', state.scale);
+                att.item.CSSTransformApply('.nGY2GThumbnail');
+                break;
+              case 'IMAGESLIDEUP':
+                att.item.$elt.css('opacity', '');
+								att.item.$Elts['.nGY2GThumbnailImage'].css('top', 0);
+                break;
+              case 'SLIDEDOWN2':
+                att.item.$elt.css('opacity', '');
+                att.item.CSSTransformApply('.nGY2GThumbnail');
+                break;
+							default :
+								// case 'SLIDEUP':
+								// case 'SLIDEDOWN':
+								// case 'FLIPUP':
+								// case 'FLIPDOWN':
+								// case 'SLIDEUP2':
+								// case 'SLIDERIGHT':
+								// case 'SLIDELEFT':
+								// case 'FADEIN':
+                att.item.$elt.css('opacity', '');
+            }
+            ThumbnailAppearFinish(att.item);
+          });
+
+        }
+      });
+    
+    }
+    
+    // batch display thumbnails with animation
+    function ThumbnailDisplayAnimBatch() {
+      
+      G.GOM.thumbnails2Display.forEach( function(one) {
+        ThumbnailDisplayAnim2(one.itm, one.d);
+      });
+      G.GOM.thumbnails2Display=[];
+    }
+    
+    
+
+    // ######################################
+    // Gallery display animation
+    function GalleryAppear() {
+      
+      var d=G.galleryDisplayTransitionDuration.Get();
+      switch( G.galleryDisplayTransition.Get() ){
+        case 'ROTATEX':
+          G.$E.base.css({ perspective: '1000px', 'perspective-origin': '50% 0%' });
+          var tweenable = new NGTweenable();
+          tweenable.tween({
+            from:         { r: 50 },
+            to:           { r: 0  },
+            attachment:   { orgIdx: G.GOM.albumIdx },
+            duration:     d,
+            easing:       'easeOutCirc',
+            step:         function (state, att) {
+              if( att.orgIdx == G.GOM.albumIdx ) {
+                // window.ng_draf( function() {
+                  G.$E.conTnParent.css( G.CSStransformName , 'rotateX(' + state.r + 'deg)');
+                // });
+              }
+            }
+          });
+          break;
+        case 'SLIDEUP':
+          G.$E.conTnParent.css({ opacity: 0 });
+          var tweenable = new NGTweenable();
+          tweenable.tween({
+            from:         { y: 200, o: 0 },
+            to:           { y: 0,   o: 1 },
+            attachment:   { orgIdx: G.GOM.albumIdx },
+            duration:     d,
+            easing:       'easeOutCirc',
+            step:         function (state, att) {
+              if( att.orgIdx == G.GOM.albumIdx ) {
+                // window.ng_draf( function() {
+                  G.$E.conTnParent.css( G.CSStransformName , 'translate( 0px, '+state.y + 'px)').css('opacity', state.o);
+                // });
+              }
+            }
+          });
+          break;
+        case 'NONE':
+        default:
+          break;
+      }
+
+
+    }
+    
+    // ######################################
+    // ##### THUMBNAIL HOVER MANAGEMENT #####
+    // ######################################
+
+    function ThumbnailOverInit( GOMidx ) {
+      // Over init in 2 step:
+      // 1) init with thumbnailBuildInit2 parameter
+      // 2) init with the hover effect parameter
+      
+      
+      var curTn = G.GOM.items[GOMidx];
+      var item = G.I[curTn.thumbnailIdx];
+
+      if( item.$elt == null ) { return; } // zombie
+      
+      var fu = G.O.fnThumbnailHoverInit;
+      if( fu !== null ) {
+        typeof fu == 'function' ? fu($e, item, GOMidx) : window[fu]($e, item, GOMidx);
+      }
+
+      // build initialization
+      var inits = G.tn.buildInit.get();
+      for( var j = 0; j < inits.length; j++) {
+        switch( inits[j].property ) {
+          // CSS Transform
+          case 'scale':
+          case 'rotateX':
+          case 'rotateY':
+          case 'rotateZ':
+          case 'translateX':
+          case 'translateY':
+          case 'translateZ':
+            item.CSSTransformSet(inits[j].element, inits[j].property, inits[j].value);
+            item.CSSTransformApply(inits[j].element);
+            break;
+          // CSS filter
+          case 'blur':
+          case 'brightness':
+          case 'grayscale':
+          case 'sepia':
+          case 'contrast':
+          case 'opacity':
+          case 'saturate':
+            item.CSSFilterSet(inits[j].element, inits[j].property, inits[j].value);
+            item.CSSFilterApply(inits[j].element);
+            break;
+          default:
+            var $t=item.$getElt(inits[j].element);
+            $t.css( inits[j].property, inits[j].value );
+            break;
+        }
+      }
+      
+      // hover
+      var effects = G.tn.hoverEffects.get();
+      for( var j = 0; j < effects.length; j++) {
+        if( effects[j].firstKeyframe === true ) {
+          switch( effects[j].type ) {
+            case 'scale':
+            case 'rotateX':
+            case 'rotateY':
+            case 'rotateZ':
+            case 'translateX':
+            case 'translateY':
+            case 'translateZ':
+              item.CSSTransformSet(effects[j].element, effects[j].type, effects[j].from);
+              item.CSSTransformApply(effects[j].element);
+              break;
+            case 'blur':
+            case 'brightness':
+            case 'grayscale':
+            case 'sepia':
+            case 'contrast':
+            case 'opacity':
+            case 'saturate':
+              item.CSSFilterSet(effects[j].element, effects[j].type, effects[j].from);
+              item.CSSFilterApply(effects[j].element);
+              break;
+            default:
+              var $t = item.$getElt(effects[j].element);
+              $t.css( effects[j].type, effects[j].from );
+              break;
+              
+          }
+        }
+      }
+      item.hoverInitDone=true;
+    }
+
+    function ThumbnailHoverReInitAll() {
+      if( G.GOM.albumIdx == -1 ) { return; };
+      var l = G.GOM.items.length;
+      for( var i = 0; i < l ; i++ ) {
+        ThumbnailOverInit(i);
+        // G.GOM.items[i].hovered=false;
+        G.I[G.GOM.items[i].thumbnailIdx].hovered = false;
+      }
+    }
+
+
+    function ThumbnailHover( GOMidx ) {
+      if( G.GOM.albumIdx == -1 || !G.galleryResizeEventEnabled ) { return; };
+      if( G.GOM.slider.hostIdx == GOMidx ) {
+        // slider hosted on thumbnail -> no hover effect
+        return;
+      }
+      var curTn = G.GOM.items[GOMidx];
+      var item = G.I[curTn.thumbnailIdx];
+      if( item.kind == 'albumUp' || item.$elt == null ) { return; }
+
+      item.hovered = true;
+
+      var fu = G.O.fnThumbnailHover;
+      if( fu !== null ) {
+        typeof fu == 'function' ? fu(item.$elt, item, GOMidx) : window[fu](item.$elt, item, GOMidx);
+      }
+      var effects = G.tn.hoverEffects.get();
+
+      try {
+        for( var j = 0; j < effects.length; j++) {
+          if( effects[j].hoverin === true ) {
+            //item.animate( effects[j], j*10,  true );
+            item.animate( effects[j], 0,  true );
+          }
+        }
+        // effects on whole layout
+        // GalleryResize( GOMidx );
+      }
+      catch (e) { 
+        NanoAlert(G, 'error on hover: ' + e.message );
+      }
+
+    }
+
+    function ThumbnailHoverOutAll() {
+      if( G.GOM.albumIdx == -1 ) { return; };
+      var l = G.GOM.items.length;
+      for( var i = 0; i < l ; i++ ) {
+        if( G.GOM.items[i].inDisplayArea ) {
+          ThumbnailHoverOut(i);
+        }
+        else {
+          G.I[G.GOM.items[i].thumbnailIdx].hovered = false;
+        }
+      }
+    }
+
+    
+    function ThumbnailHoverOut( GOMidx ) {
+      if( G.GOM.albumIdx == -1 || !G.galleryResizeEventEnabled ) { return; }
+
+      if( G.GOM.slider.hostIdx == GOMidx ) {
+        // slider on thumbnail -> no hover effect
+        return;
+      }
+
+      var curTn = G.GOM.items[GOMidx];
+      var item = G.I[curTn.thumbnailIdx];
+      if( item.kind == 'albumUp' || !item.hovered ) { return; }
+      item.hovered = false;
+      if( item.$elt == null ) { return; }
+
+      var fu = G.O.fnThumbnailHoverOut;
+      if( fu !== null ) {
+        typeof fu == 'function' ? fu(item.$elt, item, GOMidx) : window[fu](item.$elt, item, GOMidx);
+      }
+
+      var effects = G.tn.hoverEffects.get();
+      try {
+        for( var j = 0; j < effects.length; j++) {
+          if( effects[j].hoverout === true ) {
+            // item.animate( effects[j], j*10, false );
+            item.animate( effects[j], 0, false );
+          }
+        }
+        // effects on whole layout
+        // GalleryResize( );
+      }
+      catch (e) { 
+        NanoAlert(G, 'error on hoverOut: ' + e.message );
+      }
+      
+    }
+    
+
+    /** @function DisplayPhoto */
+    function DisplayPhoto( imageID, albumID ) {
+
+      if( G.O.debugMode ) { console.log('#DisplayPhoto : '+  albumID +'-'+ imageID); }
+      var albumIdx = NGY2Item.GetIdx(G, albumID);
+      if( albumIdx == 0 ) {
+        G.GOM.curNavLevel = 'l1';
+      }
+      else {
+        G.GOM.curNavLevel = 'lN';
+      }
+
+      if( albumIdx == -1 ) {
+        // get content of album on root level
+        if( G.O.kind != '' ) {
+          // do not add album if Markup or Javascript data
+          NGY2Item.New( G, '', '', albumID, '0', 'album' );    // create empty album
+          albumIdx = G.I.length - 1;
+        }
+      }
+
+      var ngy2ItemIdx = NGY2Item.GetIdx(G, imageID);
+      if( ngy2ItemIdx == -1 ) {
+        // get content of the album
+        AlbumGetContent( albumID, DisplayPhoto, imageID, albumID );
+        return;
+      }
+      
+      if( G.O.debugMode ) { console.log('#DisplayPhoto : '+  ngy2ItemIdx); }
+     
+      DisplayPhotoIdx(ngy2ItemIdx);
+    
+    }
+
+
+    // BETA -> NOT finished and not used at this time
+    // Retrieve the title+description of ONE album
+    function albumGetInfo( albumIdx, fnToCall ) {
+      var url =   '';
+      var kind =  'image';
+      
+      switch( G.O.kind ) {
+        case 'json':
+          // TODO
+        case 'flickr':
+          // TODO
+        case 'picasa':
+        case 'google':
+        case 'google2':
+        default:
+          url = G.Google.url() + 'user/'+G.O.userID+'/albumid/'+G.I[albumIdx].GetID()+'?alt=json&&max-results=1&fields=title';
+          break;
+      }
+
+      jQuery.ajaxSetup({ cache: false });
+      jQuery.support.cors = true;
+      
+      var tId = setTimeout( function() {
+        // workaround to handle JSONP (cross-domain) errors
+        //PreloaderHide();
+        NanoAlert(G, 'Could not retrieve AJAX data...');
+      }, 60000 );
+      jQuery.getJSON(url, function(data, status, xhr) {
+        clearTimeout(tId);
+        //PreloaderHide();
+        
+        fnToCall( G.I[albumIdx].GetID() );
+
+      })
+      .fail( function(jqxhr, textStatus, error) {
+        clearTimeout(tId);
+        //PreloaderHide();
+        var err = textStatus + ', ' + error;
+        NanoAlert('Could not retrieve ajax data: ' + err);
+      });      
+    
+    }
+
+    
+    // function AlbumGetContent( albumIdx, fnToCall ) {
+    function AlbumGetContent( albumID, fnToCall, fnParam1, fnParam2 ) {
+      // var url='';
+      // var kind='image';
+      // var albumIdx=NGY2Item.GetIdx(G, albumID);
+      // var photoIdx=NGY2Item.GetIdx(G, photoID);
+      
+      switch( G.O.kind ) {
+        // MARKUP / API
+        case '':
+          AlbumGetMarkupOrApi(fnToCall, fnParam1, fnParam2);
+          break;
+        // JSON, Flickr, Picasa, ...
+        default:
+          jQuery.nanogallery2['data_'+G.O.kind](G, 'AlbumGetContent', albumID, fnToCall, fnParam1, fnParam2 );
+      }
+      
+    }
+    
+    var mediaList = {
+      youtube : {
+        getID: function( url ) {
+          // https://stackoverflow.com/questions/10591547/how-to-get-youtube-video-id-from-url
+          var s = url.match( /(?:https?:\/{2})?(?:w{3}\.)?youtu(?:be)?\.(?:com|be)(?:\/watch\?v=|\/)([^\s&]+)/ );
+          return s != null ? s[1] : null;
+        },
+        thumbUrl: function( id ) {
+          return 'https://img.youtube.com/vi/' + id + '/hqdefault.jpg';
+        },
+        url: function( id ) {
+          return 'https://www.youtube.com/embed/' + id;
+        },
+        markup: function( id ) {
+          // return '<iframe class="nGY2ViewerMedia" src="https://www.youtube.com/embed/' + id + '?rel=0" frameborder="0" gesture="media" allowfullscreen></iframe>';
+          return '<iframe class="nGY2ViewerMedia" src="https://www.youtube.com/embed/' + id + '?rel=0" frameborder="0" allow="autoplay" allowfullscreen></iframe>';
+        },
+        kind: 'iframe'
+      },
+      vimeo : {
+        getID: function( url ) {
+          // https://stackoverflow.com/questions/2916544/parsing-a-vimeo-id-using-javascript
+          var s = url.match( /^.*(vimeo\.com\/)((channels\/[A-z]+\/)|(groups\/[A-z]+\/videos\/))?([0-9]+)/ );
+          return s != null ? s[5] : null;
+        },
+        url: function( id ) {
+          return 'https://player.vimeo.com/video/' + id;
+        },
+        markup: function( id ) {
+          // return '<iframe class="nGY2ViewerMedia" src="https://player.vimeo.com/video/' + id + '?rel=0" frameborder="0" gesture="media" allowfullscreen></iframe>';
+          return '<iframe class="nGY2ViewerMedia" src="https://player.vimeo.com/video/' + id + '?rel=0" frameborder="0" allow="autoplay" allowfullscreen></iframe>';
+        },
+        kind: 'iframe'
+      },
+      dailymotion : {
+        getID: function( url ) {
+          // https://stackoverflow.com/questions/12387389/how-to-parse-dailymotion-video-url-in-javascript
+          var m = url.match(/^.+dailymotion.com\/(video|hub)\/([^_]+)[^#]*(#video=([^_&]+))?/);
+          if (m !== null) {
+              if(m[4] !== undefined) {
+                  return m[4];
+              }
+              return m[2];
+          }
+          return null;
+        },
+        thumbUrl: function( id ) {
+          return 'https://www.dailymotion.com/thumbnail/video/' + id;
+        },
+        url: function( id ) {
+          return 'https://www.dailymotion.com/embed/video/' + id;
+        },
+        markup: function( id ) {
+          // return '<iframe class="nGY2ViewerMedia" src="https://www.dailymotion.com/embed/video/' + id + '?rel=0" frameborder="0" gesture="media" allowfullscreen></iframe>';
+          return '<iframe class="nGY2ViewerMedia" src="https://www.dailymotion.com/embed/video/' + id + '?rel=0" frameborder="0" allow="autoplay" allowfullscreen></iframe>';
+        },
+        kind: 'iframe'
+      },
+      selfhosted : {
+				// SELF-HOSTED VIDEOS
+        getID: function( url ) {
+          // In order to leave things as is, I used ID to identify the extension
+          // https://stackoverflow.com/questions/6997262/how-to-pull-url-file-extension-out-of-url-string-using-javascript
+          // Make sure the method used for verifying the extension matches the kind of url your selfhosted video has
+          var extension = url.split('.').pop();
+
+          // supported extensions
+          var s = ( extension === 'mp4' || extension === 'webm' || extension === 'ogv' || extension === '3gp' ) ? extension : null ;
+          return s;
+        },
+        markup: function( url ) {
+          // return '<video controls class="nGY2ViewerMedia"><source src="${id.src}" type="video/${id.type}" preload="auto">Your browser does not support the video tag (HTML 5).</video>';
+          var extension = url.split('.').pop();
+          return '<video controls class="nGY2ViewerMedia"><source src="'+ url +'" type="video/'+ extension +'" preload="auto">Your browser does not support the video tag (HTML 5).</video>';
+        },
+        kind: 'video',
+        selfhosted : true
+      }
+    };
+        
+    function AlbumGetMarkupOrApi ( fnToCall, fnParam1, fnParam2 ) {
+    
+      if( G.markupOrApiProcessed === true ) {
+        // already processed (maybe location hash to unknow reference) -> display root album
+        DisplayAlbum('-1', 0);
+        return;
+      }
+      
+      if( G.O.items !== undefined && G.O.items !== null ) {
+        // data defined as an object in an option parameter
+        GetContentApiObject();
+      }
+      else {
+				if( G.O.$markup.length > 0 ) {
+          // data defined as markup (href elements)
+          GetContentMarkup( G.O.$markup );
+          G.O.$markup=[]  ;
+        }
+        else {
+          NanoConsoleLog(G, 'error: no media to process.');
+          return;
+        }
+      }
+      
+      G.markupOrApiProcessed = true;
+      if( fnToCall !== null &&  fnToCall !== undefined) {
+        fnToCall( fnParam1, fnParam2, null );
+      }
+    }
+    
+    function StartsWithProtocol ( path ) {
+      if( path == null || path == undefined ) { return false; }
+      
+      var pattern = /^((http|https|ftp|ftps|file):\/\/)/;
+      if( !pattern.test(path) ) {
+        // not a full URL
+        return false;
+      }
+      return true;
+    }
+    
+    function GetContentApiObject() {
+      var foundAlbumID=false;
+      var nbTitles = 0;
+      var AlbumPostProcess = NGY2Tools.AlbumPostProcess.bind(G);
+
+      G.I[0].contentIsLoaded=true;
+
+      jQuery.each(G.O.items, function(i,item){
+        
+        var title = '';
+        title=GetI18nItem(item, 'title');
+        if( title === undefined ) { title=''; }
+        
+        var src='';
+        if( item['src'+RetrieveCurWidth().toUpperCase()] !== undefined ) {
+          src = item['src'+RetrieveCurWidth().toUpperCase()];
+        }
+        else {
+          src = item.src;
+        }
+        if( !StartsWithProtocol(src) ) {
+          src = G.O.itemsBaseURL + src;
+        }
+
+        var thumbsrc = '';
+        if( item.srct !== undefined && item.srct.length > 0 ) {
+          thumbsrc = item.srct;
+          if( !StartsWithProtocol(thumbsrc) ) {
+            thumbsrc = G.O.itemsBaseURL + thumbsrc;
+          }
+        }
+        else {
+          thumbsrc = src;
+        }
+        
+        if( G.O.thumbnailLabel.get('title') != '' ) {
+          title = GetImageTitle(src);
+        }
+
+        var description='';     //'&nbsp;';
+        description=GetI18nItem(item,'description');
+        if( description === undefined ) { description=''; }
+        //if( toType(item.description) == 'string' ) {
+        //  description=item.description;
+        //}
+
+        var tags = GetI18nItem(item, 'tags');
+        if( tags === undefined ) { tags=''; }
+
+        var albumID = 0;
+        if( item.albumID !== undefined  ) {
+          albumID=item.albumID;
+          foundAlbumID = true;
+        }
+        var ID = null;
+        if( item.ID !== undefined ) {
+          ID = item.ID;
+        }
+        var kind = 'image';
+        if( item.kind !== undefined && item.kind.length > 0 ) {
+          kind = item.kind;
+        }
+        
+        var newItem=NGY2Item.New( G, title, description, ID, albumID, kind, tags );
+        if( title != '' ) {
+          nbTitles++;
+        }
+        
+        // media source url - img is the default media kind
+        newItem.setMediaURL( src, 'img');
+
+        // manage media kinds other than IMG
+        jQuery.each(mediaList, function ( n, media ) {
+          var id = media.getID(src);
+          if( id != null ) {
+            if( typeof media.url == 'function' ) { src = media.url(id);  }
+            if( typeof media.thumbUrl == 'function' ) { thumbsrc = media.thumbUrl(id);  }
+            newItem.mediaKind = media.kind;
+            newItem.mediaMarkup = ( media.selfhosted ) ? media.markup( src ) : media.markup(id);
+            return false;
+          }
+        });
+
+        // image size
+        if( item.imageWidth !== undefined ) { newItem.imageWidth = item.width; }
+        if( item.imageHeight !== undefined ) { newItem.imageHeight = item.height; }
+
+        // THUMBNAILS
+        
+        // thumbnail image size
+        var tw = item.imgtWidth !== undefined ? item.imgtWidth : 0;
+        var th = item.imgtHeight !== undefined ? item.imgtHeight : 0;
+
+        // default thumbnail URL and size
+        newItem.thumbs = {
+          url:    { l1 : { xs: thumbsrc, sm: thumbsrc, me: thumbsrc, la: thumbsrc, xl: thumbsrc }, lN : { xs: thumbsrc, sm: thumbsrc, me: thumbsrc, la: thumbsrc, xl: thumbsrc } },
+          width:  { l1 : { xs: tw, sm: tw, me: tw, la: tw, xl: tw }, lN : { xs: tw, sm: tw, me: tw, la: tw, xl: tw } },
+          height: { l1 : { xs: th, sm: th, me: th, la: th, xl: th }, lN : { xs: th, sm: th, me: th, la: th, xl: th } }
+        };
+        
+        // default media type -> IMG
+        if( newItem.mediaKind == 'img' ) {
+        
+          // responsive thumbnails URL and size
+          var lst=['xs', 'sm', 'me', 'la', 'xl'];
+          for( var i=0; i< lst.length; i++ ) {
+            // url
+            var turl = item['srct' + lst[i].toUpperCase()];
+            if( turl !== undefined ) {
+              if( !StartsWithProtocol(turl) ) {
+                turl = G.O.itemsBaseURL + turl;
+              }
+              newItem.url.l1[lst[i]] = turl;
+              newItem.url.lN[lst[i]] = turl;
+            }
+            // width
+            var tw = item['imgt' + lst[i].toUpperCase() + 'Width'];
+            if( tw != undefined ) {
+              newItem.width.l1[lst[i]] = parseInt(tw);
+              newItem.width.lN[lst[i]] = parseInt(tw);
+            }
+            // height
+            var th = item['imgt' + lst[i].toUpperCase() + 'Height'];
+            if( th != undefined ) {
+              newItem.height.l1[lst[i]] = parseInt(th);
+              newItem.height.lN[lst[i]] = parseInt(th);
+            }
+          }
+        }
+        
+        // dominant colors (needs to be a base64 gif)
+        if( item.imageDominantColors !== undefined ) {
+          newItem.imageDominantColors = item.imageDominantColors;
+        }
+        // dominant color (rgb hex)
+        if( item.imageDominantColor !== undefined ) {
+          newItem.imageDominantColor = item.imageDominantColor;
+        }
+        
+        // dest url
+        if( item.destURL !== undefined && item.destURL.length>0 ) {
+          newItem.destinationURL = item.destURL;
+        }
+        
+        // download image url
+        if( item.downloadURL !== undefined && item.downloadURL.length>0 ) {
+          newItem.downloadURL = item.downloadURL;
+        }
+        
+        // EXIF DATA
+        // Exif - model
+        if( item.exifModel !== undefined ) { newItem.exif.model = item.exifModel; }
+        // Exif - flash
+        if( item.exifFlash !== undefined ) { newItem.exif.flash = item.exifFlash; }
+        // Exif - focallength
+        if( item.exifFocalLength !== undefined ) { newItem.exif.focallength = item.exifFocalLength; }
+        // Exif - fstop
+        if( item.exifFStop !== undefined ) { newItem.exif.fstop = item.exifFStop; }
+        // Exif - exposure
+        if( item.exifExposure !== undefined ) { newItem.exif.exposure = item.exifExposure; }
+        // Exif - time
+        if( item.exifIso !== undefined ) { newItem.exif.iso = item.exifIso; }
+        // Exif - iso
+        if( item.exifTime !== undefined ) { newItem.exif.time = item.exifTime; }
+        // Exif - location
+        if( item.exifLocation !== undefined ) { newItem.exif.location  = item.exifLocation; }
+        
+        
+        // custom data
+        if( item.customData !== null ) {
+          newItem.customData = cloneJSObject( item.customData );
+        }
+
+        newItem.contentIsLoaded = true;
+        
+        var fu = G.O.fnProcessData;
+        if( fu !== null ) {
+          typeof fu == 'function' ? fu(newItem, 'api', item) : window[fu](newItem, 'api', item);
+        }
+        
+        AlbumPostProcess(albumID);
+      });
+      
+      // if( foundAlbumID ) { G.O.displayBreadcrumb=true; }
+      if( nbTitles == 0 ) { G.O.thumbnailLabel.display=false; }
+
+    }
+    
+
+    // Returns the text of the DOM element (without children)
+    // Because jQuery().text() returns the text of all children
+    function ElementGetText( element ) {
+      
+      var text = '';
+      if( element.childNodes[0] !== undefined ) {
+        if( element.childNodes[0].nodeValue !== null && element.childNodes[0].nodeValue !== undefined ) {
+          text = element.childNodes[0].nodeValue.trim();
+        }
+      }
+      return text;
+    }
+    
+    // Extract items from the jQuery elements
+    function GetContentMarkup( $elements, group ) {
+      var foundAlbumID = false;
+      var nbTitles = 0;
+      var AlbumPostProcess = NGY2Tools.AlbumPostProcess.bind(G);
+      var GetImageTitleFromURL = NGY2Tools.GetImageTitleFromURL.bind(G);
+
+      G.I[0].contentIsLoaded = true;
+
+      jQuery.each($elements, function(i, item){
+        
+        // compare to group defined on the element that has been clicked (lightbox standalone)
+        if( item.dataset.nanogallery2Lgroup != group ) { return; }
+
+        // create dictionnary with all data attribute name in lowercase (to be case unsensitive)
+        var data = {
+          // all possible data attributes with some default values
+          'data-ngdesc':                  '',         // item description
+          'data-ngid':                    null,       // ID
+          'data-ngkind':                  'image',    // kind (image, album, albumup)
+          'data-ngtags':                  null,       // tags
+          'data-ngdest':                  '',         // destination URL
+          'data-ngthumbimgwidth':         0,          // thumbnail width
+          'data-ngthumbimgheight':        0,          // thumbnail height
+          'data-ngimagewidth':            0,          // image width
+          'data-ngimageheight':           0,          // image height
+          'data-ngimagedominantcolors':   null,       // image dominant colors
+          'data-ngimagedominantcolor':    null,       // image dominant colors
+          'data-ngexifmodel':             '',         // EXIF data
+          'data-ngexifflash':             '',
+          'data-ngexiffocallength':       '',
+          'data-ngexiffstop':             '',
+          'data-ngexifexposure':          '',
+          'data-ngexifiso':               '',
+          'data-ngexiftime':              '',
+          'data-ngexiflocation':          '',
+          'data-ngsrc':    					      '',
+					'alt':													''
+        };
+
+        // Extract data attributes from main item
+        [].forEach.call( item.attributes, function(attr) {
+          data[attr.name.toLowerCase()] = attr.value.trim();
+        });
+
+        var title = ElementGetText(item);
+				if( title == '' && data.alt != '') {
+					// no title -> check ALT attribute of main element
+					title = data['alt'];
+				}
+        
+        // Complete with data attributes from all children
+        jQuery.each($(item).children(), function(i, sub_item){
+          
+          // title may be set on a child element
+          if( title == '' ) {
+            title = ElementGetText(sub_item);
+          }
+          
+          [].forEach.call( sub_item.attributes, function(attr) {
+            data[attr.name.toLowerCase()] = attr.value.trim();
+          });
+
+					if( title == '' && data.alt != '') {
+						// no title -> check ALT attribute of sub element
+						title = data['alt'];
+					}
+
+				});
+
+				// BIG IMAGE URL
+        // responsive image URL
+        var src = '',
+        st = RetrieveCurWidth().toUpperCase();
+        if( data.hasOwnProperty('data-ngsrc'+st) ) {
+          src = data['data-ngsrc'+st];
+        }
+				// image URL from data-ngsrc attribute 
+        // if( src == '' ) {
+          // src = data['data-ngsrc'];
+        // }
+				// image URL from href attribute (a element)
+        // if( src == '' ) {
+          // src = data['href'];
+        // }
+        src = src || data['data-ngsrc'] || data['href'];
+        if( !StartsWithProtocol(src) ) {          // do not add the base URL if src starts with a protocol (http, https...)
+          src = G.O.itemsBaseURL + src;
+        }
+
+
+        // THUMBNAIL IMAGE
+        var thumbsrc = '';
+				// src attribute (img element)
+        if( data.hasOwnProperty('src') ) {
+          thumbsrc = data['src'];
+        }
+				// data-ngthumb attribute
+				if( thumbsrc == '' && data.hasOwnProperty('data-ngthumb') ) {
+          thumbsrc = data['data-ngthumb'];
+        }
+        if( thumbsrc == '' ) {
+          thumbsrc = src;       // no thumbnail image URL -> use big image URL
+        }
+				if( !StartsWithProtocol(thumbsrc) ) {
+					thumbsrc = G.O.itemsBaseURL + thumbsrc;
+				}
+
+        //newObj.description=jQuery(item).attr('data-ngdesc');
+        var description = data['data-ngdesc'];
+        var ID = data['id'] || data['data-ngid'];
+        // if( ID == undefined ) {
+          // ID = data['data-ngid'];
+        // }
+        var kind = data['data-ngkind'];
+        var tags = data['data-ngtags'];
+
+        var albumID = '0';
+        if( data.hasOwnProperty('data-ngalbumid') ) {
+          albumID = data['data-ngalbumid'];
+          foundAlbumID = true;
+        }
+
+        // var title = jQuery(item).text();
+        var title_from_url = GetImageTitleFromURL( src );
+        if( title_from_url != '' ) {
+          title = title_from_url;
+        }
+
+
+        var newItem = NGY2Item.New( G, title, description, ID, albumID, kind, tags );
+        if( title != '' ) {
+          nbTitles++;
+        }
+
+        // media source url - img is the default media kind
+        newItem.setMediaURL( src, 'img');
+
+        // manage media kinds other than IMG
+        // newItem.mediaKind = 'img';
+        jQuery.each(mediaList, function ( n, media ) {
+          var id = media.getID(src);
+          if( id != null ) {
+            if( typeof media.url == 'function' ) { src = media.url(id);  }
+            if( typeof media.thumbUrl == 'function' ) { thumbsrc = media.thumbUrl(id);  }
+            newItem.mediaKind = media.kind;
+            newItem.mediaMarkup = ( media.selfhosted ) ? media.markup( src ) : media.markup(id);
+            return false;
+          }
+        });
+        
+        
+        // Big image size
+        newItem.imageWidth = parseInt( data['data-ngimagewidth'] );
+        newItem.imageHeight = parseInt( data['data-ngimageheight'] );
+        
+        // default thumbnail image URL and size
+        var tw = parseInt(data['data-ngthumbimgwidth']);
+        var th = parseInt(data['data-ngthumbimgheight']);
+        newItem.thumbs = {
+          url:    { l1 : { xs: thumbsrc, sm: thumbsrc, me: thumbsrc, la: thumbsrc, xl: thumbsrc }, lN : { xs: thumbsrc, sm: thumbsrc, me: thumbsrc, la: thumbsrc, xl: thumbsrc } },
+          width:  { l1 : { xs: tw, sm: tw, me: tw, la: tw, xl: tw }, lN : { xs: tw, sm: tw, me: tw, la: tw, xl: tw } },
+          height: { l1 : { xs: th, sm: th, me: th, la: th, xl: th }, lN : { xs: th, sm: th, me: th, la: th, xl: th } }
+        };
+
+        // Media type -> IMG
+        if( newItem.mediaKind == 'img' ) {
+        
+          // responsive thumbnails URL and size
+          var lst = ['xs', 'sm', 'me', 'la', 'xl'];
+          for( var i = 0; i < lst.length; i++ ) {
+            // url
+            if( data.hasOwnProperty('data-ngthumb' + lst[i]) ) {
+              var turl=data['data-ngthumb' + lst[i]];
+              if( !StartsWithProtocol(turl) ) {
+                turl = G.O.itemsBaseURL + turl;
+              }
+              newItem.url.l1[lst[i]] = turl;
+              newItem.url.lN[lst[i]] = turl;
+            }
+
+            // width
+            if( data.hasOwnProperty('data-ngthumb' + lst[i] + 'width') ) {
+              var tw=parseInt(data['data-ngthumb' + lst[i] + 'width']);
+              newItem.width.l1[lst[i]] = tw;
+              newItem.width.lN[lst[i]] = tw;
+            }
+            // height
+            if( data.hasOwnProperty('data-ngthumb' + lst[i] + 'height') ) {
+              var th=parseInt('data-ngthumb' + lst[i] + 'height');
+              newItem.height.l1[lst[i]] = th;
+              newItem.height.lN[lst[i]] = th;
+            }
+          }
+        }
+        
+        
+        // dominant colorS (needs to be a base64 gif)
+        newItem.imageDominantColors = data['data-ngimagedominantcolors'];
+        // dominant color (rgb hex)
+        newItem.imageDominantColor = data['data-ngimagedominantcolors'];
+
+        newItem.destinationURL = data['data-ngdest'];
+        newItem.downloadURL = data['data-ngdownloadurl'];
+
+        // Exif - model
+        newItem.exif.model=data['data-ngexifmodel'];
+        // Exif - flash
+        newItem.exif.flash=data['data-ngexifflash'];
+        // Exif - focallength
+        newItem.exif.focallength=data['data-ngexiffocallength'];
+        // Exif - fstop
+        newItem.exif.fstop=data['data-ngexiffstop'];
+        // Exif - exposure
+        newItem.exif.exposure=data['data-ngexifexposure'];
+        // Exif - iso
+        newItem.exif.iso=data['data-ngexifiso'];
+        // Exif - time
+        newItem.exif.time=data['data-ngexiftime'];
+        // Exif - location
+        newItem.exif.location=data['data-ngexiflocation'];
+        
+        newItem.contentIsLoaded = true;
+
+        // custom data
+        if( jQuery(item).data('customdata') !== undefined ) {
+          newItem.customData = cloneJSObject(jQuery(item).data('customdata'));
+        }
+        // custom data
+        if( jQuery(item).data('ngcustomdata') !== undefined ) {
+          newItem.customData = cloneJSObject(jQuery(item).data('ngcustomdata'));
+        }
+
+        var fu=G.O.fnProcessData;
+        if( fu !== null ) {
+          typeof fu == 'function' ? fu(newItem, 'markup', item) : window[fu](newItem, 'markup', item);
+        }
+        
+        AlbumPostProcess(albumID);
+
+      });
+      
+      // if( foundAlbumID ) { G.O.displayBreadcrumb=true; }
+      if( nbTitles == 0 ) { G.O.thumbnailLabel.display = false; }
+			
+    }
+
+    
+    // ################################
+    // ##### DEFINE VARIABLES     #####
+    // ################################
+
+    
+    /** @function DefineVariables */
+    function DefineVariables() {
+    
+      // change 'picasa' to 'google' for compatibility reason
+      if( G.O.kind.toUpperCase() == 'PICASA'  || G.O.kind.toUpperCase() == 'GOOGLE') {
+        G.O.kind='google2';
+      }
+    
+      // management of screen width
+      G.GOM.cache.viewport = getViewport();
+      G.GOM.curWidth = RetrieveCurWidth();
+      
+      // tumbnail toolbar
+      jQuery.extend(true, G.tn.toolbar.image, G.O.thumbnailToolbarImage );
+      jQuery.extend(true, G.tn.toolbar.album, G.O.thumbnailToolbarAlbum );
+      var t = ['image', 'album'];
+      var pos= ['topLeft', 'topRight', 'bottomLeft', 'bottomRight']
+      for( var i=0; i < t.length ; i++ ) {
+        for( var j=0; j < pos.length ; j++ ) {
+          G.tn.toolbar[t[i]][pos[j]] = G.tn.toolbar[t[i]][pos[j]].toUpperCase();
+        }
+      }
+
+      // convert label settings
+      if( G.O.thumbnailLabel.position == 'overImageOnBottom' ) {
+        G.O.thumbnailLabel.valign = 'bottom';
+        G.O.thumbnailLabel.position == 'overImage';
+      }
+      if( G.O.thumbnailLabel.position == 'overImageOnMiddle' ) {
+        G.O.thumbnailLabel.valign = 'middle';
+        G.O.thumbnailLabel.position == 'overImage';
+      }
+      if( G.O.thumbnailLabel.position == 'overImageOnTop' ) {
+        G.O.thumbnailLabel.valign = 'top';
+        G.O.thumbnailLabel.position == 'overImage';
+      }
+      if( G.O.thumbnailL1Label !== undefined && G.O.thumbnailL1Label.position !== undefined ) {
+        if( G.O.thumbnailL1Label.position == 'overImageOnBottom' ) {
+          G.O.thumbnailL1Label.valign = 'bottom';
+          G.O.thumbnailL1Label.position == 'overImage';
+        }
+        if( G.O.thumbnailL1Label.position == 'overImageOnMiddle' ) {
+          G.O.thumbnailL1Label.valign = 'middle';
+          G.O.thumbnailL1Label.position == 'overImage';
+        }
+        if( G.O.thumbnailL1Label.position == 'overImageOnTop' ) {
+          G.O.thumbnailL1Label.valign = 'top';
+          G.O.thumbnailL1Label.position == 'overImage';
+        }
+      }
+
+      // thumbnails label - level dependant settings
+      G.O.thumbnailLabel.get = function( opt ) {
+        if( G.GOM.curNavLevel == 'l1' && G.O.thumbnailL1Label !== undefined && G.O.thumbnailL1Label[opt] !== undefined ) {
+          return G.O.thumbnailL1Label[opt];
+        }
+        else {
+          return G.O.thumbnailLabel[opt];
+        }
+      };
+      G.O.thumbnailLabel.set = function( opt, value ) {
+        if( G.GOM.curNavLevel == 'l1' && G.O.thumbnailL1Label !== undefined && G.O.thumbnailL1Label[opt] !== undefined ) {
+          G.O.thumbnailL1Label[opt]=value;
+        }
+        else {
+          G.O.thumbnailLabel[opt]=value;
+        }
+      };
+
+
+      if( G.O.blackList != '' ) { G.blackList=G.O.blackList.toUpperCase().split('|'); }
+      if( G.O.whiteList != '' ) { G.whiteList=G.O.whiteList.toUpperCase().split('|'); }
+      if( G.O.tagBlackList != '' ) { G.tagBlackList=G.O.tagBlackList; }
+
+
+      if( G.O.albumList2 !== undefined && G.O.albumList2 !== null && G.O.albumList2.constructor === Array  ) {
+        var l=G.O.albumList2.length;
+        for(var i=0; i< l; i++ ) {
+          G.albumList.push(G.O.albumList2[i]);
+        }
+        // G.albumList=G.O.albumList.toUpperCase().split('|');
+      }
+      if( G.O.albumList2 !== undefined && typeof G.O.albumList2 == 'string'   ) {
+        G.albumList.push(G.O.albumList2);
+      }
+      
+      
+      // thumbnail image crop
+      G.tn.opt.lN.crop = G.O.thumbnailCrop;
+      G.tn.opt.l1.crop = G.O.thumbnailL1Crop != null ? G.O.thumbnailL1Crop : G.O.thumbnailCrop;
+
+
+      function ThumbnailOpt( lN, l1, opt) {
+        G.tn.opt.lN[opt] = G.O[lN];
+        G.tn.opt.l1[opt] = G.O[lN];
+        if( toType(G.O[l1]) == 'number' ) {
+          G.tn.opt.l1[opt] = G.O[l1];
+        }
+      }
+      // thumbnail stacks
+      ThumbnailOpt('thumbnailStacks', 'thumbnailL1Stacks', 'stacks');
+      // thumbnail stacks translate X
+      ThumbnailOpt('thumbnailStacksTranslateX', 'thumbnailL1StacksTranslateX', 'stacksTranslateX');
+      // thumbnail stacks translate Y
+      ThumbnailOpt('thumbnailStacksTranslateY', 'thumbnailL1StacksTranslateY', 'stacksTranslateY');
+      // thumbnail stacks translate Z
+      ThumbnailOpt('thumbnailStacksTranslateZ', 'thumbnailL1StacksTranslateZ', 'stacksTranslateZ');
+      // thumbnail stacks rotate X
+      ThumbnailOpt('thumbnailStacksRotateX', 'thumbnailL1StacksRotateX', 'stacksRotateX');
+      // thumbnail stacks rotate Y
+      ThumbnailOpt('thumbnailStacksRotateY', 'thumbnailL1StacksRotateY', 'stacksRotateY');
+      // thumbnail stacks rotate Z
+      ThumbnailOpt('thumbnailStacksRotateZ', 'thumbnailL1StacksRotateZ', 'stacksRotateZ');
+      // thumbnail stacks scale
+      ThumbnailOpt('thumbnailStacksScale', 'thumbnailL1StacksScale', 'stacksScale');
+      // thumbnail gutter width
+      // ThumbnailOpt('thumbnailGutterWidth', 'thumbnailL1GutterWidth', 'gutterWidth');
+      // thumbnail gutter height
+      // ThumbnailOpt('thumbnailGutterHeight', 'thumbnailL1GutterHeight', 'gutterHeight');
+      // thumbnail border horizontal
+      ThumbnailOpt('thumbnailBorderHorizontal', 'thumbnailL1BorderHorizontal', 'borderHorizontal');
+      // thumbnail border vertical
+      ThumbnailOpt('thumbnailBorderVertical', 'thumbnailL1BorderVertical', 'borderVertical');
+      // thumbnail grid base height (for cascading layout)
+      ThumbnailOpt('thumbnailBaseGridHeight', 'thumbnailL1BaseGridHeight', 'baseGridHeight');
+      
+      
+      // Set same value to all widths
+      function ResponsiveSetSize( setting, level, v ) {
+        G.tn.settings[setting][level]['xs'] = v;
+        G.tn.settings[setting][level]['sm'] = v;
+        G.tn.settings[setting][level]['me'] = v;
+        G.tn.settings[setting][level]['la'] = v;
+        G.tn.settings[setting][level]['xl'] = v;
+      }
+
+      // Get and evaluate responsive values from one option
+      // Responsive is with syntax: n XSn1 SMn2 MEn3 LAn4 XLn5 (where n is the default value)
+      // Value 'auto' is accepted for all options, but is handeld only for thumbnail width/height
+      function ResponsiveOption( option, setting, level ) {
+        var v = G.O[option];
+
+        if( v === undefined || v === null ) { return; }
+        
+        // if( toType(v) == 'number' ) {
+        if( toType(v) == 'number' || v.indexOf(' ') == -1 ) {
+          // set value for all widths
+          var vn = 'auto';                              
+          if( v != 'auto' ) { vn = parseInt(v); }
+          ResponsiveSetSize( setting, level, vn );
+        }
+        else {
+          var sp = v.split(' ');
+          if( sp.length > 0 && +sp[0] === +sp[0] ) {          // check if sp[0] is a number
+            // first value is the default size for all widths
+            var vn = 'auto';
+            if( sp[0] != 'auto' ) { vn = parseInt(sp[0]); }
+            ResponsiveSetSize( setting, level, vn );
+          }
+          for( var i = 1; i < sp.length; i++ ) {
+            if( /^xs|sm|me|la|xl/i.test( sp[i] ) ) {        // regex: i ignores the case and ^ means "starts with"
+              var wi = sp[i].substring(0, 2).toLowerCase();
+              var va = sp[i].substring(2);
+              var vn = 'auto';
+              if( va != 'auto' ) { vn = parseInt(va); }
+              G.tn.settings[setting][level][wi] = vn;
+            }
+          }
+        }
+      }
+      
+      ResponsiveOption('thumbnailGutterWidth', 'gutterWidth', 'lN');
+      ResponsiveOption('thumbnailGutterWidth', 'gutterWidth', 'l1');        // set default values for first level
+      ResponsiveOption('thumbnailL1GutterWidth', 'gutterWidth', 'l1');
+      ResponsiveOption('thumbnailGutterHeight', 'gutterHeight', 'lN');
+      ResponsiveOption('thumbnailGutterHeight', 'gutterHeight', 'l1');      // set default values for first level
+      ResponsiveOption('thumbnailL1GutterHeight', 'gutterHeight', 'l1');
+      
+      // gallery display mode
+      G.galleryDisplayMode.lN = G.O.galleryDisplayMode.toUpperCase();
+      G.galleryDisplayMode.l1 = G.O.galleryL1DisplayMode != null ? G.O.galleryL1DisplayMode.toUpperCase() : G.O.galleryDisplayMode.toUpperCase();
+      
+      // gallery maximum number of lines of thumbnails
+      G.galleryMaxRows.lN = G.O.galleryMaxRows;
+      G.galleryMaxRows.l1 = toType(G.O.galleryL1MaxRows) == 'number' ? G.O.galleryL1MaxRows : G.O.galleryMaxRows;
+
+      // gallery last row full
+      G.galleryLastRowFull.lN = G.O.galleryLastRowFull;
+      G.galleryLastRowFull.l1 = G.O.galleryL1LastRowFull != null ? G.O.galleryL1LastRowFull : G.O.galleryLastRowFull;
+      
+      // gallery sorting
+      G.gallerySorting.lN = G.O.gallerySorting.toUpperCase();
+      G.gallerySorting.l1 = G.O.galleryL1Sorting != null ? G.O.galleryL1Sorting.toUpperCase() : G.gallerySorting.lN;
+      
+      // gallery display transition
+      G.galleryDisplayTransition.lN = G.O.galleryDisplayTransition.toUpperCase();
+      G.galleryDisplayTransition.l1 = G.O.galleryL1DisplayTransition != null ? G.O.galleryL1DisplayTransition.toUpperCase() : G.galleryDisplayTransition.lN;
+
+      // gallery display transition duration
+      G.galleryDisplayTransitionDuration.lN = G.O.galleryDisplayTransitionDuration;
+      G.galleryDisplayTransitionDuration.l1 = G.O.galleryL1DisplayTransitionDuration != null ? G.O.galleryL1DisplayTransitionDuration : G.galleryDisplayTransitionDuration.lN;
+      
+      // gallery max items per album (not for inline/api defined items)
+      G.galleryMaxItems.lN = G.O.galleryMaxItems;
+      G.galleryMaxItems.l1 = toType(G.O.galleryL1MaxItems) == 'number' ? G.O.galleryL1MaxItems : G.O.galleryMaxItems;
+
+      // gallery filter tags
+      G.galleryFilterTags.lN = G.O.galleryFilterTags;
+      G.galleryFilterTags.l1 = G.O.galleryL1FilterTags != null ? G.O.galleryL1FilterTags : G.O.galleryFilterTags;
+
+      // gallery filter tags mode
+      G.galleryFilterTagsMode.lN = G.O.galleryFilterTagsMode;
+      G.galleryFilterTagsMode.l1 = G.O.galleryL1FilterTagsMode != null ? G.O.galleryL1FilterTagsMode : G.O.galleryFilterTagsMode;
+      
+      // gallery pagination
+      G.O.galleryPaginationMode = G.O.galleryPaginationMode.toUpperCase();
+
+      if( toType(G.O.slideshowDelay) == 'number' && G.O.slideshowDelay >= 2000 ) {
+        G.VOM.slideshowDelay = G.O.slideshowDelay;
+      }
+      else {
+        NanoConsoleLog(G, 'Parameter "slideshowDelay" must be an integer >= 2000 ms.');
+      }
+
+      // gallery display transition
+      if( typeof G.O.thumbnailDisplayTransition == 'boolean' ) {
+        if( G.O.thumbnailDisplayTransition === true ) {
+          G.tn.opt.lN.displayTransition = 'FADEIN';
+          G.tn.opt.l1.displayTransition = 'FADEIN';
+        }
+        else {
+          G.tn.opt.lN.displayTransition = 'NONE';
+          G.tn.opt.l1.displayTransition = 'NONE';
+        }
+      }
+
+      if( G.O.fnThumbnailDisplayEffect !== '' ) {
+        G.tn.opt.lN.displayTransition = 'CUSTOM';
+        G.tn.opt.l1.displayTransition = 'CUSTOM';
+      }
+      if( G.O.fnThumbnailL1DisplayEffect !== '' ) {
+        G.tn.opt.l1.displayTransition = 'CUSTOM';
+      }
+      
+
+      // thumbnail display transition easing
+			// set default easing
+      ThumbnailOpt('thumbnailDisplayTransitionEasing', 'thumbnailL1DisplayTransitionEasing', 'displayTransitionEasing');
+      // parse thumbnail display transition
+      function thumbnailDisplayTransitionParse( cfg, level ) {
+        if( typeof cfg == 'string' ) {
+          var st=cfg.split('_');
+          if( st.length == 1 ) {
+            G.tn.opt[level]['displayTransition'] = cfg.toUpperCase();
+          }
+          if( st.length == 2 ) {
+            G.tn.opt[level]['displayTransition'] = st[0].toUpperCase();
+            G.tn.opt[level]['displayTransitionStartVal'] = Number(st[1]);
+          }
+          if( st.length == 3 ) {
+            G.tn.opt[level]['displayTransition'] = st[0].toUpperCase();
+            G.tn.opt[level]['displayTransitionStartVal'] = Number(st[1]);
+            G.tn.opt[level]['displayTransitionEasing'] = st[2];
+          }
+        }
+      }
+      thumbnailDisplayTransitionParse( G.O.thumbnailDisplayTransition, 'lN');
+      thumbnailDisplayTransitionParse( G.O.thumbnailDisplayTransition, 'l1');
+      thumbnailDisplayTransitionParse( G.O.thumbnailL1DisplayTransition, 'l1');
+
+      
+      // thumbnail display transition duration
+      ThumbnailOpt('thumbnailDisplayTransitionDuration', 'thumbnailL1DisplayTransitionDuration', 'displayTransitionDuration');
+      // thumbnail display transition interval duration
+      ThumbnailOpt('thumbnailDisplayInterval', 'thumbnailL1DisplayInterval', 'displayInterval');
+      // thumbnail display order
+      ThumbnailOpt('thumbnailDisplayOrder', 'thumbnailL1DisplayOrder', 'displayOrder');
+
+      
+      // resolution breakpoints --> convert old syntax to new one
+      if( G.O.thumbnailSizeSM !== undefined ) { G.O.breakpointSizeSM = G.O.thumbnailSizeSM; }
+      if( G.O.thumbnailSizeME !== undefined ) { G.O.breakpointSizeME = G.O.thumbnailSizeME; }
+      if( G.O.thumbnailSizeLA !== undefined ) { G.O.breakpointSizeLA = G.O.thumbnailSizeLA; }
+      if( G.O.thumbnailSizeXL !== undefined ) { G.O.breakpointSizeXL = G.O.thumbnailSizeXL; }
+
+      // THUMBNAIL BUILD INIT
+      //level 1
+      if( G.O.thumbnailL1BuildInit2 !== undefined ) {
+        var t1 = G.O.thumbnailL1BuildInit2.split('|');
+        for( var i = 0; i < t1.length; i++ ) {
+          var o1 = t1[i].trim().split('_');
+          if( o1.length == 3 ) {
+            var i1 = NewTBuildInit();
+            i1.element = ThumbnailOverEffectsGetCSSElement(o1[0], '');
+            i1.property = o1[1];
+            i1.value = o1[2];
+            G.tn.buildInit.level1.push(i1);
+          }
+        }
+      }
+      //level N
+      if( G.O.thumbnailBuildInit2 !== undefined ) {
+        var t1 = G.O.thumbnailBuildInit2.split('|');
+        for( var i = 0; i < t1.length; i++ ) {
+          var o1 = t1[i].trim().split('_');
+          if( o1.length == 3 ) {
+            var i1 = NewTBuildInit();
+            i1.element = ThumbnailOverEffectsGetCSSElement(o1[0], '');
+            i1.property = o1[1];
+            i1.value = o1[2];
+            G.tn.buildInit.std.push(i1);
+          }
+        }
+      }
+
+      
+      // THUMBNAIL HOVER EFFETCS
+      
+      // thumbnails hover effects - Level1
+      var tL1HE = G.O.thumbnailL1HoverEffect2;
+      if( tL1HE !== undefined ) {
+        switch( toType(tL1HE) ) {
+          case 'string':
+            var tmp = tL1HE.split('|');
+            for(var i = 0; i < tmp.length; i++) {
+              var oDef = NewTHoverEffect();
+              oDef = ThumbnailHoverEffectExtract( tmp[i].trim(), oDef );
+              if(  oDef != null ) {
+                G.tn.hoverEffects.level1.push(oDef);
+              }
+            }
+            break;
+          case 'object':
+            var oDef = NewTHoverEffect();
+            oDef = jQuery.extend(oDef,tL1HE);
+            oDef = ThumbnailHoverEffectExtract( oDef.name, oDef );
+            if(  oDef != null ) {
+              G.tn.hoverEffects.level1.push(oDef);
+            }
+            break;
+          case 'array':
+            for(var i = 0; i < tL1HE.length; i++) {
+              var oDef = NewTHoverEffect();
+              oDef = jQuery.extend(oDef,tL1HE[i]);
+              oDef = ThumbnailHoverEffectExtract( oDef.name, oDef );
+              if(  oDef != null ) {
+                G.tn.hoverEffects.level1.push(oDef);
+              }
+            }
+            break;
+          case 'null':
+            break;
+          default:
+            NanoAlert(G, 'incorrect parameter for "thumbnailL1HoverEffect2".');
+        }
+      }
+      G.tn.hoverEffects.level1 = ThumbnailOverEffectsPreset(G.tn.hoverEffects.level1);
+  
+      // thumbnails hover effects - other levels
+      var tHE = G.O.thumbnailHoverEffect2;
+      switch( toType(tHE) ) {
+        case 'string':
+          var tmp = tHE.split('|');
+          for(var i = 0; i < tmp.length; i++) {
+            var oDef = NewTHoverEffect();
+            oDef = ThumbnailHoverEffectExtract( tmp[i].trim(), oDef );
+            if(  oDef != null ) {
+              G.tn.hoverEffects.std.push(oDef);
+            }
+          }
+          break;
+        case 'object':
+          var oDef = NewTHoverEffect();
+          oDef = jQuery.extend(oDef, tHE);
+          oDef = ThumbnailHoverEffectExtract( oDef.name, oDef );
+          if(  oDef != null ) {
+            G.tn.hoverEffects.std.push(oDef);
+          }
+          break;
+        case 'array':
+          for(var i = 0; i < tHE.length; i++) {
+            var oDef = NewTHoverEffect();
+            oDef = jQuery.extend(oDef,tHE[i]);
+            oDef = ThumbnailHoverEffectExtract( oDef.name, oDef );
+            if(  oDef!= null ) {
+              G.tn.hoverEffects.std.push(oDef);
+            }
+          }
+          break;
+        case 'null':
+          break;
+        default:
+          NanoAlert(G, 'incorrect parameter for "thumbnailHoverEffect2".');
+      }
+      G.tn.hoverEffects.std = ThumbnailOverEffectsPreset(G.tn.hoverEffects.std);
+
+      
+      if( G.O.touchAnimationL1 == undefined ) {
+        G.O.touchAnimationL1 = G.O.touchAnimation;
+      }
+      
+      // disable thumbnail touch animation when no hover effect defined
+      if( G.tn.hoverEffects.std.length == 0 ) {
+        if( G.tn.hoverEffects.level1.length == 0 ) {
+          G.O.touchAnimationL1 = false;
+        }
+        G.O.touchAnimation = false;
+      }      
+      
+      
+      // thumbnail sizes
+      if( G.O.thumbnailHeight == 0 || G.O.thumbnailHeight == ''  ) { G.O.thumbnailHeight = 'auto'; }
+      if( G.O.thumbnailWidth == 0 || G.O.thumbnailWidth == '' ) { G.O.thumbnailWidth = 'auto'; }
+      if( G.O.thumbnailL1Height == 0 || G.O.thumbnailL1Height == '' ) { G.O.thumbnailL1Height = 'auto'; }
+      if( G.O.thumbnailL1Width == 0 || G.O.thumbnailL1Width == '' ) { G.O.thumbnailL1Width = 'auto'; }
+
+      // RETRIEVE ALL THUMBNAIL SIZES
+      // ThumbnailSizes( 'thumbnailWidth', false, 'width');
+      // ThumbnailSizes( 'thumbnailL1Width', true, 'width');
+      // ThumbnailSizes( 'thumbnailHeight', false, 'height');
+      // ThumbnailSizes( 'thumbnailL1Height', true, 'height');
+      ResponsiveOption('thumbnailWidth', 'width', 'lN');
+      ResponsiveOption('thumbnailWidth', 'width', 'l1');
+      ResponsiveOption('thumbnailL1Width', 'width', 'l1');
+      ResponsiveOption('thumbnailHeight', 'height', 'lN');
+      ResponsiveOption('thumbnailHeight', 'height', 'l1');
+      ResponsiveOption('thumbnailL1Height', 'height', 'l1');
+
+      
+      G.O.thumbnailLabelHeight = parseInt(G.O.thumbnailLabelHeight);
+
+      
+      // retrieve all mosaic layout patterns
+      // default pattern
+      if( G.O.galleryMosaic != undefined ) {
+        // clone object
+        G.tn.settings.mosaic.l1.xs = JSON.parse(JSON.stringify(G.O.galleryMosaic));
+        G.tn.settings.mosaic.l1.sm = JSON.parse(JSON.stringify(G.O.galleryMosaic));
+        G.tn.settings.mosaic.l1.me = JSON.parse(JSON.stringify(G.O.galleryMosaic));
+        G.tn.settings.mosaic.l1.la = JSON.parse(JSON.stringify(G.O.galleryMosaic));
+        G.tn.settings.mosaic.l1.xl = JSON.parse(JSON.stringify(G.O.galleryMosaic));
+        G.tn.settings.mosaic.lN.xs = JSON.parse(JSON.stringify(G.O.galleryMosaic));
+        G.tn.settings.mosaic.lN.sm = JSON.parse(JSON.stringify(G.O.galleryMosaic));
+        G.tn.settings.mosaic.lN.me = JSON.parse(JSON.stringify(G.O.galleryMosaic));
+        G.tn.settings.mosaic.lN.la = JSON.parse(JSON.stringify(G.O.galleryMosaic));
+        G.tn.settings.mosaic.lN.xl = JSON.parse(JSON.stringify(G.O.galleryMosaic));
+        G.tn.settings.mosaicCalcFactor('l1', 'xs');
+        G.tn.settings.mosaicCalcFactor('l1', 'sm');
+        G.tn.settings.mosaicCalcFactor('l1', 'me');
+        G.tn.settings.mosaicCalcFactor('l1', 'la');
+        G.tn.settings.mosaicCalcFactor('l1', 'xl');
+        G.tn.settings.mosaicCalcFactor('lN', 'xs');
+        G.tn.settings.mosaicCalcFactor('lN', 'sm');
+        G.tn.settings.mosaicCalcFactor('lN', 'me');
+        G.tn.settings.mosaicCalcFactor('lN', 'la');
+        G.tn.settings.mosaicCalcFactor('lN', 'xl');
+      }
+      if( G.O.galleryL1Mosaic != undefined ) {
+        // default L1 pattern
+        G.tn.settings.mosaic.l1.xs = JSON.parse(JSON.stringify(G.O.galleryL1Mosaic));
+        G.tn.settings.mosaic.l1.sm = JSON.parse(JSON.stringify(G.O.galleryL1Mosaic));
+        G.tn.settings.mosaic.l1.me = JSON.parse(JSON.stringify(G.O.galleryL1Mosaic));
+        G.tn.settings.mosaic.l1.la = JSON.parse(JSON.stringify(G.O.galleryL1Mosaic));
+        G.tn.settings.mosaic.l1.xl = JSON.parse(JSON.stringify(G.O.galleryL1Mosaic));
+        G.tn.settings.mosaicCalcFactor('l1', 'xs');
+        G.tn.settings.mosaicCalcFactor('l1', 'sm');
+        G.tn.settings.mosaicCalcFactor('l1', 'me');
+        G.tn.settings.mosaicCalcFactor('l1', 'la');
+        G.tn.settings.mosaicCalcFactor('l1', 'xl');
+      }
+
+			var lst=['xs','sm','me','la','xl'];
+			// retrieve responsive mosaic definition for levels l1 & lN
+      for( var w = 0; w < lst.length; w++ ) {
+				if( G.O['galleryMosaic' + lst[w].toUpperCase()] != undefined ) {
+          G.tn.settings.mosaic.lN[lst[w]] = JSON.parse(JSON.stringify( G.O['galleryMosaic' + lst[w].toUpperCase()] ));
+          G.tn.settings.mosaic.l1[lst[w]] = JSON.parse(JSON.stringify( G.O['galleryMosaic' + lst[w].toUpperCase()] ));
+          G.tn.settings.mosaicCalcFactor('lN',lst[w]);
+          G.tn.settings.mosaicCalcFactor('l1', lst[w]);
+        }
+      }
+			// retrieve responsive mosaic definition for level l1
+      for( var w = 0; w < lst.length; w++ ) {
+				if( G.O['galleryL1Mosaic' + lst[w].toUpperCase()] != undefined ) {
+          G.tn.settings.mosaic.l1[lst[w]] = JSON.parse(JSON.stringify( G.O['galleryL1Mosaic' + lst[w].toUpperCase()] ));
+          G.tn.settings.mosaicCalcFactor('l1', lst[w]);
+        }
+      }
+      
+      G.O.imageTransition = G.O.imageTransition.toUpperCase();
+
+      G.layout.SetEngine();
+      
+      // init plugins
+      switch( G.O.kind ) {
+        // MARKUP / API
+        case '':
+          break;
+        // JSON, Flickr, Picasa, ...
+        default:
+        jQuery.nanogallery2['data_' + G.O.kind](G, 'Init' );
+      }
+
+    }
+
+    // HOVER EFFECTS
+    function ThumbnailHoverEffectExtract( name, effect) {
+      var easings = [ 'easeInQuad', 'easeOutQuad', 'easeInOutQuad', 'easeInCubic', 'easeOutCubic', 'easeInOutCubic', 'easeInQuart', 'easeOutQuart', 'easeInOutQuart', 'easeInQuint', 'easeOutQuint', 'easeInOutQuint', 'easeInSine', 'easeOutSine', 'easeInOutSine', 'easeInExpo', 'easeOutExpo', 'easeInOutExpo', 'easeInCirc', 'easeOutCirc', 'easeInOutCirc', 'easeOutBounce', 'easeInBack', 'easeOutBack', 'easeInOutBack', 'elastic', 'bounce'];
+    
+      var sp = name.split('_');
+      if( sp.length >= 4 ) {
+        // var oDef=NewTHoverEffect();
+        effect.name = '';
+        effect.type = sp[1];
+        effect.from = sp[2];
+        effect.to = sp[3];
+        if( sp.length >= 5 ) {
+          // effect.duration=sp[4];
+
+          for( var n = 4; n < sp.length; n++ ) {
+            var v = sp[n];
+            
+            // check if an easing name
+            var foundEasing = false;
+            for( var e = 0; e < easings.length; e++) {
+              if( v == easings[e] ) {
+                foundEasing = true;
+                effect.easing = v;
+                break;
+              }
+            }
+            if( foundEasing === true ) {
+              continue;
+            }
+            
+            v = v.toUpperCase();
+            
+            if( v == 'HOVERIN' ) {
+              effect.hoverout = false;
+              continue;
+            }
+            if( v == 'HOVEROUT' ) {
+              effect.hoverin = false;
+              continue;
+            }
+            
+            if( v == 'KEYFRAME' ) {
+              effect.firstKeyframe = false;
+              continue;
+            }
+            
+            var num = parseInt(v.replace(/[^0-9\.]/g, ''), 10);   // extract a number if one exists
+
+            if( num > 0 ) {
+              // the string contains a numbers > 0
+              if( v.indexOf('DURATION') >= 0 ) {
+                effect.duration = num;
+                continue;
+              }
+              if( v.indexOf('DURATIONBACK') >= 0 ) {
+                effect.durationBack = num;
+                continue;
+              }
+              if( v.indexOf('DELAY') >= 0 ) {
+                effect.delay = num;
+                continue;
+              }
+              if( v.indexOf('DELAYBACK') >= 0 ) {
+                effect.delayBack = num;
+                continue;
+              }
+              
+              // no parameter name found -> default is duration
+              effect.duration = num;
+            }
+          }
+        }        
+        effect.element=ThumbnailOverEffectsGetCSSElement(sp[0], effect.type);
+        
+      }
+      else {
+        effect.name = name;
+        // NanoAlert(G, 'incorrect parameter for "thumbnailHoverEffect": ' + name);
+        // return null;
+      }
+      return effect;
+    }
+    
+    
+    function ThumbnailOverEffectsGetCSSElement( element, property ) {
+        
+         var elts = {
+          'image':        '.nGY2GThumbnailImage',
+          'thumbnail':    '.nGY2GThumbnail',
+          'label':        '.nGY2GThumbnailLabel',
+          'title':        '.nGY2GThumbnailTitle',
+          'description':  '.nGY2GThumbnailDescription',
+          'tools':        '.nGY2GThumbnailIcons',
+          'customlayer':  '.nGY2GThumbnailCustomLayer',
+          'default':      'nGY2GThumbnailImage'
+        };
+        return (elts[element] || elts['default']);
+        
+
+    }
+    
+    // convert preset hover effects (nanoGALLERY) to new ones (nanogallery2)
+    function ThumbnailOverEffectsPreset( effects ) {
+
+      // COMPATIBILITY WITH nanoGALLERY
+      // OK:
+      //  'borderLighter', 'borderDarker', 'scale120', 'labelAppear', 'labelAppear75', 'labelOpacity50', 'scaleLabelOverImage'
+      //  'overScale', 'overScaleOutside', 'descriptionAppear'
+      //  'slideUp', 'slideDown', 'slideRight', 'slideLeft'
+      //  'imageScale150', 'imageScaleIn80', 'imageScale150Outside', 'imageSlideUp', 'imageSlideDown', 'imageSlideRight', 'imageSlideLeft'
+      //  'labelSlideUpTop', 'labelSlideUp', 'labelSlideDown', 'descriptionSlideUp'
+      // KO:
+      //  'labelSplit4', 'labelSplitVert', 'labelAppearSplit4', 'labelAppearSplitVert' 
+      // TODO:
+      //  'rotateCornerBL', 'rotateCornerBR', 'imageSplit4', 'imageSplitVert', 'imageRotateCornerBL', 'imageRotateCornerBR', 'imageFlipHorizontal', 'imageFlipVertical'
+
+    
+   
+      var newEffects=[];
+      for( var i=0; i< effects.length; i++ ) {
+        switch( effects[i].name.toUpperCase() ) {
+          case 'BORDERLIGHTER':
+            var rgb = ColorHelperToRGB(GalleryThemeGetCurrent().thumbnail.borderColor);
+            name = 'thumbnail_borderColor_'+rgb+'_'+ShadeBlendConvert(0.5, rgb );
+            newEffects.push(ThumbnailHoverEffectExtract(name, effects[i]));
+            break;
+
+          case 'BORDERDARKER':
+            var rgb = ColorHelperToRGB(GalleryThemeGetCurrent().thumbnail.borderColor);
+            name = 'thumbnail_borderColor_'+rgb+'_'+ShadeBlendConvert(-0.5, rgb );
+            newEffects.push(ThumbnailHoverEffectExtract(name, effects[i]));
+            break;
+
+          case 'SCALE120':
+            newEffects.push(ThumbnailHoverEffectExtract('thumbnail_scale_1.00_1.20', effects[i]));
+            break;
+          case 'LABELAPPEAR':
+          case 'LABELAPPEAR75':
+            newEffects.push(ThumbnailHoverEffectExtract('label_opacity_0.00_1.00', effects[i]));
+            break;
+          case 'TOOLSAPPEAR':
+            newEffects.push(ThumbnailHoverEffectExtract('tools_opacity_0_1', effects[i]));
+            break;
+          case 'TOOLSSLIDEDOWN':
+            newEffects.push(ThumbnailHoverEffectExtract('tools_translateY_-100%_0%', effects[i]));
+            break;
+          case 'TOOLSSLIDEUP':
+            newEffects.push(ThumbnailHoverEffectExtract('tools_translateY_100%_0%', effects[i]));
+            break;
+          case 'LABELOPACITY50':
+            newEffects.push(ThumbnailHoverEffectExtract('label_opacity_1.00_0.50', effects[i]));
+            break;
+          case 'LABELSLIDEUPTOP':
+          case 'LABELSLIDEUP':
+            newEffects.push(ThumbnailHoverEffectExtract('label_translateY_100%_0%', effects[i]));
+            break;
+          case 'LABELSLIDEDOWN':
+            newEffects.push(ThumbnailHoverEffectExtract('label_translateY_-100%_0%', effects[i]));
+            break;
+          case 'SCALELABELOVERIMAGE':
+            newEffects.push(ThumbnailHoverEffectExtract('label_scale_0.00_1.00', effects[i]));
+            var n = cloneJSObject(effects[i]);
+            newEffects.push(ThumbnailHoverEffectExtract('image_scale_1.00_0.00', n));
+            break;
+          case 'OVERSCALE':
+          case 'OVERSCALEOUTSIDE':
+            name = 'label_scale_0_100';
+            newEffects.push(ThumbnailHoverEffectExtract('label_scale_2.00_1.00', effects[i]));
+            var n = cloneJSObject(effects[i]);
+            newEffects.push(ThumbnailHoverEffectExtract('label_opacity_0.00_1.00', n));
+            n = cloneJSObject(effects[i]);
+            newEffects.push(ThumbnailHoverEffectExtract('image_scale_1.00_0.00', n));
+            n = cloneJSObject(effects[i]);
+            newEffects.push(ThumbnailHoverEffectExtract('image_opacity_1.00_0.00', n));
+            break;
+          case 'DESCRIPTIONAPPEAR':
+            newEffects.push(ThumbnailHoverEffectExtract('description_opacity_0_1', effects[i]));
+            break;
+          case 'SLIDERIGHT':
+            newEffects.push(ThumbnailHoverEffectExtract('image_translateX_0%_100%', effects[i]));
+            newEffects.push(ThumbnailHoverEffectExtract('label_translateX_-100%_0%', cloneJSObject(effects[i])));
+            break;
+          case 'SLIDELEFT':
+            newEffects.push(ThumbnailHoverEffectExtract('image_translateX_0%_-100%', effects[i]));
+            newEffects.push(ThumbnailHoverEffectExtract('label_translateX_100%_0%', cloneJSObject(effects[i])));
+            break;
+          case 'SLIDEUP':
+            newEffects.push(ThumbnailHoverEffectExtract('image_translateY_0%_-100%', effects[i]));
+            newEffects.push(ThumbnailHoverEffectExtract('label_translateY_100%_0%', cloneJSObject(effects[i])));
+            break;
+          case 'SLIDEDOWN':
+            newEffects.push(ThumbnailHoverEffectExtract('image_translateY_0%_100%', effects[i]));
+            newEffects.push(ThumbnailHoverEffectExtract('label_translateY_-100%_0%', cloneJSObject(effects[i])));
+            break;
+          case 'IMAGESCALE150':
+          case 'IMAGESCALE150OUTSIDE':
+            newEffects.push(ThumbnailHoverEffectExtract('image_scale_1.00_1.50', effects[i]));
+            break;
+          case 'IMAGESCALEIN80':
+            newEffects.push(ThumbnailHoverEffectExtract('image_scale_1.20_1.00', effects[i]));
+            break;
+          case 'IMAGESLIDERIGHT':
+            newEffects.push(ThumbnailHoverEffectExtract('image_translateX_0%_100%', effects[i]));
+            break;
+          case 'IMAGESLIDELEFT':
+            newEffects.push(ThumbnailHoverEffectExtract('image_translateX_0%_-100%', effects[i]));
+            break;
+          case 'IMAGESLIDEUP':
+            newEffects.push(ThumbnailHoverEffectExtract('image_translateY_0%_-100%', effects[i]));
+            break;
+          case 'IMAGESLIDEDOWN':
+            newEffects.push(ThumbnailHoverEffectExtract('image_translateY_0%_100%', effects[i]));
+            break;
+          case 'LABELSLIDEUP':
+          case 'LABELSLIDEUPTOP':
+            newEffects.push(ThumbnailHoverEffectExtract('label_translateY_100%_0%', effects[i]));
+            break;
+          case 'LABELSLIDEUPDOWN':
+            newEffects.push(ThumbnailHoverEffectExtract('label_translateY_0%_100%', effects[i]));
+            break;
+          case 'DESCRIPTIONSLIDEUP':
+            newEffects.push(ThumbnailHoverEffectExtract('description_translateY_110%_0%', effects[i]));
+            break;
+
+          case 'IMAGEBLURON':
+            newEffects.push(ThumbnailHoverEffectExtract('image_blur_2.00px_0.00px', effects[i]));
+            break;
+          case 'IMAGEBLUROFF':
+            newEffects.push(ThumbnailHoverEffectExtract('image_blur_0.00px_2.00px', effects[i]));
+            break;
+          case 'IMAGEGRAYON':
+            newEffects.push(ThumbnailHoverEffectExtract('image_grayscale_0%_100%', effects[i]));
+            break;
+          case 'IMAGEGRAYOFF':
+            newEffects.push(ThumbnailHoverEffectExtract('image_grayscale_100%_0%', effects[i]));
+            break;
+          case 'IMAGESEPIAON':
+            newEffects.push(ThumbnailHoverEffectExtract('image_sepia_100%_1%', effects[i]));
+            break;
+          case 'IMAGESEPIAOFF':
+            newEffects.push(ThumbnailHoverEffectExtract('image_sepia_1%_100%', effects[i]));
+            break;
+            
+          default:
+            newEffects.push(effects[i]);
+            break;
+        }
+      }
+      
+      return newEffects;
+    }
+
+
+    // Thumbnail hover effect definition
+    function NewTHoverEffect() {
+      var oDef={ 
+          name:           '',
+          element:        '',               // element class
+          type:           '',               
+          from:           '',               // start value
+          to:             '',               // end value
+          hoverin:        true,
+          hoverout:       true,
+          firstKeyframe:  true,
+          delay:          0,
+          delayBack:      0,
+          duration:       400,
+          durationBack:   300,
+          easing:         'easeOutQuart',
+          easingBack:     'easeOutQuart',
+          animParam:      null
+        };
+      return oDef;
+    }
+
+    function NewTBuildInit() {
+      // to set CSS properties
+      var oDef={ element: '', property: '', value: '' };
+      return oDef;
+    }
+
+    
+    function ThumbnailStyle( cfg, level) {
+
+      switch( cfg.position ){
+        case 'onBottom' :
+          G.tn.style[level]['label'] = 'bottom:0; ';
+          break;
+        case 'right' :
+          switch( cfg.valign ) {
+              case 'top':
+                G.tn.style[level]['label'] = 'top:0; position:absolute; left: 50%;';
+                break;
+              case 'middle':
+                G.tn.style[level]['label'] = 'top:0; bottom:0; left: 50%;';
+                G.tn.style[level]['title'] = 'position:absolute; bottom:50%;';
+                G.tn.style[level]['desc'] = 'position:absolute; top:50%;';
+                break;
+              case 'bottom':
+              default:
+                G.tn.style[level].label = 'bottom:0; position:absolute; left: 50%;';
+                G.tn.style[level].title = 'position:absolute;bottom:0;';
+                break;
+          }
+          break;
+        case 'custom':
+          break;
+        default:
+        case 'overImage' :
+          switch( cfg.valign ) {
+              case 'top':
+                G.tn.style[level]['label'] = 'top:0; position:absolute;';
+                break;
+              case 'middle':
+                G.tn.style[level]['label'] = 'top:0; bottom:0;';
+                G.tn.style[level]['title'] = 'position:absolute; bottom:50%;';
+                G.tn.style[level]['desc'] = 'position:absolute; top:50%;';
+                break;
+              case 'bottom':
+              default:
+                // G.O.thumbnailLabel.position = 'overImageOnBottom';
+                G.tn.style[level].label = 'bottom:0; position:absolute;';
+                break;
+          }
+        
+        // case 'overImageOnTop' :
+          // G.tn.style[level]['label'] = 'top:0; position:absolute;';
+          // break;
+        // case 'overImageOnMiddle' :
+          // G.tn.style[level]['label'] = 'top:0; bottom:0;';
+          // G.tn.style[level]['title'] = 'position:absolute; bottom:50%;';
+          // G.tn.style[level]['desc'] = 'position:absolute; top:50%;';
+          // break;
+        // case 'right' :
+        // case 'custom' :
+          // break;
+        // case 'overImageOnBottom' :
+        // default :
+          // G.O.thumbnailLabel.position = 'overImageOnBottom';
+          // G.tn.style[level].label = 'bottom:0; position:absolute;';
+          // break;
+      }
+      
+      // if( G.layout.engine != 'CASCADING' ) {
+      if( cfg.position != 'onBottom' ) {
+        // multi-line
+        if( cfg.titleMultiLine ) {
+          G.tn.style[level]['title'] += 'white-space:normal;';
+        }
+        if( cfg.descriptionMultiLine ) {
+          G.tn.style[level]['desc'] += 'white-space:normal;';
+        }
+      }
+      
+      // horizontal alignement
+      switch( cfg.align ) {
+        case 'right':
+            G.tn.style[level].label += 'text-align:right;';
+          break;
+        case 'left':
+            G.tn.style[level].label += 'text-align:left;';
+          break;
+        default:
+            G.tn.style[level].label += 'text-align:center;';
+          break;
+      }
+      
+      
+      if( cfg.titleFontSize != undefined && cfg.titleFontSize != '' ) {
+        G.tn.style[level].title += 'font-size:' + cfg.titleFontSize + ';';
+      }
+      if( cfg.descriptionFontSize != undefined && cfg.descriptionFontSize != '' ) {
+        G.tn.style[level].desc += 'font-size:' + cfg.descriptionFontSize + ';';
+      }
+      
+      if( cfg.displayDescription == false ) {
+        G.tn.style[level].desc += 'display:none;';
+      }
+    }
+
+    
+    // cache some thumbnail settings
+    function ThumbnailDefCaches() {
+      // thumbnail content CSS styles
+
+      // settings for level L1 and LN
+      ThumbnailStyle( G.O.thumbnailLabel, 'lN');
+      if( G.O.thumbnailL1Label !== undefined ) {
+        ThumbnailStyle( G.O.thumbnailL1Label, 'l1');
+      }
+      else {
+        ThumbnailStyle( G.O.thumbnailLabel, 'l1');
+      }
+
+      if( G.O.thumbnailL1Label && G.O.thumbnailL1Label.display ) {
+        // settings for level L1
+        ThumbnailStyle( G.O.thumbnailL1Label, 'l1');
+      }
+      
+
+      // default thumbnail sizes levels l1 and lN
+      var lst=['xs','sm','me','la','xl'];
+      for( var i = 0; i < lst.length; i++ ) {
+        var w = G.tn.settings.width.lN[lst[i]];
+        if( w != 'auto' ) {
+          G.tn.defaultSize.width.lN[lst[i]] = w;
+          G.tn.defaultSize.width.l1[lst[i]] = w;
+        }
+        else {
+          var h = G.tn.settings.height.lN[lst[i]];
+          G.tn.defaultSize.width.lN[lst[i]] = h;      // dynamic width --> set height value as default for the width
+          G.tn.defaultSize.width.l1[lst[i]] = h;      // dynamic width --> set height value as default
+        }
+      }
+      for( var i = 0; i < lst.length; i++ ) {
+        var h = G.tn.settings.height.lN[lst[i]];
+        if( h != 'auto' ) {
+          // grid or justified layout
+          G.tn.defaultSize.height.lN[lst[i]] = h;  //+G.tn.labelHeight.get();
+          G.tn.defaultSize.height.l1[lst[i]] = h;  //+G.tn.labelHeight.get();
+        }
+        else {
+          var w = G.tn.settings.width.lN[lst[i]];
+          G.tn.defaultSize.height.lN[lst[i]] = w;      // dynamic height --> set width value as default for the height
+          G.tn.defaultSize.height.l1[lst[i]] = w;      // dynamic height --> set width value as default
+        }
+      }
+
+      // default thumbnail sizes levels l1
+      for( var i = 0; i < lst.length; i++ ) {
+        var w = G.tn.settings.width.l1[lst[i]];
+        if( w != 'auto' ) {
+          G.tn.defaultSize.width.l1[lst[i]] = w;
+        }
+        else {
+          var h = G.tn.settings.height.l1[lst[i]];
+          G.tn.defaultSize.width.l1[lst[i]] = h;      // dynamic width --> set height value as default
+        }
+      }
+      for( var i = 0; i < lst.length; i++ ) {
+        var h = G.tn.settings.height.l1[lst[i]];
+        if( h != 'auto' ) {
+          // grid or justified layout
+          G.tn.defaultSize.height.l1[lst[i]] = h;  //+G.tn.labelHeight.get();
+        }
+        else {
+          var w = G.tn.settings.width.l1[lst[i]];
+          G.tn.defaultSize.height.l1[lst[i]]= w ;      // dynamic height --> set width value as default
+        }
+      }
+      
+    }
+    
+
+    //
+    function GalleryThemeGetCurrent() {
+
+      var cs=null;
+      switch(toType(G.O.galleryTheme)) {
+        case 'object':    // user custom color scheme object 
+          cs = G.galleryTheme_dark;  // default color scheme
+          jQuery.extend(true,cs,G.O.galleryTheme);
+          break;
+        case 'string':    // name of an internal defined color scheme
+          switch( G.O.galleryTheme ) {
+            case 'light':
+              cs = G.galleryTheme_light;
+              break;
+            case 'default':
+            case 'dark':
+            case 'none':
+            default:
+              cs = G.galleryTheme_dark;
+          }
+          break;
+        default:
+          cs = G.galleryTheme_dark;
+      }
+      return cs;
+    }
+    
+    // ##### BREADCRUMB/THUMBNAIL COLOR SCHEME #####
+    function SetGalleryTheme() {
+    
+      if( typeof G.O.colorScheme  !== 'undefined' ) {
+        G.O.galleryTheme = G.O.colorScheme;
+      }
+
+      var cs = null;
+      var galleryTheme = '';
+      switch(toType(G.O.galleryTheme)) {
+        case 'object':    // user custom color scheme object 
+          cs = G.galleryTheme_dark;  // default color scheme
+          jQuery.extend(true,cs,G.O.galleryTheme);
+          galleryTheme='nanogallery_gallerytheme_custom_' + G.baseEltID;
+          break;
+        case 'string':    // name of an internal defined color scheme
+          switch( G.O.galleryTheme ) {
+            case 'light':
+              cs = G.galleryTheme_light;
+              galleryTheme='nanogallery_gallerytheme_light_' + G.baseEltID;
+              break;
+            case 'default':
+            case 'dark':
+            case 'none':
+            default:
+              cs = G.galleryTheme_dark;
+              galleryTheme='nanogallery_gallerytheme_dark_' + G.baseEltID;
+          }
+          break;
+        default:
+          NanoAlert(G, 'Error in galleryTheme parameter.');
+          return;
+      }
+
+      //var s1='.nanogallery_theme_'+G.O.theme+' ';
+      var s1='.' + galleryTheme + ' ';
+    
+      // navigation bar
+      var c = cs.navigationBar;
+      var s=s1+'.nGY2Navigationbar { background:'+c.background+'; }'+'\n';
+      if( c.border !== undefined && c.border !== '' ) { s+=s1+'.nGY2Navigationbar { border:'+c.border+'; }'+'\n'; }
+      if( c.borderTop !== undefined && c.borderTop !== '' ) { s+=s1+'.nGY2Navigationbar { border-top:'+c.borderTop+'; }'+'\n'; }
+      if( c.borderBottom !== undefined && c.borderBottom !== '' ) { s+=s1+'.nGY2Navigationbar { border-bottom:'+c.borderBottom+'; }'+'\n'; }
+      if( c.borderRight !== undefined && c.borderRight !== '' ) { s+=s1+'.nGY2Navigationbar { border-right:'+c.borderRight+'; }'+'\n'; }
+      if( c.borderLeft !== undefined && c.borderLeft !== '' ) { s+=s1+'.nGY2Navigationbar { border-left:'+c.borderLeft+'; }'+'\n'; }
+      
+      // navigation bar - breadcrumb
+      var c = cs.navigationBreadcrumb;
+      s+=s1+'.nGY2Breadcrumb { background:'+c.background+'; border-radius:'+c.borderRadius+'; }'+'\n';
+      s+=s1+'.nGY2Breadcrumb .oneItem  { color:'+c.color+'; }'+'\n';
+      s+=s1+'.nGY2Breadcrumb .oneItem:hover { color:'+c.colorHover+'; }'+'\n';
+
+      // navigation bar - tag filter
+      var c = cs.navigationFilter;
+      s+=s1+'.nGY2NavFilterUnselected { color:'+c.color+'; background:'+c.background+'; border-radius:'+c.borderRadius+'; }'+'\n';
+      s+=s1+'.nGY2NavFilterSelected { color:'+c.colorSelected+'; background:'+c.backgroundSelected+'; border-radius:'+c.borderRadius+'; }'+'\n';
+      s+=s1+'.nGY2NavFilterSelectAll { color:'+c.colorSelected+'; background:'+c.background+'; border-radius:'+c.borderRadius+'; }'+'\n';
+
+      // navigation bar - pagination next/previous
+      var c = cs.navigationPagination;
+      s+=s1+'.nGY2NavPagination { color:'+c.color+'; background:'+c.background+'; border-radius:'+c.borderRadius+'; }'+'\n';
+      s+=s1+'.nGY2NavPagination:hover { color:'+c.colorHover+'; }'+'\n';
+      
+      // thumbnails
+      var c = cs.thumbnail;
+      // s+=s1+'.nGY2GThumbnail { border-radius: '+c.borderRadius+'; background:'+c.background+'; border-color:'+c.borderColor+'; border-top-width:'+G.tn.opt.Get('borderVertical')+'px; border-right-width:'+G.tn.opt.Get('borderHorizontal')+'px; border-bottom-width:'+G.tn.opt.Get('borderVertical')+'px; border-left-width:'+G.tn.opt.Get('borderHorizontal')+'px;}'+'\n';
+      s+=s1+'.nGY2GThumbnail { border-radius: '+c.borderRadius+'; background:'+c.background+'; border-color:'+c.borderColor+'; }'+'\n';
+      s+=s1+'.nGY2GThumbnail_l1 { border-top-width:'+G.tn.opt.l1.borderVertical+'px; border-right-width:'+G.tn.opt.l1.borderHorizontal+'px; border-bottom-width:'+G.tn.opt.l1.borderVertical+'px; border-left-width:'+G.tn.opt.l1.borderHorizontal+'px;}'+'\n';
+      s+=s1+'.nGY2GThumbnail_lN { border-top-width:'+G.tn.opt.lN.borderVertical+'px; border-right-width:'+G.tn.opt.lN.borderHorizontal+'px; border-bottom-width:'+G.tn.opt.lN.borderVertical+'px; border-left-width:'+G.tn.opt.lN.borderHorizontal+'px;}'+'\n';
+      s+=s1+'.nGY2GThumbnailStack { background:'+c.stackBackground+'; }'+'\n';
+      // s+=s1+'.nGY2GThumbnailImage { background:'+cs.thumbnail.background+'; background-image:'+cs.thumbnail.backgroundImage+'; }'+'\n';
+      s+=s1+'.nGY2TnImgBack { background:'+c.background+'; background-image:'+c.backgroundImage+'; }'+'\n';
+      s+=s1+'.nGY2GThumbnailAlbumUp { background:'+c.background+'; background-image:'+c.backgroundImage+'; color:'+cs.thumbnail.titleColor+'; }'+'\n';
+      s+=s1+'.nGY2GThumbnailIconsFullThumbnail { color:'+c.titleColor+'; }\n';
+      s+=s1+'.nGY2GThumbnailLabel { background:'+c.labelBackground+'; opacity:'+c.labelOpacity+'; }'+'\n';
+      s+=s1+'.nGY2GThumbnailImageTitle  { color:'+c.titleColor+'; background-color:'+c.titleBgColor+'; '+(c.titleShadow =='' ? '': 'Text-Shadow:'+c.titleShadow+';')+' }'+'\n';
+      s+=s1+'.nGY2GThumbnailAlbumTitle { color:'+c.titleColor+'; background-color:'+c.titleBgColor+'; '+(c.titleShadow =='' ? '': 'Text-Shadow:'+c.titleShadow+';')+' }'+'\n';
+      s+=s1+'.nGY2GThumbnailDescription { color:'+c.descriptionColor+'; background-color:'+c.descriptionBgColor+'; '+(c.descriptionShadow =='' ? '': 'Text-Shadow:'+c.descriptionShadow+';')+' }'+'\n';
+
+      // thumbnails - icons
+      var c = cs.thumbnailIcon;
+      s+=s1+'.nGY2GThumbnailIcons { padding:'+c.padding+'; }\n';
+      s+=s1+'.nGY2GThumbnailIcon { color:'+c.color+'; '+(c.shadow =='' ? '': 'Text-Shadow:'+c.shadow+';')+' }\n';
+      s+=s1+'.nGY2GThumbnailIconTextBadge { background-color:'+c.color+'; }\n';
+      
+      // gallery pagination -> dot/rectangle based
+      var c = cs.pagination;
+      if( G.O.galleryPaginationMode != 'NUMBERS' ) {
+        s+=s1+'.nGY2paginationDot { border:'+c.shapeBorder+'; background:'+c.shapeColor+';}\n';
+        s+=s1+'.nGY2paginationDotCurrentPage { border:'+c.shapeBorder+'; background:'+c.shapeSelectedColor+';}\n';
+        s+=s1+'.nGY2paginationRectangle { border:'+c.shapeBorder+'; background:'+c.shapeColor+';}\n';
+        s+=s1+'.nGY2paginationRectangleCurrentPage { border:'+c.shapeBorder+'; background:'+c.shapeSelectedColor+';}\n';
+      } else {
+        s+=s1+'.nGY2paginationItem { background:'+c.background+'; color:'+c.color+'; border-radius:'+c.borderRadius+'; }\n';
+        s+=s1+'.nGY2paginationItemCurrentPage { background:'+c.background+'; color:'+c.color+'; border-radius:'+c.borderRadius+'; }\n';
+        s+=s1+'.nGY2PaginationPrev { background:'+c.background+'; color:'+c.color+'; border-radius:'+c.borderRadius+'; }\n';
+        s+=s1+'.nGY2PaginationNext { background:'+c.background+'; color:'+c.color+'; border-radius:'+c.borderRadius+'; }\n';
+        s+=s1+'.nGY2paginationItemCurrentPage { background:'+c.backgroundSelected+'; }\n';
+      }
+      
+      // gallery more button
+      var c = cs.thumbnail;
+      // s+=s1+'.nGY2GalleryMoreButtonAnnotation { background:'+c.background+'; border-color:'+c.borderColor+'; border-top-width:'+G.O.thumbnailBorderVertical+'px; border-right-width:'+G.O.thumbnailBorderHorizontal+'px; border-bottom-width:'+G.O.thumbnailBorderVertical+'px; border-left-width:'+G.O.thumbnailBorderHorizontal+'px;}\n';
+      s+=s1+'.nGY2GalleryMoreButtonAnnotation { background:'+c.background+'; border-color:'+c.borderColor+'; border-top-width: 1px; border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px;}\n';
+      s+=s1+'.nGY2GalleryMoreButtonAnnotation  { color:'+c.titleColor+'; '+(c.titleShadow =='' ? '': 'Text-Shadow:'+c.titleShadow)+'; }\n';
+      
+      jQuery('head').append('<style id="ngycs_'+G.baseEltID+'">'+s+'</style>');
+      G.$E.base.addClass(galleryTheme);
+
+    };
+    
+    // ##### VIEWER COLOR SCHEME #####
+    function SetViewerTheme( ) {
+
+      if( G.VOM.viewerTheme != '' ) {
+        G.VOM.$baseCont.addClass(G.VOM.viewerTheme);
+        return;
+      }
+
+      if( typeof G.O.colorSchemeViewer  !== 'undefined' ) {
+        G.O.viewerTheme = G.O.colorSchemeViewer;
+      }
+
+      var cs=null;
+      switch(toType(G.O.viewerTheme)) {
+        case 'object':    // user custom color scheme object 
+          cs = G.viewerTheme_dark;
+          jQuery.extend(true, cs, G.O.viewerTheme);
+          G.VOM.viewerTheme = 'nanogallery_viewertheme_custom_' + G.baseEltID;
+          break;
+        case 'string':    // name of an internal defined color scheme
+          switch( G.O.viewerTheme ) {
+            case 'none':
+              return;
+              break;
+            case 'light':
+              cs = G.viewerTheme_light;
+              G.VOM.viewerTheme = 'nanogallery_viewertheme_light_' + G.baseEltID;
+              break;
+            case 'dark':
+            case 'default':
+              cs = G.viewerTheme_dark;
+              G.VOM.viewerTheme = 'nanogallery_viewertheme_dark_' + G.baseEltID;
+              break;
+          }
+          break;
+        default:
+          NanoAlert(G, 'Error in viewerTheme parameter.');
+          return;
+      }
+
+      var s1 = '.' + G.VOM.viewerTheme + ' ';
+      var s = s1 + '.nGY2Viewer { background:' + cs.background + '; }'+'\n';
+      s += s1 + '.nGY2Viewer .toolbarBackground { background:' + cs.barBackground + '; }'+'\n';
+      s += s1 + '.nGY2Viewer .toolbar { border:' + cs.barBorder + '; color:' + cs.barColor + '; }'+'\n';
+      s += s1 + '.nGY2Viewer .toolbar .previousButton:after { color:' + cs.barColor + '; }'+'\n';
+      s += s1 + '.nGY2Viewer .toolbar .nextButton:after { color:' + cs.barColor + '; }'+'\n';
+      s += s1 + '.nGY2Viewer .toolbar .closeButton:after { color:' + cs.barColor + '; }'+'\n';
+      s += s1 + '.nGY2Viewer .toolbar .label .title { color:' + cs.barColor + '; }'+'\n';
+      s += s1 + '.nGY2Viewer .toolbar .label .description { color:' + cs.barDescriptionColor + '; }'+'\n';
+      jQuery('head').append('<style>' + s + '</style>');
+      G.VOM.$baseCont.addClass(G.VOM.viewerTheme);
+    };
+
+    
+    
+    /** @function SetPolyFills */
+    function SetPolyFills() {
+
+      // POLYFILL FOR BIND function --> for older Safari mobile
+      // found on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind#Compatibility
+      if (!Function.prototype.bind) {
+        Function.prototype.bind = function (oThis) {
+          if (typeof this !== "function") {
+            // closest thing possible to the ECMAScript 5
+            // internal IsCallable function
+            throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
+          }
+
+          var aArgs = Array.prototype.slice.call(arguments, 1), 
+              fToBind = this, 
+              fNOP = function () {},
+              fBound = function () {
+                return fToBind.apply(this instanceof fNOP && oThis
+                       ? this
+                       : oThis,
+                       aArgs.concat(Array.prototype.slice.call(arguments)));
+              };
+
+          fNOP.prototype = this.prototype;
+          fBound.prototype = new fNOP();
+
+          return fBound;
+        };
+      }
+
+      // requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel
+      // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
+      // http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
+      // MIT license
+      (function() {
+        var lastTime = 0;
+        var vendors = ['ms', 'moz', 'webkit', 'o'];
+        for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
+          window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
+          window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
+        }
+        if (!window.requestAnimationFrame)
+          window.requestAnimationFrame = function(callback, element) {
+            var currTime = new Date().getTime();
+            var timeToCall = Math.max(0, 16 - (currTime - lastTime));
+            var id = window.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall);
+            lastTime = currTime + timeToCall;
+            return id;
+          };
+       
+        if (!window.cancelAnimationFrame)
+          window.cancelAnimationFrame = function(id) {
+            clearTimeout(id);
+          };
+      }());
+      
+      // array.removeIf -> removes items from array base on a function's result
+      Array.prototype.removeIf = function(callback) {
+        var i = this.length;
+        while (i--) {
+          if (callback(this[i], i)) {
+            this.splice(i, 1);
+          }
+        }
+      };  
+
+      // IE11 for startsWith 
+      // thanks to @lichtamberg - https://github.com/lichtamberg
+      if (!String.prototype.startsWith) {
+        String.prototype.startsWith = function(searchString, position) {
+          position = position || 0;
+          return this.indexOf(searchString, position) === position;
+        };
+      }
+      
+    }
+    
+    
+    // Gallery clicked or toolbar touched -> retrieve & execute action
+    function GalleryClicked(e) {
+    
+      var r = GalleryEventRetrieveElementl(e, false);
+
+      if( r.GOMidx == -1 ) { return 'exit'; }
+      
+      var idx = G.GOM.items[r.GOMidx].thumbnailIdx;
+      if( G.GOM.slider.hostIdx == r.GOMidx ) {
+        idx = G.GOM.items[G.GOM.slider.currentIdx].thumbnailIdx;
+      }
+      switch( r.action ) {
+        case 'OPEN':
+          ThumbnailOpen(idx, false);
+          return 'exit';
+          break;
+        case 'DISPLAY':
+          // used the display icon (ignore if selection mode)
+          ThumbnailOpen(idx, true);
+          return 'exit';
+          break;
+        case 'TOGGLESELECT':
+          ThumbnailSelectionToggle(idx);
+          return 'exit';
+          break;
+        case 'SHARE':
+          PopupShare(idx);
+          return 'exit';
+          break;
+        case 'DOWNLOAD':
+          DownloadImage(idx);
+          return 'exit';
+          break;
+        case 'INFO':
+          ItemDisplayInfo(G.I[idx]);
+          return 'exit';
+          break;
+        case 'SHOPPINGCART':
+          AddToCart(idx, 'gallery');
+          return 'exit';
+          break;
+        default:
+          // all other actions (custom1..10, or anything else)
+          var fu = G.O.fnThumbnailToolCustAction;
+          if( fu !== null ) {
+            typeof fu == 'function' ? fu(r.action, G.I[idx]) : window[fu](r.action, G.I[idx]);
+          }
+          break;
+      }
+    }
+
+    // Download an image
+    function DownloadImage(idx) {
+      if( G.I[idx].mediaKind != 'img' ) { return; }
+
+      
+      var url = G.I[idx].src;
+
+      if( G.I[idx].downloadURL != undefined && G.I[idx].downloadURL != '' ) {
+        url = G.I[idx].downloadURL;
+      }
+      
+      var a = document.createElement('a');
+      a.href = url;
+      // a.download = url.split('.').pop();
+      a.download = url.split('/').pop();
+      a.target = '_blank';
+      a.style.display = 'none';
+      document.body.appendChild(a);
+      a.click();
+      document.body.removeChild(a);      
+      
+    }
+    
+    // add one image to the shopping cart
+    function AddToCart( idx, source ) {
+      // increment quantity if already in shopping cart
+      var found=false;
+      for( var i=0; i<G.shoppingCart.length; i++ ) {
+        if( G.shoppingCart[i].idx == idx ) {
+          G.shoppingCart[i].qty++;
+          ThumbnailBuildToolbarOneCartUpdate( G.I[idx] );
+          
+          var fu = G.O.fnShoppingCartUpdated;
+          if( fu !== null ) {
+            typeof fu == 'function' ? fu(G.shoppingCart, G.I[idx], source) : window[fu](G.shoppingCart, G.I[idx], source);
+          }
+          TriggerCustomEvent('shoppingCartUpdated');
+          return;
+        }
+      }
+      
+      // add to shopping cart
+      if( !found) {
+        G.shoppingCart.push( { idx:idx, ID:G.I[idx].GetID(), qty:1} );
+        ThumbnailBuildToolbarOneCartUpdate(G.I[idx]);
+
+        var fu=G.O.fnShoppingCartUpdated;
+        if( fu !== null ) {
+          typeof fu == 'function' ? fu(G.shoppingCart, G.I[idx], source) : window[fu](G.shoppingCart, G.I[idx], source);
+        }
+        TriggerCustomEvent('shoppingCartUpdated');
+      }
+    }
+    
+
+    // All thumbnails are set to unselected
+    function ThumbnailSelectionClear() {
+      G.GOM.nbSelected = 0;
+      for( var i = 0, nbTn = G.GOM.items.length; i < nbTn ; i++ ) {
+        var item = G .I[G.GOM.items[i].thumbnailIdx];
+        if( item.selected ) {
+          item.selected = false;
+          var fu = G.O.fnThumbnailSelection;
+          if( fu !== null ) {
+            typeof fu == 'function' ? fu(item.$elt, item, G.I) : window[fu](item.$elt, item, G.I);
+          }
+        }
+        item.selected = false;
+      }
+    }
+    
+    function ThumbnailSelectionToggle( idx ){
+      var item = G.I[idx];
+      if( item.selected === true ) {
+        ThumbnailSelectionSet(item, false);
+        G.GOM.nbSelected--;
+        TriggerCustomEvent('itemUnSelected');
+      }
+      else {
+        ThumbnailSelectionSet(item, true);
+        G.GOM.nbSelected++;
+        TriggerCustomEvent('itemSelected');
+      }
+    }
+    
+    
+    // this replaces ThumbnailSelection()
+    function ThumbnailSelectionSet(item, selected ){
+      
+      item.selected = selected;
+      
+      ThumbnailSelectionSetIcon( item );
+      
+      // called when the selection status of an item changed
+      var fu=G.O.fnThumbnailSelection;
+      if( fu !== null ) {
+        typeof fu == 'function' ? fu(item.$elt, item, G.I) : window[fu](item.$elt, item, G.I);
+      }
+    
+    }
+    
+    function ThumbnailSelectionSetIcon( item ) {
+      if( item.$elt == null ) {
+        // thumbnail is not built
+        return;
+      }
+      var $sub = item.$getElt('.nGY2GThumbnail');
+      var $icon = item.$getElt('.nGY2GThumbnailIconImageSelect');
+      if( item.selected === true) {
+        $sub.addClass('nGY2GThumbnailSubSelected');
+        $icon.addClass('nGY2ThumbnailSelected');
+        $icon.removeClass('nGY2ThumbnailUnselected');
+        $icon.html(G.O.icons.thumbnailSelected);
+      }
+      else {
+        $sub.removeClass('nGY2GThumbnailSubSelected');
+        $icon.removeClass('nGY2ThumbnailSelected');
+        $icon.addClass('nGY2ThumbnailUnselected');
+        $icon.html(G.O.icons.thumbnailUnselected);
+      }
+    }
+    
+    
+    // display a modal popup for sharing image/album
+    function PopupShare(idx) {
+    
+      // SEE SAMPLES: https://gist.github.com/chrisjlee/5196139
+      // https://github.com/Julienh/Sharrre
+    
+      var item=G.I[idx];
+
+      var currentURL=document.location.protocol + '//' + document.location.hostname + document.location.pathname;
+      var newLocationHash = '#nanogallery/' + G.baseEltID + '/';
+      if( item.kind == 'image' ) {
+        newLocationHash += item.albumID + '/' + item.GetID();
+      }
+      else {
+        newLocationHash += item.GetID();
+      }
+    
+      var content = '<br><br>';
+      content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="facebook">'   + G.O.icons.shareFacebook +   '</div>';
+      content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="pinterest">'  + G.O.icons.sharePinterest +  '</div>';
+      content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="tumblr">'     + G.O.icons.shareTumblr +     '</div>';
+      content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="twitter">'    + G.O.icons.shareTwitter +    '</div>';
+      // content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="googleplus">' + G.O.icons.shareGooglePlus + '</div>';
+      content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="vk">'         + G.O.icons.shareVK +         '</div>';
+      content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="mail">'       + G.O.icons.shareMail +       '</div>';
+      content += '<div class="nGY2PopupOneItem" style="text-align:center;"></div>';
+      content += '<input class="nGY2PopupOneItemText" readonly type="text" value="' + currentURL+newLocationHash + '" style="width:100%;text-align:center;">';
+      content += '<br>';
+
+      currentURL = encodeURIComponent(document.location.protocol + '//' + document.location.hostname + document.location.pathname + newLocationHash);
+
+      var currentTitle = item.title;
+      var currentTn = item.thumbImg().src;
+      
+      
+      Popup('nanogallery2 - share to:', content, 'Center');
+      
+      G.popup.$elt.find('.nGY2PopupOneItem').on('click', function(e) {
+        e.stopPropagation();
+        
+        var shareURL = '';
+        var found = true;
+        switch(jQuery(this).attr('data-share').toUpperCase()) {
+          case 'FACEBOOK':
+            // <a name="fb_share" type="button" href="http://www.facebook.com/sharer.php?u={$url}&media={$imgPath}&description={$desc}" class="joinFB">Share Your Advertise</a>
+            //window.open("https://www.facebook.com/sharer.php?u="+currentURL,"","height=368,width=600,left=100,top=100,menubar=0");
+            shareURL = 'https://www.facebook.com/sharer.php?u=' + currentURL;
+            break;
+          case 'VK':
+            shareURL = 'http://vk.com/share.php?url=' + currentURL;
+            break;
+          case 'GOOGLEPLUS':
+            shareURL = "https://plus.google.com/share?url=" + currentURL;
+            break;
+          case 'TWITTER':
+            // shareURL="https://twitter.com/share?url="+currentURL+"&text="+currentTitle;
+            shareURL = 'https://twitter.com/intent/tweet?text=' + currentTitle + 'url=' + currentURL;
+            break;
+          case 'PINTEREST':
+            // shareURL='https://pinterest.com/pin/create/bookmarklet/?media='+currentTn+'&url='+currentURL+'&description='+currentTitle;
+            shareURL = 'https://pinterest.com/pin/create/button/?media=' + currentTn + '&url=' + currentURL + '&description=' + currentTitle;
+            break;
+          case 'TUMBLR':
+            //shareURL='https://www.tumblr.com/widgets/share/tool/preview?caption=<strong>'+currentTitle+'</strong>&tags=nanogallery2&url='+currentURL+'&shareSource=legacy&posttype=photo&content='+currentTn+'&clickthroughUrl='+currentURL;
+            shareURL = 'http://www.tumblr.com/share/link?url=' + currentURL + '&name=' + currentTitle;
+            break;
+          case 'MAIL':
+            shareURL = 'mailto:?subject=' + currentTitle + '&body=' + currentURL;
+            break;
+          default:
+            found = false;
+            break;
+        }
+        
+        if( found ) {
+          window.open(shareURL, "" , "height=550,width=500,left=100,top=100,menubar=0" );
+          G.popup.close();
+          // $popup.remove();
+        }
+        
+      });
+    }
+    
+    // build a modal popup
+    function Popup(title, content, align) {
+      var pp =  '<div class="nGY2Popup" style="opacity:0;"><div class="nGY2PopupContent' + align + '">';
+      pp    +=  '<div class="nGY2PopupCloseButton" style="font-size:0.9em;">' + G.O.icons.buttonClose + '</div>';
+      pp    +=  '<div class="nGY2PopupTitle">' + title + '</div>';
+      pp    +=  content;
+      pp    +=  '</div></div>';
+      
+      G.popup.$elt = jQuery(pp).appendTo('body');
+      setElementOnTop( G.VOM.$viewer, G.popup.$elt);
+      
+      G.popup.isDisplayed = true;
+      
+      var tweenable = new NGTweenable();
+      tweenable.tween({
+        from:       { o: 0, y: 100 },
+        to:         { o: 1, y: 0 },
+        easing:     'easeInOutSine',
+        duration:   250,
+        step:       function (state, att) {
+          G.popup.$elt[0].style.opacity = state.o;
+          G.popup.$elt[0].style[G.CSStransformName] = 'translateY(' + (state.y) + 'px)';
+        }
+      });
+      
+      G.popup.$elt.find('.nGY2PopupCloseButton').on('click', function(e) {
+        e.stopPropagation();
+        G.popup.close();
+      });
+      
+    }
+
+
+    function GalleryMouseEnter(e) {
+      if( !G.VOM.viewerDisplayed && G.GOM.albumIdx != -1 ) {
+        var r = GalleryEventRetrieveElementl(e, true);
+        // if( r.action == 'OPEN' && r.GOMidx != -1 ) {
+        if( r.GOMidx != -1 ) {
+          var target = e.target || e.srcElement;
+          // if( target.getAttribute('class') != 'nGY2GThumbnail' ) { return; }
+          ThumbnailHover(r.GOMidx);
+        }
+      }
+    }
+    
+    function GalleryMouseLeave(e) {
+      if( !G.VOM.viewerDisplayed && G.GOM.albumIdx != -1 ) {
+        var r = GalleryEventRetrieveElementl(e, true);
+        if( r.GOMidx != -1 ) {
+          var target = e.target || e.srcElement;
+          // if( target.getAttribute('class') != 'nGY2GThumbnail' ) { return; }
+          ThumbnailHoverOut(r.GOMidx);
+        }
+      }
+    }
+    
+    function GalleryEventRetrieveElementl( e, ignoreSubItems ) {
+      var r = { action: 'NONE', GOMidx: -1 };
+      
+      if( e == undefined ) {
+        return r;
+      }
+      var target = e.target || e.srcElement;
+      while( target != G.$E.conTnParent[0] ) {       // loop element parent up to find the thumbnail element
+        if( jQuery(target).hasClass('nGY2GThumbnail') ) {
+          if( r.action == 'NONE' ) {
+            r.action = 'OPEN';
+          }
+          r.GOMidx = jQuery(target).data('index');
+          return r;
+        }
+        // if( !ignoreSubItems && jQuery(target).hasClass('nGY2GThumbnailIcon') ) {
+        if( !ignoreSubItems ) {
+          var a = jQuery(target).data('ngy2action');
+          if( a != '' && a != undefined ) {
+            r.action = a;
+          }
+        }
+        if( target.parentNode == null ) {
+          return r;
+        }
+        target = target.parentNode;
+      }
+      return r;
+    }
+    
+
+    // OPEN ONE THUMBNAIL
+    function ThumbnailOpen( idx, ignoreSelected ) {
+      var item = G.I[idx];
+      
+      G.GOM.albumIdxLoading = idx;      // store idx -> may be used to display loader on album thumbnail
+
+      var fu = G.O.fnThumbnailClicked;
+      if( fu !== null ) {
+        typeof fu == 'function' ? fu(item.$elt, item) : window[fu](item.$elt, item);
+      }
+      
+      // open URL
+      if( item.destinationURL !== undefined && item.destinationURL.length > 0 ) {
+        window.location = item.destinationURL;
+        return;
+      }
+
+      switch( item.kind ) {
+        case 'image':
+          if( ignoreSelected === false && G.GOM.nbSelected > 0 ) {
+            ThumbnailSelectionToggle(idx);
+          }
+          else {
+            // display image
+            DisplayPhotoIdx( idx );
+          }
+          break;
+        case 'album':
+          if( ignoreSelected === false && G.GOM.nbSelected > 0 ) {
+            ThumbnailSelectionToggle( idx );
+          }
+          else {
+            if( G.O.thumbnailAlbumDisplayImage && idx != 0 ) {
+              // display album content in lightbox
+              DisplayFirstMediaInAlbum( idx );
+              return;
+            }
+            else {
+              // display album content in gallery
+              DisplayAlbum('-1', item.GetID());
+            }
+          }
+          break;
+        case 'albumUp':
+          var parent = NGY2Item.Get(G, item.albumID);
+          DisplayAlbum('-1', parent.albumID);
+          break;
+      }
+    }
+    
+    function DisplayFirstMediaInAlbum( albumIdx ) {
+      if( G.O.debugMode ) { console.log('#DisplayFirstPhotoInAlbum : '+  albumIdx); }
+
+      var item = G.I[albumIdx];
+      
+      var l = G.I.length;
+      for( var i = 0; i < l; i++ ) {
+        if( G.I[i].albumID == item.GetID() ) {
+          DisplayPhotoIdx( i );
+          return;
+        }
+      }
+      
+      // load album content
+      AlbumGetContent( item.GetID(), DisplayFirstMediaInAlbum, albumIdx, null );
+      
+    }
+    
+
+    // Open link to original image (new window)
+    function OpenOriginal( item ) {
+      switch( G.O.kind ) {
+        case 'flickr':
+          var sU = 'https://www.flickr.com/photos/' + G.O.userID + '/' + item.GetID();
+          if( item.albumID != '0' ) {
+            sU += '/in/album-' + item.albumID + '/';
+          }
+          window.open(sU, '_blank');
+          break;
+        case 'picasa':
+        case 'google':
+        case 'google2':
+          // no more working since Google changed the access to Google Photos in 2017
+          // var sU='https://plus.google.com/photos/'+G.O.userID+'/albums/'+item.albumID+'/'+item.GetID();
+          // window.open(sU,'_blank');
+          // break;
+        default:
+          var sU = item.responsiveURL();
+          window.open(sU, '_blank');
+          break;
+      }
+    }
+    
+    // ########################################################
+    // DISPLAY ONE MEDIA
+    // with internal or external viewer
+    // ########################################################
+    function DisplayPhotoIdx( ngy2ItemIdx ) {
+
+      if( !G.O.thumbnailOpenInLightox ) { return; }
+
+      if( G.O.thumbnailOpenOriginal ) {
+        // Open link to original image
+        OpenOriginal( G.I[ngy2ItemIdx] );
+        return;
+      }
+        
+      var items = [];
+//      G.VOM.currItemIdx = 0;
+      G.VOM.content.current.vIdx = 0;
+      G.VOM.items = [];
+      G.VOM.albumID = G.I[ngy2ItemIdx].albumID;
+      
+      var vimg = new VImg(ngy2ItemIdx);
+      G.VOM.items.push(vimg);
+      items.push(G.I[ngy2ItemIdx]);
+      //TODO -> danger? -> pourquoi reconstruire la liste si déjà ouvert (back/forward)     
+      var l = G.I.length;
+      for( var idx = ngy2ItemIdx+1; idx < l ; idx++) {
+        var item = G.I[idx];
+        if( item.kind == 'image' && item.isToDisplay(G.VOM.albumID) && item.destinationURL == '' ) {
+          var vimg = new VImg(idx);
+          G.VOM.items.push(vimg);
+          items.push(item);
+        }
+      }
+      var last = G.VOM.items.length;
+      var cnt = 1;
+      for( var idx = 0; idx < ngy2ItemIdx ; idx++) {
+        var item = G.I[idx];
+        if( item.kind == 'image' && item.isToDisplay(G.VOM.albumID) && item.destinationURL == '' ) {
+          var vimg = new VImg(idx);
+          vimg.mediaNumber = cnt;
+          G.VOM.items.push(vimg);
+          items.push(item);
+          cnt++;
+        }
+      }
+      for( var i = 0; i < last; i++ ) {
+        G.VOM.items[i].mediaNumber = cnt;
+        cnt++;
+      }
+    
+      // opens media with external viewer
+      var fu = G.O.fnThumbnailOpen;
+      if( fu !== null ) {
+        typeof fu == 'function' ? fu(items) : window[fu](items);
+        return;
+      }
+    
+      // use internal viewer
+      if( !G.VOM.viewerDisplayed ) {
+        // build viewer and display
+        LightboxOpen();
+      }
+      else {
+        // viewer already displayed -> display new media in current viewer
+        G.VOM.content.current.$media.empty();
+        var item = G.VOM.content.current.NGY2Item();
+        var spreloader = '<div class="nGY2ViewerMediaLoaderDisplayed"></div>';
+        if( item.mediaKind == 'img' && item.imageWidth != 0 && item.imageHeight != 0 ) {
+          spreloader = '<div class="nGY2ViewerMediaLoaderHidden"></div>';
+        }
+        G.VOM.content.current.$media.append( spreloader + item.mediaMarkup);
+        ViewerSetMediaVisibility(G.VOM.content.next, 0);
+        ViewerSetMediaVisibility(G.VOM.content.previous, 0);
+        if( item.mediaKind == 'img' ) {
+          G.VOM.ImageLoader.loadImage(VieweImgSizeRetrieved, item);
+        }
+        // G.VOM.$mediaCurrent.css({ opacity:0 }).attr('src','');
+        // G.VOM.ImageLoader.loadImage(VieweImgSizeRetrieved, G.VOM.NGY2Item(0));
+        // G.VOM.$mediaCurrent.children().eq(0).attr('src',G.emptyGif).attr('src', G.VOM.NGY2Item(0).responsiveURL());
+        // LightboxDisplay(0, '');
+        LightboxDisplay('');
+      }
+    }
+    
+    function ViewerZoomStart() {
+      if( G.O.viewerZoom && !G.VOM.viewerMediaIsChanged ) {
+        var item = G.VOM.content.current.NGY2Item();
+        if( item.imageHeight > 0 && item.imageWidth > 0 ) {
+          if( G.VOM.zoom.isZooming === false ) {
+            // default zoom
+            G.VOM.zoom.userFactor = 1;
+            G.VOM.zoom.isZooming = true;
+          }
+          return true;
+        }
+      }
+    }
+          
+    function ViewerZoomIn( zoomIn ) {
+    if( zoomIn ) {
+        // zoom in
+        G.VOM.zoom.userFactor += 0.1;
+        ViewerZoomMax();
+      }
+      else {
+        // zoom out
+        G.VOM.zoom.userFactor -= 0.1;
+        ViewerZoomMin();
+      }
+      ViewerMediaSetPosAndZoom();
+    }
+    
+    function ViewerZoomMax() {
+      if( G.VOM.zoom.userFactor > 3 ) {
+        G.VOM.zoom.userFactor = 3;
+      }
+    }
+    function ViewerZoomMin() {
+    
+      if( G.VOM.zoom.userFactor < 0.2 ) {
+        G.VOM.zoom.userFactor = 0.2;
+      }
+    }
+    
+    
+    
+    // Set position and size of all 3 media containers
+    function ViewerMediaSetPosAndZoom() {
+    
+      if( !G.VOM.zoom.isZooming ) {
+        G.VOM.zoom.userFactor = 1;
+      }
+      // window.ng_draf( function() {
+        ViewerMediaSetPosAndZoomOne( G.VOM.content.current, true );
+        ViewerMediaSetPosAndZoomOne( G.VOM.content.previous, false );
+        ViewerMediaSetPosAndZoomOne( G.VOM.content.next, false );
+      // });
+    }
+    
+
+    
+    // Media which is not IMG -> center and set size
+    function ViewerMediaCenterNotImg( $mediaContainer ) {
+      var $media = $mediaContainer.children().eq(1);
+      $media.css( {'height': '80%' });
+      $media.css( {'width':  '90%' });
+      $media[0].style[G.CSStransformName] = 'translate(0px, "50%") ';
+    }
+    
+    // Set position and size of ONE media container
+    function ViewerMediaSetPosAndZoomOne(content_item, isCurrent ) {
+
+      var item = content_item.NGY2Item();
+      var $img = content_item.$media;
+      
+
+      if( item.mediaKind != 'img' ) {
+        ViewerMediaCenterNotImg( $img );
+        return;
+      }
+      
+      if( item.imageHeight == 0 || item.imageWidth == 0 ) { 
+        // ViewerSetMediaVisibility( item, $img, 0 );
+        ViewerSetMediaVisibility( content_item, 0 );
+        return;
+      }
+
+      // part 1: set the image size
+      var zoomUserFactor = isCurrent == true ? G.VOM.zoom.userFactor : 1;
+      
+      var dpr = 1;
+      if( G.O.viewerImageDisplay == 'bestImageQuality' ) {
+        dpr = window.devicePixelRatio;
+      }
+      
+      // retrieve the base zoom factor (image fill screen)
+      var zoomBaseFactorW = (G.VOM.window.lastWidth  - G.VOM.padding.V) / (item.imageWidth  / dpr);
+      var zoomBaseFactorH = (G.VOM.window.lastHeight - G.VOM.padding.H) / (item.imageHeight / dpr);
+      var zoomBaseFactor = Math.min(zoomBaseFactorW, zoomBaseFactorH);
+      if( zoomBaseFactor > 1 && G.O.viewerImageDisplay != 'upscale' ) {
+        // no upscale
+        zoomBaseFactor = 1;
+      }
+
+      var imageCurrentHeight = (item.imageHeight / dpr) * zoomUserFactor * zoomBaseFactor;
+      var imageCurrentWidth  = (item.imageWidth / dpr)  * zoomUserFactor * zoomBaseFactor;
+      $img.children().eq(1).css( {'height': imageCurrentHeight });
+      $img.children().eq(1).css( {'width':  imageCurrentWidth  });
+
+      // retrieve posX/Y to center image
+      var posX = 0;
+      if( imageCurrentWidth > G.VOM.window.lastWidth ) {
+        posX = -(imageCurrentWidth - G.VOM.window.lastWidth)/2;
+      }
+      var posY = 0;
+      if( imageCurrentHeight > G.VOM.window.lastHeight ) {
+        posY = ( imageCurrentHeight - G.VOM.window.lastHeight ) / 2;
+      }
+      posY = 0;   // actually, it seems that the image is always centered vertically -> so no need to to anything
+      
+      // Part 2: set the X/Y position (for zoom/pan)
+      if( isCurrent ) {
+        if( !G.VOM.zoom.isZooming ) {
+          G.VOM.panPosX = 0;
+          G.VOM.panPosY = 0;
+        }
+        G.VOM.zoom.posX = posX;
+        G.VOM.zoom.posY = posY;
+        ViewerImagePanSetPosition(G.VOM.panPosX, G.VOM.panPosY, $img, false);
+      }
+      // else {
+        //$img[0].style[G.CSStransformName]= 'translate3D('+ posX+'px, '+ posY+'px, 0) ';
+      // }
+      else {
+        // set the pan position of each media container
+        ViewerMediaPanX( G.VOM.swipePosX );
+        $img.children().eq(1)[0].style[G.CSStransformName]= 'translate(0px, 0px) rotate('+ item.rotationAngle +'deg)';
+      }
+      
+    }
+
+    // position the image depending on the zoom factor and the pan X/Y position
+    // IMG is the only media supporting zoom/pan
+    function ViewerImagePanSetPosition(posX, posY, imageContainer, savePosition ) {
+      if( savePosition ) {
+        G.VOM.panPosX = posX;
+        G.VOM.panPosY = posY;
+      }
+
+      posX += G.VOM.zoom.posX;
+      posY += G.VOM.zoom.posY;
+      
+      // imageContainer.children().eq(1)[0].style[G.CSStransformName]= 'translate('+ posX + 'px, '+ posY + 'px)';
+      imageContainer.children().eq(1)[0].style[G.CSStransformName]= 'translate('+ posX + 'px, '+ posY + 'px) rotate('+ G.VOM.content.current.NGY2Item().rotationAngle +'deg)';
+
+
+    }
+    
+
+    // LIGHTBOX
+    // display media with internal viewer
+    function LightboxOpen( idx ) {
+
+      // G.VOM.viewerDisplayed = true;
+      G.GOM.firstDisplay = false;
+      
+      // remove scrollbar and add right margin with same width as the scrollbar to avoid page reflow
+      jQuery('head').append('<style id="nGY2_body_scrollbar_style" type="text/css">.nGY2_body_scrollbar{margin-right: ' + (window.innerWidth - document.documentElement.clientWidth) + 'px;}</style>');
+      jQuery("body").addClass("nGY2_body_scrollbar");
+        
+
+      G.VOM.$baseCont = jQuery('<div  class="nGY2 nGY2ViewerContainer" style="opacity:1"></div>').appendTo('body');
+      
+      SetViewerTheme();
+
+      G.VOM.$viewer = jQuery('<div class="nGY2Viewer" style="opacity:0" itemscope itemtype="http://schema.org/ImageObject"></div>').appendTo( G.VOM.$baseCont );
+      G.VOM.$viewer.css({ msTouchAction: 'none', touchAction: 'none' });            // avoid pinch zoom
+
+      if( idx == undefined ) {
+        G.VOM.content.current.vIdx = 0;
+      }
+      else {
+        G.VOM.content.current.vIdx = idx;
+      }
+      G.VOM.content.previous.vIdx = G.VOM.IdxNext();
+      G.VOM.content.next.vIdx = G.VOM.IdxPrevious();   
+ 
+      var sMedia = '<div class="nGY2ViewerMediaPan"><div class="nGY2ViewerMediaLoaderDisplayed"></div>' + G.VOM.content.previous.NGY2Item().mediaMarkup + '</div>';    // previous media
+      sMedia    += '<div class="nGY2ViewerMediaPan"><div class="nGY2ViewerMediaLoaderDisplayed"></div>' + G.VOM.content.current.NGY2Item().mediaMarkup  + '</div>';    // current media
+      sMedia    += '<div class="nGY2ViewerMediaPan"><div class="nGY2ViewerMediaLoaderDisplayed"></div>' + G.VOM.content.next.NGY2Item().mediaMarkup  + '</div>';    // next media
+
+      var sNav = '';
+      var iconP = G.O.icons.viewerImgPrevious;
+      if( iconP != undefined && iconP != '') {
+        sNav += '<div class="nGY2ViewerAreaPrevious ngy2viewerToolAction" data-ngy2action="previous">' + iconP + '</div>';
+      }
+      var iconN = G.O.icons.viewerImgNext;
+      if( iconN != undefined && iconN != '') {
+        sNav += '<div class="nGY2ViewerAreaNext ngy2viewerToolAction" data-ngy2action="next">' + iconN + '</div>';
+      }
+
+      G.VOM.$content = jQuery('<div class="nGY2ViewerContent">' + sMedia + sNav + '</div>').appendTo( G.VOM.$viewer );
+
+      G.VOM.$buttonLeft = G.VOM.$content.find('.nGY2ViewerAreaPrevious');
+      G.VOM.$buttonRight = G.VOM.$content.find('.nGY2ViewerAreaNext');
+
+      var $mediaPan = G.VOM.$content.find('.nGY2ViewerMediaPan');
+      G.VOM.content.previous.$media = $mediaPan.eq(0);    // pointer to previous media container
+      G.VOM.content.current.$media = $mediaPan.eq(1);     // pointer to current media container
+      G.VOM.content.next.$media = $mediaPan.eq(2);        // pointer to next media container
+
+      G.VOM.ImageLoader.loadImage( VieweImgSizeRetrieved, G.VOM.content.current.NGY2Item()  );
+      G.VOM.ImageLoader.loadImage( VieweImgSizeRetrieved, G.VOM.content.previous.NGY2Item() );
+      G.VOM.ImageLoader.loadImage( VieweImgSizeRetrieved, G.VOM.content.next.NGY2Item()  );
+      
+      G.VOM.padding.H = parseInt(G.VOM.$content.css("padding-left")) + parseInt(G.VOM.$content.css("padding-right"));
+      G.VOM.padding.V = parseInt(G.VOM.$content.css("padding-top")) + parseInt(G.VOM.$content.css("padding-bottom"));
+      
+      // build media toolbar container
+      var vtbBg1 = '';
+      var vtbBg2 = ' toolbarBackground';
+      if( G.O.viewerToolbar.fullWidth ) {
+        vtbBg1 = ' toolbarBackground';
+        vtbBg2 = '';
+      }
+      var vtbAlign = 'text-align:center;';
+      switch ( G.O.viewerToolbar.align ) {
+        case 'left':
+          vtbAlign = 'text-align:left;';
+          break;
+        case 'right':
+          vtbAlign = 'text-align:right;';
+          break;
+      }
+      var sTB = '<div class="toolbarContainer nGEvent' + vtbBg1 + '" style="visibility:' +(G.O.viewerToolbar.display ? "visible" : "hidden")+';'+vtbAlign+'"><div class="toolbar nGEvent' + vtbBg2 + '"></div></div>';
+      G.VOM.$toolbar = jQuery(sTB).appendTo(G.VOM.$viewer);
+
+      if( G.VOM.toolbarMode == 'min' || (G.O.viewerToolbar.autoMinimize > 0 && G.O.viewerToolbar.autoMinimize >= G.GOM.cache.viewport.w) ) {
+        ViewerToolbarForVisibilityMin();
+      }
+      else {
+        ViewerToolbarForVisibilityStd();
+      }
+      
+      // top-left toolbar
+      var sTopLeft = '<div class="nGY2ViewerToolsTopLeft nGEvent"><div class="toolbar nGEvent">';
+      var sTL = G.O.viewerTools.topLeft.split(',');
+      for( var i = 0, sTLL = sTL.length; i < sTLL; i++) {
+        sTopLeft += ToolbarAddElt( sTL[i] );
+      }
+      sTopLeft += '</div></div>';
+      G.VOM.$toolbarTL = jQuery(sTopLeft).appendTo(G.VOM.$viewer);
+
+      // top-right toolbar
+      var sTopRight = '<div class="nGY2ViewerToolsTopRight nGEvent"><div class="toolbar nGEvent">';
+      var sTR = G.O.viewerTools.topRight.split(',');
+      for( var i = 0, sTRL = sTR.length; i < sTRL; i++) {
+        sTopRight += ToolbarAddElt( sTR[i] );
+      }
+      sTopRight += '</div></div>';
+      G.VOM.$toolbarTR = jQuery(sTopRight).appendTo(G.VOM.$viewer);
+
+      // set the events handler on the toolbars
+      ViewerToolsOn();
+
+      // Go to fullscreen mode
+      if( ngscreenfull.enabled && G.O.viewerFullscreen ) { ngscreenfull.request(); }
+
+      // Gallery
+      LightboxGalleryBuild();
+
+      setElementOnTop('', G.VOM.$viewer);
+      ResizeLightbox(true);
+      G.VOM.gallery.Resize();
+      G.VOM.timeImgChanged = new Date().getTime();
+      
+      // viewer display transition
+      G.VOM.$toolbarTL.css('opacity', 0);
+      G.VOM.$toolbarTR.css('opacity', 0);
+      G.VOM.$buttonLeft.css('opacity', 0);
+      G.VOM.$buttonRight.css('opacity', 0);
+      G.VOM.gallery.$elt.css('opacity', 0);
+      G.VOM.$content.css('opacity', 0);
+      G.VOM.$toolbarTR[0].style[G.CSStransformName] = 'translateY(-40px) ';
+      G.VOM.$toolbarTL[0].style[G.CSStransformName] = 'translateY(-40px) ';
+      G.VOM.$buttonLeft[0].style[G.CSStransformName] = 'translateX(-40px) ';
+      G.VOM.$buttonRight[0].style[G.CSStransformName] = 'translateX(40px) ';
+
+      // STEP 1: display main container, including media
+      var tweenable = new NGTweenable();
+      tweenable.tween({
+        from:         { opacity: 0, posY: G.VOM.window.lastHeight*.5 },
+        to:           { opacity: 1, posY: 0 },
+        delay:        10,
+        duration:     450,
+        easing:       'easeInOutQuint',
+        step:         function (state) {
+          // lightbox
+          G.VOM.$viewer.css('opacity', state.opacity);
+          G.VOM.$viewer[0].style[G.CSStransformName] = 'translateY(' + (state.posY) + 'px) ';
+          
+          // media in lightbox
+          G.VOM.$content.css('opacity', state.opacity);
+        }
+      });
+      
+      
+      // STEP 2: display tools, left/right navigation buttons, gallery
+      var tweenable = new NGTweenable();
+      tweenable.tween({
+        from:         { posY: -40, opacity: 0, scale: 3 },
+        to:           { posY: 0, opacity: 1, scale: 1 },
+        delay:        300,
+        duration:     400,
+        easing:       'easeInOutQuint',
+        step:         function (state) {
+          
+          // tools
+          G.VOM.$toolbarTR[0].style[G.CSStransformName] = 'translateY(' + (state.posY) + 'px) ';
+          G.VOM.$toolbarTL[0].style[G.CSStransformName] = 'translateY(' + (state.posY) + 'px) ';
+          G.VOM.$buttonLeft[0].style[G.CSStransformName] = 'translateX(' + (state.posY) + 'px) ';
+          G.VOM.$buttonRight[0].style[G.CSStransformName] = 'translateX(' + (-state.posY) + 'px) ';
+          
+          // gallery
+          G.VOM.gallery.$elt.css({ opacity: state.opacity });
+          G.VOM.gallery.$elt[0].style[G.CSStransformName] = 'scale('+state.scale+')';
+        },
+        finish: function() {
+          G.VOM.viewerDisplayed = true;
+          ViewerMediaPanX(0);
+          ViewerSetEvents();
+
+          LightboxDisplay('');
+          
+          if( G.O.slideshowAutoStart ) {
+            G.VOM.playSlideshow = false;
+            SlideshowToggle();
+          }
+          
+          ViewerToolsUnHide();
+          LightboxDisplayFinalize('');
+        }
+      });
+
+
+      
+
+      // stop click propagation on media ==> if the user clicks outside of an media, the viewer is closed
+      // --> no more supported since v2.0.0
+      // G.VOM.$viewer.find('img').on('click', function (e) { e.stopPropagation(); });
+      
+      
+      // ViewerMediaPanX(0);
+      // ViewerSetEvents();
+
+      // LightboxDisplay('');
+      
+      // if( G.O.slideshowAutoStart ) {
+        // G.VOM.playSlideshow = false;
+        // SlideshowToggle();
+      // }
+    }
+    
+    function ViewerEvents() {
+      if( !G.VOM.viewerDisplayed || G.VOM.viewerMediaIsChanged ) {
+      // if( !G.VOM.viewerDisplayed || G.VOM.viewerMediaIsChanged || G.VOM.content.current.NGY2Item().mediaKind != 'img') {
+        // ignore fired event if viewer not displayed or if currently changed (or if current media not an image)
+        return false;
+      }
+      return true;
+    }
+    
+    // VIEWER - BUILD THE THUMBNAILS GALLERY
+    function LightboxGalleryBuild() {
+
+			G.VOM.gallery.firstDisplay = true;
+	
+			if( G.O.viewerGallery != 'none' ) {
+	
+				var tw = G.O.viewerGalleryTWidth;
+				var th = G.O.viewerGalleryTHeight;
+				var gutter = 2;
+				
+				var t = '';
+				for( var i=0; i< G.VOM.items.length; i++) {
+					var idx = G.VOM.items[i].ngy2ItemIdx;
+					var o = G.I[idx];
+					var src = (o.thumbImg().src).replace(/'/g, "%27");   // replace single quote with %27
+					t += '<div class="nGY2VThumbnail" style="width:'+tw+'px;height:'+th+'px;left:'+i*(tw+gutter*2)+'px;background-image: url(&apos;'+src+'&apos;);" data-ngy2_lightbox_thumbnail="true" data-ngy2_idx="' + idx + '" data-ngy2_vidx="' + i + '" ></div>';
+				}
+				G.VOM.gallery.gwidth = (tw+2*gutter) * G.VOM.items.length;
+				G.VOM.gallery.oneTmbWidth = tw+2*gutter;
+				var tc = "<div class='nGY2VThumbnailContainer' style='height:"+(th+gutter*2)+"px;left:0;width:"+G.VOM.gallery.gwidth+"px;' data-ngy2_lightbox_gallery='true'>" + t + "</div>";
+				G.VOM.gallery.$elt = jQuery('<div class="nGY2viewerGallery" style="display: inline-block;height:'+(th+gutter*2)+'px;left:0;right:0;">'+ tc +'</div>').appendTo(G.VOM.$viewer);
+				G.VOM.gallery.$tmbCont = G.VOM.gallery.$elt.find('.nGY2VThumbnailContainer')
+				
+				G.VOM.gallery.Resize();
+				G.VOM.gallery.SetThumbnailActive();
+      
+			}
+    }
+    
+
+    // Lightbox gesture handling
+    function ViewerSetEvents() {
+
+      if( G.VOM.hammertime == null ) {
+      
+        G.VOM.hammertime =  new NGHammer.Manager(G.VOM.$baseCont[0],  {
+          // domEvents: true,
+          recognizers: [
+            [NGHammer.Pinch, { enable: true }],
+            [NGHammer.Pan, { direction: NGHammer.DIRECTION_ALL }]
+          ]
+        });
+     
+        // PAN
+        G.VOM.hammertime.on('pan', function(ev) {
+          if( !ViewerEvents() ) { return; }
+
+          
+          if( G.VOM.panMode == 'off' ) {
+            // PAN START -> determine the element to pan
+            if( ev.target.dataset.ngy2_lightbox_thumbnail != undefined || ev.target.dataset.ngy2_lightbox_gallery != undefined ){
+              G.VOM.panMode = 'gallery';
+            }
+            else {
+              if( G.VOM.zoom.isZooming ) {
+                G.VOM.panMode = 'zoom';
+              }
+              else {
+                G.VOM.panMode = 'media';
+              }
+            }
+          }
+
+          // PAN the determined element
+          switch( G.VOM.panMode ) {
+            case 'zoom':
+              // pan zoomed image
+              ViewerImagePanSetPosition(G.VOM.panPosX + ev.deltaX, G.VOM.panPosY + ev.deltaY, G.VOM.content.current.$media, false);
+              G.VOM.toolsHide();
+              break;
+              
+            case 'media':
+              if( Math.abs(ev.deltaY) > G.VOM.panThreshold && Math.abs(ev.deltaX) < G.VOM.panThreshold && !G.VOM.panXOnly ) {
+                // pan viewer down/up to close the lightbox
+                ViewerMediaPanX( 0 );
+                var dist = 0;
+                if( ev.deltaY < 0 ) {
+                  // pan up
+                  dist = Math.max( ev.deltaY, -200);
+                }
+                else {
+                  // pan down
+                  dist = Math.min( ev.deltaY, 200);
+                }
+                G.VOM.$viewer[0].style[G.CSStransformName] = 'translateY(' + dist + 'px) ';
+                G.VOM.$viewer.css('opacity', 1-Math.abs(dist)/200/2);
+              }
+              else {
+                // pan media left/right
+                if( Math.abs(ev.deltaX) > G.VOM.panThreshold ) {
+                  G.VOM.panXOnly = true;
+                }
+                ViewerMediaPanX( ev.deltaX );
+                G.VOM.$viewer[0].style[G.CSStransformName] = 'translateY(0px)';
+                G.VOM.$viewer.css('opacity', 1);
+              }
+              break;
+              
+            case 'gallery':
+              G.VOM.gallery.PanGallery( ev.deltaX );
+              break;
+          }
+          
+        });
+
+        // PAN END
+        G.VOM.hammertime.on('panend', function(ev) {
+          if( !ViewerEvents() ) { return; }
+
+          switch( G.VOM.panMode ) {
+            case 'zoom':
+              // PAN END in image zoom mode
+              G.VOM.timeImgChanged = new Date().getTime();
+              ViewerImagePanSetPosition( G.VOM.panPosX+ev.deltaX, G.VOM.panPosY+ev.deltaY, G.VOM.content.current.$media, true);
+              break;
+            case 'media':
+              var panY = false;
+              if( !G.VOM.panXOnly ) {
+                if( Math.abs(ev.deltaY) > 50 && Math.abs(ev.deltaX) < 50 ) {
+                  // close viewer
+                  LightboxClose();
+                  panY = true;
+                }
+              }
+              if( !panY ) {
+                if( Math.abs( ev.deltaX ) < 50 ) {
+                  ViewerMediaPanX(0);
+                }
+                else {
+                  ev.deltaX > 50 ? DisplayPreviousMedia( Math.abs(ev.velocityX) ) : DisplayNextMedia( Math.abs(ev.velocityX) );
+                }
+              }
+              G.VOM.panXOnly = false;
+              break;
+            case 'gallery':
+              // PAN END on thumbnail gallery
+              G.VOM.gallery.posX += ev.deltaX;
+              G.VOM.gallery.PanGallery( 0 );
+              G.VOM.gallery.PanGalleryEnd( ev.velocityX );
+              break;
+          }
+
+          G.VOM.panMode = 'off';
+        });
+        
+				
+				// ZOOM FEATURE ENABLED
+        if( G.O.viewerZoom ) {
+
+					G.VOM.hammertime.add( new NGHammer.Tap({ event: 'doubletap', taps: 2, interval: 250 }) );
+          G.VOM.hammertime.add( new NGHammer.Tap({ event: 'singletap' }) );
+          G.VOM.hammertime.get('doubletap').recognizeWith('singletap');
+          G.VOM.hammertime.get('singletap').requireFailure('doubletap');
+
+          // single tap -> next/previous media
+          G.VOM.hammertime.on('singletap', function(ev) {
+
+						if( !ViewerEvents() ) { return; }
+						
+						// Gallery on viewer -> click/touch on one thumbnail -> display corresponding image
+						if( ev.target.dataset.ngy2_lightbox_thumbnail != undefined ){
+
+							var idx = parseInt(ev.target.dataset.ngy2_idx);
+							var vidx = parseInt(ev.target.dataset.ngy2_vidx);
+
+							if( idx != undefined && vidx != G.VOM.content.current.vIdx ) {
+                
+                if( vidx > G.VOM.content.current.vIdx ) {
+                  TriggerCustomEvent('lightboxNextImage');
+        
+                  // replace the next media with selected media
+                  G.VOM.content.next.$media.empty();
+                  var nextItem = G.I[idx];
+                  G.VOM.content.next.vIdx = vidx;
+                  var spreloader = '<div class="nGY2ViewerMediaLoaderDisplayed"></div>';
+                  if( nextItem.mediaKind == 'img' && nextItem.imageWidth != 0 && nextItem.imageHeight != 0 ) {
+                    spreloader = '<div class="nGY2ViewerMediaLoaderHidden"></div>';
+                  }
+                  G.VOM.content.next.$media.append( spreloader + nextItem.mediaMarkup );
+                  if( nextItem.mediaKind == 'img' ) {
+                    G.VOM.ImageLoader.loadImage(VieweImgSizeRetrieved, nextItem);
+                  }
+                  else {
+                    ViewerMediaCenterNotImg( G.VOM.content.next.$media );
+                  }
+                  LightboxDisplay('nextImage');
+
+                }
+                else {
+                  TriggerCustomEvent('lightboxPreviousImage');
+        
+                  // replace the previous media with selected media
+                  G.VOM.content.previous.$media.empty();
+                  var previousItem = G.I[idx];
+                  G.VOM.content.previous.vIdx = vidx;
+                  var spreloader = '<div class="nGY2ViewerMediaLoaderDisplayed"></div>';
+                  if( previousItem.mediaKind == 'img' && previousItem.imageWidth != 0 && previousItem.imageHeight != 0 ) {
+                    spreloader = '<div class="nGY2ViewerMediaLoaderHidden"></div>';
+                  }
+                  G.VOM.content.previous.$media.append( spreloader + previousItem.mediaMarkup );
+                  if( previousItem.mediaKind == 'img' ) {
+                    G.VOM.ImageLoader.loadImage(VieweImgSizeRetrieved, previousItem);
+                  }
+                  else {
+                    ViewerMediaCenterNotImg( G.VOM.content.previous.$media );
+                  }
+                  LightboxDisplay('previousImage');
+                }
+								return;
+							}
+						}
+						
+						
+            StopPropagationPreventDefault(ev.srcEvent);
+            if( G.VOM.toolbarsDisplayed == false ) {
+              debounce( ViewerToolsUnHide, 100, false)();
+							G.VOM.singletapTime = new Date().getTime();
+            }
+            else {
+							// toolbars are displayed -> display next/previous media
+							if( (new Date().getTime()) - G.VOM.singletapTime < 400 ) { return; }		// to avoid conflict with MOUSEMOVE event
+              if( ev.target.className.indexOf('nGY2ViewerMedia') !== -1 ) {
+								var x =0;
+								if( ev.srcEvent instanceof MouseEvent ) {
+									x = ev.srcEvent.pageX;
+								}
+								else {
+									x = ev.srcEvent.changedTouches[0].pageX;
+								}
+								if( x < (G.GOM.cache.viewport.w/2) ) {
+                  DisplayPreviousMedia();
+                }
+                else {
+                  DisplayNextMedia();
+                }
+              }
+            }
+          });
+          
+          // double tap -> zoom
+          G.VOM.hammertime.on('doubletap', function(ev) {
+            if( !ViewerEvents() ) { return; }
+            StopPropagationPreventDefault(ev.srcEvent);
+            
+            if( ev.target.className.indexOf('nGY2ViewerMedia') !== -1 ) {
+              // double tap only on image
+              if( G.VOM.zoom.isZooming ) {
+                G.VOM.zoom.isZooming = false;
+                // G.VOM.zoom.userFactor = 1;
+                ResizeLightbox(true);
+              }
+              else {
+                if( ViewerZoomStart() ) {
+                  G.VOM.zoom.userFactor = 1.5;
+                  ViewerMediaSetPosAndZoom();
+                }
+              }
+            }
+          });
+        
+          // pinch end
+          G.VOM.hammertime.on('pinchend', function(ev) {
+            ev.srcEvent.stopPropagation();
+            ev.srcEvent.preventDefault();  // cancel  mouseenter event
+            G.VOM.timeImgChanged = new Date().getTime();
+          });
+          G.VOM.hammertime.on('pinch', function(ev) {
+            ev.srcEvent.stopPropagation();
+            ev.srcEvent.preventDefault();  // cancel  mouseenter event
+            
+            if( ViewerZoomStart() ) {
+              G.VOM.zoom.userFactor = ev.scale;
+              ViewerZoomMax();
+              ViewerZoomMin();
+              ViewerMediaSetPosAndZoom();   // center media
+            }
+          });
+        }
+				
+				
+        else {
+					// ZOOM FEATURE DISABLED
+
+					G.VOM.hammertime.add( new NGHammer.Tap({ event: 'singletap' }) );
+
+          // click/tap on image to go to next/previous one
+          // G.VOM.hammertime.on('tap', function(ev) {
+          G.VOM.hammertime.on('singletap', function(ev) {
+            if( !ViewerEvents() ) { return; }
+            StopPropagationPreventDefault( ev.srcEvent );
+            if( G.VOM.toolbarsDisplayed == false  ){
+              // display tools on tap if hidden
+              debounce( ViewerToolsUnHide, 100, false)();
+							G.VOM.singletapTime = new Date().getTime();
+            }
+            else {
+							// toolbars are displayed -> display next/previous media
+							if( (new Date().getTime()) - G.VOM.singletapTime < 400 ) { return; }		// to avoid conflict with MOUSEMOVE event
+              if( ev.target.className.indexOf('nGY2ViewerMedia') !== -1 ) {
+								var x = 0;
+								if( ev.srcEvent instanceof MouseEvent ) {
+									x = ev.srcEvent.pageX;
+								}
+								else {
+									x = ev.srcEvent.changedTouches[0].pageX;
+								}
+                if( x < (G.GOM.cache.viewport.w/2) ) {
+                  DisplayPreviousMedia();
+                }
+                else {
+                  DisplayNextMedia();
+                }
+              }
+            }
+            
+          });
+        }
+      }
+    }
+
+
+    function StopPropagationPreventDefault(e) {
+      e.stopPropagation();
+      e.preventDefault();
+    }
+
+    // Hide toolbars on user inactivity
+    function ViewerToolsHide() {
+      if( G.VOM.viewerDisplayed ) {
+        G.VOM.toolbarsDisplayed = false;
+        ViewerToolsOpacity(0);
+      }
+    }
+    
+    function ViewerToolsUnHide() {
+			if( G.VOM.viewerDisplayed ) {
+        G.VOM.toolbarsDisplayed = true;
+        ViewerToolsOpacity(1);
+        G.VOM.toolsHide();        // re-init delay before hide tools+gallery 
+      }
+    }
+    
+    function ViewerToolsOpacity( op ) {
+      if( G.VOM.$toolbar != null ) {
+        G.VOM.$toolbar.css('opacity', op);
+      }
+      if( G.VOM.$toolbarTL != null ) {
+        G.VOM.$toolbarTL.css('opacity', op);
+      }
+      if( G.VOM.$toolbarTR != null ) { 
+        G.VOM.$toolbarTR.css('opacity', op);
+      }
+      
+      // next/previous
+      G.VOM.$content.find('.nGY2ViewerAreaNext').css('opacity', op);
+      G.VOM.$content.find('.nGY2ViewerAreaPrevious').css('opacity', op);
+      
+      // gallery
+      // G.VOM.gallery.$elt.css('opacity', op);
+    }
+    
+    
+    
+    function ViewerToolsOn() {
+      // removes all events
+      G.VOM.$viewer.off('touchstart click', '.ngy2viewerToolAction', ViewerToolsAction); 
+      
+      // action button
+      G.VOM.$viewer.on('touchstart click', '.ngy2viewerToolAction', ViewerToolsAction); 
+    }
+
+    
+    // Actions of the button/elements
+    function ViewerToolsAction(e) {
+      // delay to avoid twice handling on smartphone/tablet (both touchstart click events are fired)
+      if( (new Date().getTime()) - G.timeLastTouchStart < 300 ) { return; }
+      G.timeLastTouchStart = new Date().getTime();
+      
+      var $this = $(this);
+      var ngy2action = $this.data('ngy2action');
+      if( ngy2action == undefined ) { return; }
+      switch( ngy2action ) {
+        case 'next':
+          StopPropagationPreventDefault(e);
+          DisplayNextMedia();
+          break;
+        case 'previous':
+          StopPropagationPreventDefault(e);
+          DisplayPreviousMedia();
+          break;
+        case 'playPause':
+          e.stopPropagation();
+          SlideshowToggle();
+          break;
+        case 'zoomIn':
+          StopPropagationPreventDefault(e);
+          if( ViewerZoomStart() ) { ViewerZoomIn( true ); }
+          break;
+        case 'zoomOut':
+          StopPropagationPreventDefault(e);
+          if( ViewerZoomStart() ) { ViewerZoomIn( false ); }
+          break;
+        case 'minimize':
+          // toggle toolbar visibility
+          StopPropagationPreventDefault(e);
+          if( G.VOM.toolbarMode == 'std' ) {
+            ViewerToolbarForVisibilityMin();
+          }
+          else {
+            ViewerToolbarForVisibilityStd();
+          }
+          break;
+        case 'fullScreen':
+          // Toggle viewer fullscreen mode on/off
+          e.stopPropagation();
+          if( ngscreenfull.enabled ) {
+            ngscreenfull.toggle();
+          }
+          break;
+        case 'info':
+          e.stopPropagation();
+          ItemDisplayInfo( G.VOM.content.current.NGY2Item() );
+          break;
+        case 'close':
+          StopPropagationPreventDefault(e);
+          if( (new Date().getTime()) - G.VOM.timeImgChanged < 400 ) { return; }
+          LightboxClose();
+          break;
+        case 'download':
+          StopPropagationPreventDefault(e);
+          DownloadImage(G.VOM.items[G.VOM.content.current.vIdx].ngy2ItemIdx);
+          break;
+        case 'share':
+          StopPropagationPreventDefault(e);
+          PopupShare(G.VOM.items[G.VOM.content.current.vIdx].ngy2ItemIdx);
+          break;
+        case 'linkOriginal':
+          StopPropagationPreventDefault(e);
+          OpenOriginal( G.VOM.content.current.NGY2Item() );
+          break;
+        case 'rotateLeft':
+          StopPropagationPreventDefault(e);
+          ViewerImageRotate(-90);
+          break;
+        case 'rotateRight':
+          StopPropagationPreventDefault(e);
+          ViewerImageRotate(90);
+          break;
+        case 'shoppingcart':
+          StopPropagationPreventDefault(e);
+          AddToCart( G.VOM.items[G.VOM.content.current.vIdx].ngy2ItemIdx, 'lightbox');
+          break;
+      }
+      
+      // custom button
+      var fu = G.O.fnImgToolbarCustClick;
+      if( ngy2action.indexOf('custom') == 0  && fu !== null ) {
+        typeof fu == 'function' ? fu(ngy2action, $this, G.VOM.content.current.NGY2Item() ) : window[fu](ngy2action, $this, G.VOM.content.current.NGY2Item() );
+      }
+    }
+
+    // rotate displayed image
+    function ViewerImageRotate( angle ) {
+      var item = G.VOM.content.current.NGY2Item();
+      if( item.mediaKind == 'img' ) {
+        item.rotationAngle += angle;
+        item.rotationAngle = item.rotationAngle % 360;
+        if( item.rotationAngle < 0 ) {
+          item.rotationAngle += 360;
+        }
+        ViewerMediaPanX( 0 );
+        ViewerMediaSetPosAndZoomOne( G.VOM.content.current, true );
+      }
+    }
+     
+
+    // Display photo infos in popup/modal
+    function ItemDisplayInfo( ng2item ) {
+
+      var content = '<div class="nGY2PopupOneItem">' + ng2item.title + '</div>';
+      content    += '<div class="nGY2PopupOneItemText">' + ng2item.description + '</div>';
+      if( ng2item.author != '' ) {
+        content  += '<div class="nGY2PopupOneItemText">' + G.O.icons.user + ' ' + ng2item.author + '</div>';
+      }
+      if( ng2item.exif.model != '' ) {
+        content  += '<div class="nGY2PopupOneItemText">' + G.O.icons.config + ' ' + ng2item.exif.model + '</div>';
+      }
+      var sexif = G.O.icons.picture + ':';
+      if( ng2item.exif.flash != '' || ng2item.exif.focallength != '' || ng2item.exif.fstop != '' || ng2item.exif.exposure != '' || ng2item.exif.iso != '' || ng2item.exif.time != '' ) {
+      sexif += '<br>';
+      sexif += ng2item.exif.flash == '' ? '' : ' &nbsp; ' + ng2item.exif.flash;
+      sexif += ng2item.exif.focallength == '' ? '' : ' &nbsp; ' + ng2item.exif.focallength+'mm';
+      sexif += ng2item.exif.fstop == '' ? '' : ' &nbsp; f' + ng2item.exif.fstop;
+      sexif += ng2item.exif.exposure == '' ? '' : ' &nbsp; ' + ng2item.exif.exposure+'s';
+      sexif += ng2item.exif.iso == '' ? '' : ' &nbsp; ' + ng2item.exif.iso+' ISO';
+      if( ng2item.exif.time != '' ) {
+        // var date = new Date(parseInt(ng2item.exif.time));
+        // sexif += ' &nbsp; '+date.toLocaleDateString();
+        sexif += ' &nbsp; ' + ng2item.exif.time;
+      }
+      }
+      else {
+        sexif += ' n/a';
+      }
+      content += '<div class="nGY2PopupOneItemText">' + sexif + '</div>';
+
+      if( ng2item.exif.location != '' ) {
+        content += '<div class="nGY2PopupOneItemText">'+G.O.icons.location+' <a href="http://maps.google.com/maps?z=12&t=m&q='+encodeURIComponent(ng2item.exif.location)+'" target="_blank">'+ng2item.exif.location+'</a></div>';
+        // embed google map in iframe (no api key required)
+        content += '<iframe width="300" height="150" frameborder="0" scrolling="no" marginheight="0" marginwidth="0" src="https://maps.google.com/maps?&amp;t=m&amp;q='+encodeURIComponent( ng2item.exif.location ) +'&amp;output=embed"></iframe>';  
+      }
+      else {
+        content += '<div class="nGY2PopupOneItemText">' + G.O.icons.location + ': n/a</div>';
+      }
+
+      var r = { title: G.O.icons.viewerInfo, content: content };
+
+      // callback
+      var fu = G.O.fnPopupMediaInfo;
+      if( fu !== null ) {
+        typeof fu == 'function' ? r=fu(ng2item, r.title, r.content) : r=window[fu](ng2item, r.title, r.content);
+      }
+
+
+      Popup( r.title, r.content, 'Left');
+   
+    }
+    
+
+    
+    function ToolbarAddElt( elt ) {
+      var r = '<div class="ngbt ngy2viewerToolAction ',
+      e=elt.replace(/^\s+|\s+$/g, '');    // remove trailing/leading whitespace
+      switch( e ) {
+        case 'minimizeButton':
+        case 'minimize':
+          var ic = G.O.icons.viewerToolbarMin;
+          if( G.VOM.toolbarMode == 'min' ) {
+            ic = G.O.icons.viewerToolbarStd;
+          }
+          r += 'minimizeButton nGEvent" data-ngy2action="minimize">'+ic+'</div>';
+          break;
+        case 'previousButton':
+        case 'previous':
+          r += 'previousButton nGEvent" data-ngy2action="previous">'+ G.O.icons.viewerPrevious +'</div>';
+          break;
+        case 'pageCounter':
+          r += 'pageCounter nGEvent"></div>';
+          break;
+        case 'nextButton':
+        case 'next':
+          r += 'nextButton nGEvent" data-ngy2action="next">'+ G.O.icons.viewerNext +'</div>';
+          break;
+        case 'playPauseButton':
+        case 'playPause':
+          r += 'playButton playPauseButton nGEvent" data-ngy2action="playPause">'+ G.O.icons.viewerPlay +'</div>';
+          break;
+        case 'rotateLeft':
+          r += 'rotateLeftButton nGEvent" data-ngy2action="rotateLeft">'+ G.O.icons.viewerRotateLeft +'</div>';
+          break;
+        case 'rotateRight':
+          r += 'rotateRightButton nGEvent" data-ngy2action="rotateRight">'+ G.O.icons.viewerRotateRight +'</div>';
+          break;
+        case 'downloadButton':
+        case 'download':
+          r += 'downloadButton nGEvent" data-ngy2action="download">'+ G.O.icons.viewerDownload +'</div>';
+          break;
+        case 'zoomButton':
+        case 'zoom':
+          r += 'nGEvent" data-ngy2action="zoomIn">'+ G.O.icons.viewerZoomIn +'</div><div class="ngbt ngy2viewerToolAction nGEvent" data-ngy2action="zoomOut">'+ G.O.icons.viewerZoomOut +'</div>';
+          break;
+        case 'fullscreenButton':
+        case 'fullscreen':
+          var s = G.O.icons.viewerFullscreenOn;
+          if( ngscreenfull.enabled && G.VOM.viewerIsFullscreen ) {
+            s = G.O.icons.viewerFullscreenOff;
+          }
+          r += 'setFullscreenButton fullscreenButton nGEvent" data-ngy2action="fullScreen">'+s+'</div>';
+          break;
+        case 'infoButton':
+        case 'info':
+          r += 'infoButton nGEvent" data-ngy2action="info">'+ G.O.icons.viewerInfo +'</div>';
+          break;
+        case 'linkOriginalButton':
+        case 'linkOriginal':
+          r += 'linkOriginalButton nGEvent" data-ngy2action="linkOriginal">' + G.O.icons.viewerLinkOriginal + '</div>';
+          break;
+        case 'closeButton':
+        case 'close':
+          r += 'closeButton nGEvent" data-ngy2action="close">'+ G.O.icons.buttonClose +'</div>';
+          break;
+        case 'shareButton':
+        case 'share':
+          r += 'nGEvent" data-ngy2action="share">'+ G.O.icons.viewerShare +'</div>';
+          break;
+        case 'label':
+          r += '"><div class="label"><div class="title nGEvent" itemprop="name"></div><div class="description nGEvent" itemprop="description"></div></div></div>';
+          break;
+        case 'shoppingcart':
+          r += 'closeButton nGEvent" data-ngy2action="shoppingcart">'+ G.O.icons.viewerShoppingcart +'</div>';
+          break;
+        default:
+          // custom button
+          if( e.indexOf('custom') == 0 ) {
+            var t = '';
+            // content to display from custom script
+            var fu = G.O.fnImgToolbarCustInit;
+            if( fu !== null ) {
+              typeof fu == 'function' ? fu(e) : window[fu](e);
+            }
+            if( t == undefined || t == '' ) {
+              // content from icons
+              var n = e.substring(6);
+              t = G.O.icons['viewerCustomTool'+n];
+            }
+            r += 'ngy2CustomBtn ' + e + ' nGEvent" data-ngy2action="' + e + '">' + t + '</div>';
+          }
+          else {
+            r = '';
+          }
+          break;
+      }
+      return r;
+    }
+    
+    
+    // toggle slideshow mode on/off
+    function SlideshowToggle(){
+      if( G.VOM.playSlideshow ) {
+        window.clearTimeout(G.VOM.playSlideshowTimerID);
+        G.VOM.playSlideshow = false;
+        G.VOM.$viewer.find('.playPauseButton').html(G.O.icons.viewerPlay);
+      }
+      else {
+        G.VOM.playSlideshow = true;
+        DisplayNextMedia();
+        G.VOM.$viewer.find('.playPauseButton').html(G.O.icons.viewerPause);
+      }
+    }
+
+    function ViewerToolbarForVisibilityStd() {
+      G.VOM.toolbarMode = 'std';
+      
+      var sTB = '';
+      var t = G.O.viewerToolbar.standard.split(',');
+      for( var i = 0, lt = t.length; i < lt; i++) {
+        sTB += ToolbarAddElt( t[i] );
+      }
+      G.VOM.$toolbar.find('.toolbar').html(sTB);
+      ViewerToolbarElementContent();
+    }
+    
+    function ViewerToolbarForVisibilityMin() {
+      if( G.O.viewerToolbar.minimized == undefined || G.O.viewerToolbar.minimized == '' ) {
+        ViewerToolbarForVisibilityStd();
+      }
+      else {
+        G.VOM.toolbarMode = 'min';
+        var sTB = '';
+        var t = G.O.viewerToolbar.minimized.split(',');
+        for( var i = 0, lt = t.length; i < lt; i++) {
+          sTB += ToolbarAddElt( t[i] );
+        }
+        G.VOM.$toolbar.find('.toolbar').html(sTB);
+        ViewerToolbarElementContent();
+      }
+    }
+    
+    function ViewerToolbarElementContent() {
+    
+      var vomIdx = G.VOM.content.current.vIdx;
+      if( vomIdx == null ) { return; }
+      
+      var item = G.VOM.content.current.NGY2Item();
+    
+      // LABEL
+      var setTxt = false;
+      // set title
+      if( item.title !== undefined && item.title != '' ) {
+        G.VOM.$viewer.find('.ngy2viewerToolAction').find('.title').html(item.title);
+        setTxt = true;
+      }
+      else {
+        G.VOM.$viewer.find('.ngy2viewerToolAction').find('.title').html('');
+      }
+      // set description
+      if( item.description !== undefined && item.description != '' ) {
+        G.VOM.$viewer.find('.ngy2viewerToolAction').find('.description').html(item.description);
+        setTxt = true;
+      }
+      else {
+        G.VOM.$viewer.find('.ngy2viewerToolAction').find('.description').html('');
+      }
+      
+      if( setTxt ) {
+        G.VOM.$viewer.find('.ngy2viewerToolAction').find('.label').show();
+      }
+      else {
+        G.VOM.$viewer.find('.ngy2viewerToolAction').find('.label').hide();
+      }
+      
+      // set page number
+      var viewerMaxImages = G.VOM.items.length;
+      if( viewerMaxImages > 0 ) {
+        G.VOM.$viewer.find('.pageCounter').html((G.VOM.items[vomIdx].mediaNumber)+'/'+viewerMaxImages);
+      }
+      
+      // custom elements
+      var $cu = G.VOM.$viewer.find('.ngy2CustomBtn');
+      var fu = G.O.fnImgToolbarCustDisplay;
+      if( $cu.length > 0 && fu !== null ) {
+        typeof fu == 'function' ? fu($cu, item) : window[fu]($cu, item);
+      }
+      
+      // set event handlers again
+      ViewerToolsOn();
+    }
+    
+    // Pan the media container in the lightbox (left/right)
+    function ViewerMediaPanX( posX ) {
+      G.VOM.swipePosX = posX;
+      if( G.CSStransformName == null ) {
+        // no pan if CSS transform not supported
+        // G.VOM.$mediaCurrent.css({ left: posX }); 
+      }
+      else {
+      
+        // pan left/right the current media
+        // window.ng_draf( function() {
+          G.VOM.content.current.$media[0].style[G.CSStransformName] = 'translate(' + posX + 'px, 0px)';
+        // });
+
+        var itemPrevious = G.VOM.content.previous.NGY2Item();
+        var itemNext = G.VOM.content.next.NGY2Item();
+        
+        // next/previous media
+        if(  G.O.imageTransition.startsWith('SWIPE') ) {
+          if( itemPrevious.mediaTransition() ) {
+            ViewerSetMediaVisibility(G.VOM.content.previous, 1);
+          }
+          if( itemNext.mediaTransition() ) {
+            ViewerSetMediaVisibility(G.VOM.content.next, 1);
+          }
+
+          var sc = Math.min( Math.max( Math.abs(posX) / G.VOM.window.lastWidth, .8), 1);
+          if( G.O.imageTransition == 'SWIPE' ) { sc = 1; }
+
+          if( posX > 0 ) {
+            var dir = G.VOM.window.lastWidth;
+            if( itemPrevious.mediaTransition() ) {
+              // window.ng_draf( function() {
+                G.VOM.content.previous.$media[0].style[G.CSStransformName] = 'translate(' + (-dir + posX) + 'px, 0px) scale(' + sc + ')';
+              // });
+            }
+            if( itemNext.mediaTransition() ) {
+              // window.ng_draf( function() {
+                G.VOM.content.next.$media[0].style[G.CSStransformName] = 'translate(' + (dir) + 'px, 0px) scale(' + sc + ')';
+              // });
+            }
+          }
+          else {
+            var dir = -G.VOM.window.lastWidth;
+            if( itemNext.mediaTransition() ) {
+              // window.ng_draf( function() {
+                G.VOM.content.next.$media[0].style[G.CSStransformName] = 'translate(' + (-dir + posX) + 'px, 0px) scale(' + sc + ')';
+              // });
+            }
+            if( itemPrevious.mediaTransition() ) {
+              // window.ng_draf( function() {
+                G.VOM.content.previous.$media[0].style[G.CSStransformName] = 'translate(' + (dir) + 'px, 0px) scale(' + sc + ')';
+              // });
+            }
+          }
+        }
+        
+        
+        if(  G.O.imageTransition == 'SLIDEAPPEAR' ) {
+          G.VOM.content.previous.$media[0].style[G.CSStransformName] = '';
+          G.VOM.content.next.$media[0].style[G.CSStransformName] = '';
+          if( posX < 0 ) {
+            var o = (-posX) / G.VOM.window.lastWidth;
+            if( itemNext.mediaTransition() ) {
+              ViewerSetMediaVisibility(G.VOM.content.next, o);
+            }
+            if( itemPrevious.mediaTransition() ) {
+              ViewerSetMediaVisibility(G.VOM.content.previous, 0);
+            }
+          }
+          else {
+            var o = posX / G.VOM.window.lastWidth;
+            if( itemPrevious.mediaTransition() ) {
+              ViewerSetMediaVisibility(G.VOM.content.previous, o);
+            }
+            if( itemNext.mediaTransition() ) {
+              ViewerSetMediaVisibility(G.VOM.content.next, 0);
+            }
+          }
+        }
+      }
+    }
+    
+    // Display next image
+    function DisplayNextMedia( velocity ) {
+      velocity = velocity || 0;
+      
+      if( G.VOM.viewerMediaIsChanged || ((new Date().getTime()) - G.VOM.timeImgChanged < 300) ) { return; }
+      
+      TriggerCustomEvent('lightboxNextImage');
+      LightboxDisplay('nextImage', velocity);
+    };
+    
+    // Display previous image
+    function DisplayPreviousMedia( velocity ) {
+      velocity = velocity || 0;
+
+      if( G.VOM.viewerMediaIsChanged || ((new Date().getTime()) - G.VOM.timeImgChanged < 300) ) { return; }
+      if( G.VOM.playSlideshow ) {
+        SlideshowToggle();
+      }
+      
+      TriggerCustomEvent('lightboxPreviousImage');
+      LightboxDisplay( 'previousImage', velocity);
+    };
+
+
+
+    // Display image (with animation if possible)
+    function LightboxDisplay( displayType, velocity ) {
+
+      velocity = velocity || 0;
+
+      if( G.O.debugMode && console.timeline ) { console.timeline('nanogallery2_viewer'); }
+
+      if( G.VOM.playSlideshow ) { window.clearTimeout( G.VOM.playSlideshowTimerID ); }
+      
+      var current_content_item = null;
+      var new_content_item  = null;
+
+      G.VOM.timeImgChanged = new Date().getTime();
+      G.VOM.viewerMediaIsChanged = true;
+      G.VOM.zoom.isZooming = false;
+      ResizeLightbox(true);
+
+      switch( displayType ) {
+        case '':
+            current_content_item = G.VOM.content.current;
+            new_content_item = G.VOM.content.current;
+          break;
+        case 'previousImage':
+            current_content_item = G.VOM.content.current;
+            new_content_item = G.VOM.content.previous;
+          break;
+        default:
+            current_content_item = G.VOM.content.current;
+            new_content_item = G.VOM.content.next;
+      }
+
+      // SetLocationHash( next_ng2item.albumID, next_ng2item.GetID() );
+      SetLocationHash( new_content_item.NGY2Item().albumID, new_content_item.NGY2Item().GetID() );
+
+      if( displayType == '' ) {
+        // first media -> no transition -> exit
+        return;
+      }
+      
+      // animation duration is proportional of the remaining distance
+      var vP = G.GOM.cache.viewport;
+      var t_easing = '';
+      var t_dur = 400 * (vP.w - Math.abs(G.VOM.swipePosX)) / vP.w;
+      if( velocity > 0 ) {
+        // velocity = pixels/millisecond
+         t_dur = Math.min( (vP.w - Math.abs(G.VOM.swipePosX)) / velocity, t_dur);
+         t_easing = 'linear';     // use linear to avoid a slow-down in the transition after user swipe
+      }
+      
+        
+      // animate the image transition between 2 medias
+        
+      if( G.CSStransformName == null  ) {
+        // no CSS transform support -> no animation
+        ViewerSetMediaVisibility(new_content_item, 1);
+        ViewerSetMediaVisibility(current_content_item, 1);
+        LightboxDisplayFinalize(displayType);
+      }
+      else {
+        switch( G.O.imageTransition ) {
+          case 'SWIPE':
+          case 'SWIPE2':
+            var dir = ( displayType == 'nextImage' ? - vP.w : vP.w );
+            new_content_item.$media[0].style[G.CSStransformName] = 'translate(' + (-dir) + 'px, 0px) '
+
+            if( velocity == 0 ) {
+              t_easing = G.O.imageTransition == 'swipe' ? 'easeInOutSine' : 'easeInQuart';
+            }
+            
+            ViewerSetMediaVisibility(G.VOM.content.current, 1);
+            G.VOM.content.current.$media[0].style[G.CSStransformName] = 'translate(0px, 0px)';
+            ViewerSetMediaVisibility(new_content_item, 1);
+
+            new NGTweenable().tween({
+              from:         { t: G.VOM.swipePosX  },
+              to:           { t: (displayType == 'nextImage' ? - vP.w : vP.w) },
+              attachment:   { dT: displayType, new_content_item: new_content_item, dir: dir, media_transition: new_content_item.NGY2Item().mediaTransition()},
+              // delay:        30,
+              duration:     t_dur,
+              easing:       ( t_easing ),
+              step:         function (state, att) {
+                // current displayed media
+                G.VOM.content.current.$media[0].style[G.CSStransformName] = 'translate(' + state.t + 'px, 0px)';
+
+                // new media
+                if( att.media_transition ) {
+                  // new media supports transition
+                  var sc = Math.min( Math.max( (Math.abs(state.t)) / G.VOM.window.lastWidth, .8), 1);
+                  if( G.O.imageTransition == 'SWIPE' ) { sc = 1; }
+                  att.new_content_item.$media[0].style[G.CSStransformName] = 'translate(' + (-att.dir+state.t) + 'px, 0px) scale(' + sc + ')';
+                }
+              },
+              finish:       function (state, att) {
+                G.VOM.content.current.$media[0].style[G.CSStransformName] = '';
+                ViewerSetMediaVisibility(G.VOM.content.current, 0);
+                att.new_content_item.$media[0].style[G.CSStransformName] = '';
+                LightboxDisplayFinalize(att.dT);
+              }
+            });
+            break;
+            
+          case 'SLIDEAPPEAR':
+          default:
+            var dir=(displayType == 'nextImage' ? - vP.w : vP.w);
+            var op = (Math.abs(G.VOM.swipePosX)) / G.VOM.window.lastWidth;
+            new_content_item.$media[0].style[G.CSStransformName] = '';
+            if( velocity == 0 ) {
+              t_easing ='easeInOutSine';
+            }
+            new NGTweenable().tween({
+              from:         { o: op, t: G.VOM.swipePosX },
+              to:           { o: 1,  t: (displayType == 'nextImage' ? - vP.w : vP.w) },
+              attachment:   { dT: displayType, new_content_item:new_content_item, media_transition: new_content_item.NGY2Item().mediaTransition()  },
+              delay:        30,
+              duration:     t_dur,
+              easing:       t_easing,
+              step:         function (state, att) {
+                // current media - translate
+                G.VOM.content.current.$media[0].style[G.CSStransformName]= 'translate('+state.t+'px, 0px)';
+
+                // new media - opacity
+                if( att.media_transition ) {
+                  // new media supports transition
+                  ViewerSetMediaVisibility(att.new_content_item, state.o);
+                }
+              },
+              finish:       function (state, att) {
+                G.VOM.content.current.$media[0].style[G.CSStransformName]= '';
+                LightboxDisplayFinalize(att.dT);
+              }
+            });
+            break;
+        }
+      }
+    
+    }
+  
+
+    function LightboxDisplayFinalize( displayType ) {
+
+      var newVomIdx = 0;
+      switch( displayType ) {
+        case '':
+					// first media to display in lightbox
+					newVomIdx = G.VOM.content.current.vIdx;
+          break;
+        case 'previousImage':
+					// previous media
+					newVomIdx = G.VOM.content.previous.vIdx;
+          break;
+        default:
+					// next media
+					newVomIdx = G.VOM.content.next.vIdx;
+      }
+
+			
+
+      G.VOM.content.current.vIdx = newVomIdx;
+      G.VOM.content.next.vIdx = G.VOM.IdxNext();
+      G.VOM.content.previous.vIdx = G.VOM.IdxPrevious();
+			G.VOM.gallery.Resize();
+			G.VOM.gallery.SetThumbnailActive();
+      
+      var ngy2item = G.VOM.content.current.NGY2Item();
+
+      ViewerToolbarElementContent();
+      if( G.O.debugMode && console.timeline ) { console.timelineEnd('nanogallery2_viewer'); }
+
+      var fu=G.O.fnImgDisplayed;
+      if( fu !== null ) {
+        typeof fu == 'function' ? fu(ngy2item) : window[fu](ngy2item);
+      }
+      
+      G.VOM.swipePosX = 0;
+      if( displayType != '' ) {
+        // not on first media display
+        G.VOM.content.current.$media.removeClass('imgCurrent');
+
+        var $tmp = G.VOM.content.current.$media;
+        switch( displayType ) {
+          case 'nextImage':
+            G.VOM.content.current.$media = G.VOM.content.next.$media;
+            G.VOM.content.next.$media = $tmp;
+            break;
+          case 'previousImage':
+            G.VOM.content.current.$media =  G.VOM.content.previous.$media;
+             G.VOM.content.previous.$media = $tmp;
+            break;
+        }
+      }
+      
+      G.VOM.content.current.$media.addClass('imgCurrent');
+      
+      // re-sort the media containers --> current on top
+      var $pans = G.VOM.$content.find('.nGY2ViewerMediaPan');
+      G.VOM.content.current.$media.insertAfter($pans.last());
+      
+      if( ngy2item.mediaKind == 'img' && ngy2item.imageWidth == 0 ) {
+        ViewerSetMediaVisibility(G.VOM.content.current, 0);
+      }
+      else {
+        G.VOM.content.current.$media.children().eq(0).attr('class', 'nGY2ViewerMediaLoaderHidden');    // hide preloader
+        ViewerSetMediaVisibility(G.VOM.content.current, 1);
+      }
+
+      
+      // set the new NEXT media
+      G.VOM.content.next.$media.empty();
+      var nextItem = G.VOM.content.next.NGY2Item();
+      var spreloader = '<div class="nGY2ViewerMediaLoaderDisplayed"></div>';
+      if( nextItem.mediaKind == 'img' && nextItem.imageWidth != 0 && nextItem.imageHeight != 0 ) {
+        spreloader = '<div class="nGY2ViewerMediaLoaderHidden"></div>';
+      }
+      G.VOM.content.next.$media.append( spreloader + nextItem.mediaMarkup );
+      ViewerSetMediaVisibility(G.VOM.content.next, 0);
+      ViewerSetMediaVisibility(G.VOM.content.previous, 0);
+      if( nextItem.mediaKind == 'img' ) {
+        G.VOM.ImageLoader.loadImage(VieweImgSizeRetrieved, nextItem);
+      }
+      else {
+        ViewerMediaCenterNotImg( G.VOM.content.next.$media );
+      }
+
+      // set the new PREVIOUS media
+      G.VOM.content.previous.$media.empty();
+      var previousItem = G.VOM.content.previous.NGY2Item();
+      spreloader = '<div class="nGY2ViewerMediaLoaderDisplayed"></div>';
+      if( previousItem.mediaKind == 'img' && previousItem.imageWidth != 0 && previousItem.imageHeight != 0 ) {
+        spreloader = '<div class="nGY2ViewerMediaLoaderHidden"></div>';
+      }
+      G.VOM.content.previous.$media.append( spreloader + previousItem.mediaMarkup );
+      ViewerSetMediaVisibility(G.VOM.content.previous, 0);
+      ViewerSetMediaVisibility(G.VOM.content.next, 0);
+      if( previousItem.mediaKind == 'img' ) {
+        G.VOM.ImageLoader.loadImage( VieweImgSizeRetrieved, previousItem );
+      }
+      else {
+        ViewerMediaCenterNotImg( G.VOM.content.previous.$media );
+      }
+
+        
+      // slideshow mode - wait until image is loaded to start the delay for next image
+      if( G.VOM.playSlideshow ) {
+        G.VOM.content.current.$media.children().eq(1).ngimagesLoaded().always( function( instance ) {
+          if( G.VOM.playSlideshow ) {
+            // in the meantime the user could have stopped the slideshow
+            G.VOM.playSlideshowTimerID = window.setTimeout( function(){ DisplayNextMedia(); }, G.VOM.slideshowDelay );
+          }
+        });
+      }
+      
+      // close viewer when user clicks outside of the image
+      // G.VOM.$mediaCurrent.on("click",function(e){
+      //  e.stopPropagation();
+      //  if( (new Date().getTime()) - G.VOM.timeImgChanged < 400 ) { return; }
+      //  StopPropagationPreventDefault(e);
+      //  LightboxClose(G.VOM.currItemIdx);
+      //  return false;
+      // });
+
+      ResizeLightbox();
+
+      G.VOM.viewerMediaIsChanged = false;
+      TriggerCustomEvent('lightboxImageDisplayed');
+      
+    }
+
+    
+    // Is fired as soon as the size of an image has been retrieved (the download may not be finished)
+    function VieweImgSizeRetrieved(w, h, item, n) {
+
+      item.imageWidth = w;
+      item.imageHeight = h;
+  
+      // image sized retrieved for currently displayed media
+      if( G.VOM.content.current.NGY2Item() == item ) {
+        // it is the current displayed media
+        G.VOM.content.current.$media.children().eq(0).attr('class', 'nGY2ViewerMediaLoaderHidden');    // hide preloader
+        ViewerSetMediaVisibility(G.VOM.content.current, 1);
+        G.VOM.zoom.userFactor = 1;
+      }
+      
+      if( G.VOM.content.next.NGY2Item() == item ) {   // next media
+        G.VOM.content.next.$media.children().eq(0).attr('class', 'nGY2ViewerMediaLoaderHidden');    // hide preloader
+      }
+      if( G.VOM.content.previous.NGY2Item() == item ) {   // previous media
+        G.VOM.content.previous.$media.children().eq(0).attr('class', 'nGY2ViewerMediaLoaderHidden');    // hide preloader
+      }
+      
+      ViewerMediaSetPosAndZoom();
+
+    }
+
+    // Viewer - Set the visibility of the media and it's container
+    // function ViewerSetMediaVisibility(item, $media, opacity ) {
+    function ViewerSetMediaVisibility( content_item, opacity ) {
+
+			var item = content_item.NGY2Item();
+      var $media = content_item.$media;
+      
+      if( item.mediaKind == 'img' && item.imageWidth == 0 ) {
+        // do not display image if width is unknown (--> callback will set the width when know)
+        // setting opacity to 0 is not enough -> it is mandatory to change also the visibility to hidden to avoid responds to events (click/touch)
+        // $media.children().css({ opacity: 0, visibility: 'hidden' });
+        $media.children().eq(1).css({ opacity: 0, visibility: 'hidden' });    // hide media
+        // $media.css({ opacity: 0, visibility: 'hidden' });
+        return;
+      }
+      
+      if( opacity == 0 ) {
+        // setting opacity to 0 is not enough -> it is mandatory to change also the visibility to hidden to avoid responds to events (click/touch)
+        // $media.css({ opacity: 0, visibility: 'hidden' });
+        $media.children().css({ opacity: 0, visibility: 'hidden' });    // hide media
+      }
+      else {
+        // $media.css({ opacity: opacity, visibility: 'visible' });
+        $media.children().css({ opacity: opacity, visibility: 'visible' });      // display media
+      }
+    }
+    
+    
+    // Close the internal lightbox
+    function LightboxClose( vomIdx ) {
+
+
+      if( vomIdx == undefined ) {
+        vomIdx = G.VOM.content.current.vIdx;
+      }
+
+      G.VOM.viewerMediaIsChanged = false;
+
+      if( G.VOM.viewerDisplayed ) {
+
+        // set scrollbar visible again
+        jQuery("body").removeClass("nGY2_body_scrollbar");
+        jQuery("#nGY2_body_scrollbar_style").remove();
+        
+        if( G.VOM.playSlideshow ) {
+          window.clearTimeout( G.VOM.playSlideshowTimerID );
+          G.VOM.playSlideshow = false;
+        }
+
+        // G.VOM.userEvents.removeEventListeners();
+        // G.VOM.userEvents=null;
+        G.VOM.hammertime.destroy();
+        G.VOM.hammertime = null;
+
+        if( ngscreenfull.enabled && G.VOM.viewerIsFullscreen ) {
+          G.VOM.viewerIsFullscreen = false;
+          ngscreenfull.exit();
+        }
+        
+        G.VOM.$baseCont.hide(0).off().show(0).html('').remove();
+        G.VOM.viewerDisplayed = false;
+
+        if( G.O.lightboxStandalone ) { return; }
+
+        if( G.O.thumbnailAlbumDisplayImage ) {
+          // content of album displayed directly in lightbox (no gallery display for album content)
+          if( vomIdx == null ) {
+            // lightbox closed with browser back-button
+            // the gallery is already displayed
+          }
+          else {
+            var item = G.I[G.VOM.items[vomIdx].ngy2ItemIdx];
+            var parent = NGY2Item.Get(G, item.albumID);
+            if( G.GOM.albumIdx != parent.albumID ) {
+              // display album only if not already displayed
+              DisplayAlbum('-1', parent.albumID);
+            }
+            else {
+              GalleryResize();        
+              SetLocationHash( '', '' );
+              ThumbnailHoverReInitAll();
+            }
+          }
+            // DisplayAlbum( '-', G.I[G.VOM.items[vomIdx].ngy2ItemIdx].albumID );
+        }
+        else {
+          if( vomIdx != null ) {
+            if( G.GOM.albumIdx == -1 ) {
+              // album not displayed --> display gallery
+              DisplayAlbum( '', G.I[G.VOM.items[vomIdx].ngy2ItemIdx].albumID );
+            }
+            else {
+              GalleryResize();        
+              SetLocationHash( G.I[G.VOM.items[vomIdx].ngy2ItemIdx].albumID, '' );
+              ThumbnailHoverReInitAll();
+            }
+          }
+        }
+        G.VOM.timeImgChanged = new Date().getTime();
+      }
+    }
+
+    
+    // Lightbox resized -> reposition elements
+    function ResizeLightbox( forceUpdate ) {
+      forceUpdate = typeof forceUpdate !== 'undefined' ? forceUpdate : false;
+      
+      if( G.VOM.$toolbar === null ) { return; }   // viewer build not finished
+      
+      
+      // window.requestAnimationFrame( function() {    // synchronize with screen
+      var windowsW = G.VOM.$viewer.width();
+      var windowsH = G.VOM.$viewer.height();
+      var $elt = G.VOM.content.current.$media.children().eq(1);
+      if( $elt == null || G.VOM.content.current.vIdx == -1 ) { return; }
+
+      if( !forceUpdate && G.VOM.window.lastWidth == windowsW  && G.VOM.window.lastHeight == windowsH ) { return; }
+      
+      G.VOM.window.lastWidth = windowsW;
+      G.VOM.window.lastHeight = windowsH;
+
+      var $tb = G.VOM.$toolbar.find('.toolbar');
+      // var tb_OHt = $tb.outerHeight(true);
+
+
+      var galleryHeight = 0;
+      var cBottom = 0;
+			// Height of the thumbnails gallery
+			if( G.O.viewerGallery != 'none' ) {
+        galleryHeight = G.O.viewerGalleryTHeight + 10;
+			}
+			if( G.O.viewerGallery == 'bottom' ) {
+       cBottom = galleryHeight;
+			}
+
+			
+      switch( G.O.viewerToolbar.position ) {
+        case 'top':
+        case 'topOverImage':
+          G.VOM.$content.css({height: windowsH, width: windowsW, top: 0  });
+          G.VOM.$toolbar.css({top: 0, bottom: ''});
+          break;
+        // case 'top':
+          // windowsH -= tb_OHt;
+          // G.VOM.$content.css({height: windowsH, width: windowsW, top: tb_OHt  });
+          // G.VOM.$toolbar.css({top: 0});
+          // break;
+        case 'bottom':
+        case 'bottomOverImage':
+        default:
+          windowsH -= cBottom;
+          G.VOM.$content.css({height: windowsH, width: windowsW, bottom: -cBottom, top: 0  });
+          G.VOM.$toolbar.css({bottom: galleryHeight});
+          break;
+        // case 'bottom':
+        // default:
+          // windowsH -= tb_OHt;
+          // G.VOM.$content.css({ width: windowsW, top: 0, bottom: tb_OHt });
+          // G.VOM.$toolbar.css({bottom: galleryHeight});
+          // break;
+      }
+			
+			
+      if( !G.VOM.viewerMediaIsChanged && G.VOM.zoom.isZooming ) {
+        ViewerMediaSetPosAndZoom();
+      }
+      else {
+				if( !G.VOM.zoom.isZooming && ( G.VOM.zoom.userFactor != 0 || G.VOM.panPosX != 0 || G.VOM.panPosY != 0 || G.VOM.zoom.posX != 0 || G.VOM.zoom.posY != 0 )) {
+					// animate image zoom factor and position back to initial values
+					G.VOM.zoom.isZooming= true;		// activate zooming temporarily
+          new NGTweenable().tween({
+            from:           { userFactor: G.VOM.zoom.userFactor, panPosX: G.VOM.panPosX, panPosY: G.VOM.panPosY, zoomPosX: G.VOM.zoom.posX, zoomPosY: G.VOM.zoom.posY },
+            to:           { userFactor: 1, panPosX: 0, panPosY: 0, zoomPosX: 0, zoomPosY: 0 },
+            easing:       'easeInOutSine',
+            delay:        0,
+            duration:     150,
+            step:         function (state) {
+							G.VOM.zoom.userFactor = state.userFactor;
+							G.VOM.panPosX = state.panPosX;
+							G.VOM.panPosY = state.panPosY;
+							G.VOM.zoom.posX = state.zoomPosX;
+							G.VOM.zoom.posY = state.zoomPosY;
+							ViewerMediaSetPosAndZoom();
+            },
+            finish:       function (state) {
+							G.VOM.zoom.isZooming=false;
+            }
+          });
+			
+				}
+				else {
+					G.VOM.zoom.userFactor = 1;
+					G.VOM.zoom.isZooming = false;
+					G.VOM.panPosX = 0;
+					G.VOM.panPosY = 0;
+					G.VOM.zoom.posX = 0;
+					G.VOM.zoom.posY = 0;
+					ViewerMediaSetPosAndZoom();
+				}
+      }
+    }
+
+		// Retrieve the first parent element which is scrollable
+		// source: ncubica - https://stackoverflow.com/questions/35939886/find-first-scrollable-parent
+		// returns null if nothing found
+		function getScrollableParent (node) {
+			const regex = /(auto|scroll)/;
+			const parents = (_node, ps) => {
+				if (_node.parentNode === null) { return ps; }
+				return parents(_node.parentNode, ps.concat([_node]));
+			};
+
+			const style = (_node, prop) => getComputedStyle(_node, null).getPropertyValue(prop);
+			const overflow = _node => style(_node, 'overflow') + style(_node, 'overflow-y') + style(_node, 'overflow-x');
+			const scroll = _node => regex.test(overflow(_node));
+
+			const scrollParent = (_node) => {
+				if (!(_node instanceof HTMLElement || _node instanceof SVGElement)) {
+					return undefined;
+				}
+
+				const ps = parents(_node.parentNode, []);
+
+				for (let i = 0; i < ps.length; i += 1) {
+					if( ps[i] === document.body ) {
+						return null;
+					}
+					if (scroll(ps[i])) {
+						return ps[i];
+					}
+				}
+
+				return document.scrollingElement || document.documentElement;
+			};
+
+			return scrollParent(node);
+		};
+
+
+
+    /** @function BuildSkeleton */
+    /** Build the gallery structure **/
+    function BuildSkeleton() {
+
+		
+      // store markup if defined
+      // var $elements = G.$E.base.children('a');
+      var $elements = G.$E.base.children();
+      if( $elements.length > 0 ) {
+        G.O.$markup = $elements;
+      }
+    
+      if( !G.O.lightboxStandalone ) {
+        G.$E.base.text('');
+        G.$E.base.addClass('ngy2_container');
+      
+        // RTL or LTR
+        var sRTL='';
+        if( G.O.RTL ) {
+          sRTL = 'style="text-align:right;direction:rtl;"';
+        }
+      
+        // theme
+        G.$E.base.addClass(G.O.theme)
+        // gallery color scheme
+        SetGalleryTheme();
+
+        // Hide icons (thumbnails and breadcrumb)
+        if( G.O.thumbnailLabel.get('hideIcons') ) {
+          G.O.icons.thumbnailAlbum = '';
+          G.O.icons.thumbnailImage = '';
+        }
+
+        // Navigation bar
+        var styleNavigation="";
+        if( G.O.navigationFontSize != undefined && G.O.navigationFontSize != '' ) {
+          styleNavigation=' style="font-size:'+G.O.navigationFontSize+';"';
+        }      
+        G.$E.conNavigationBar = jQuery('<div class="nGY2Navigationbar" '+styleNavigation+'></div>').appendTo(G.$E.base);
+
+        // pre-loader
+        G.$E.conLoadingB = jQuery('<div class="nanoGalleryLBarOff"><div></div><div></div><div></div><div></div><div></div></div>').appendTo(G.$E.base);
+
+        // gallery
+        G.$E.conTnParent = jQuery('<div class="nGY2Gallery"></div>').appendTo( G.$E.base );
+        G.$E.conTn = jQuery('<div class="nGY2GallerySub"></div>').appendTo( G.$E.conTnParent );
+
+        // configure gallery
+        switch( G.O.thumbnailAlignment ) {
+          case 'left':
+            G.$E.conTnParent.css({'text-align':'left'});
+            // G.$E.conNavBCon.css({'margin-left':0 });
+            break;
+          case 'right':
+            G.$E.conTnParent.css({'text-align':'right'});
+            // G.$E.conNavBCon.css({ 'margin-right':0});
+            break;
+        }
+        
+        // apply galleryBuildInit2 css settings to the gallery
+        if( G.O.galleryBuildInit2 !== undefined ) {
+        var t1=G.O.galleryBuildInit2.split('|');
+          for( var i=0; i<t1.length; i++ ) {
+            var o1=t1[i].split('_');
+            if( o1.length == 2 ) {
+              G.$E.conTn.css(o1[0], o1[1]);
+            }
+          }
+        }
+        
+        // configure gallery depending on some thumbnail hover effects
+        var effects=G.tn.hoverEffects.std.concat(G.tn.hoverEffects.level1);
+        for( var j=0; j<effects.length; j++) {
+          switch( effects[j].type ) {
+            case 'scale':
+            case 'rotateZ':
+            case 'rotateX':
+            case 'rotateY':
+            case 'translateX':
+            case 'translateY':
+              // handle some special cases
+              if( effects[j].element == '.nGY2GThumbnail' ) {
+                // allow thumbnail upscale over the gallery's aera
+                G.$E.base.css('overflow', 'visible');
+                G.$E.base.find('.nGY2GallerySub').css('overflow', 'visible');
+                G.$E.conTnParent.css('overflow', 'visible');
+              }
+              break;
+          }
+        }
+        
+        // Gallery bottom container
+        G.$E.conTnBottom = jQuery('<div class="nGY2GalleryBottom" '+styleNavigation+'></div>').appendTo( G.$E.conTnParent );
+        
+        // portable edition
+        if( G.O.portable ) {
+          // http://www.picresize.com/
+          // http://base64encode.net/base64-image-encoder
+          // var logo='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB8AAAAWCAYAAAA4oUfxAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QA/wD/AP+gvaeTAAAAB3RJTUUH4QMPBwY6mxZgsAAABTFJREFUSMe1ll9oVGcaxn/fd86ZSWbSkEBMiWNdTTfRxiVbXFiU1bjKGqNexlURKys0tHqXpQZ64Sq4FxKqFy4qFSm9kA1FHNhFISgJqFCd6lL/YC7M3jhrJv5JmGSSMzPnzDnfuxdpZtP4b1vaF154P3gPD+/zPC/nVSKiAQOsBj7O5XK/nZiYeEtELH6iUEqFNTU1U9XV1d8AnwNfA1qJCMCfHz169NcjR45UXL16VWWzWQnD0PxU4JZl6draWtXW1iYHDx4sLlmy5C/AZwRB0JVOpyWRSHhACMjPmOHChQuL6XRagiDoUiIyumvXrpq+vr6obduqs7OTjRvbsbSFUgqUgKjyFG5+mlKpVH6LCMYYRAQRQSmF1hqtNd+xijGGVCpFMpkkCALZuXOn19fXN6Gmp6dNc3NzMDo66nR2dnL+/Hm+Ov933PwUAPHKagqei4gBFNs7dxGPx38U/du2bSOZTNLQ0FB6+PChbWez2WI+n3dEhI3tf+Det0N8de0Imz9YQWHa48u/3afjgxbqEpUM/es/uF8W+fijffi+TywWQ0S4fv06t2/fJpfLsXjxYtauXUtTUxNBECAihGFIJBJh1apVXLhwgXw+r7LZbNGeYU7MLD1BEPCLxkWs+HUT+SmPJY0TvPerd6l/J05YcLCGHWzbxrZtHjx4wP79+7l27dr3Jqyurqarq4ujR49i2zYAWmvCMJyVygCiZ7dh9kOtNb5XopD3KBQ8fL9EseBRyHsUCz6zS3Dnzh3WrVtXBq6oqGDBggUA5HI5jh07xo4dOzDmf0ujVBlGAWjmhTGC41hEow6RiI3j2DgRh0jUxonYWJaFGGHPnj2Mj49jWRYHDhzg7t27DA0NMTAwwOrVqwFIJpOcOHECx3Fe6oEXwG3bYux5ltHHz3mSGePpk+c8yczUI+knVFVVcePmDe7fvw9AT08Pvb29NDc3U1dXx4YNG7h8+TItLS1orTl58iT5fL68Ga8En55yWb6iifff/iPD/0iQGfglG3/zJ6a+beHf/3yH6Mjv+P269Vy5cgWlFDU1NXR3dxOGYdlcnudRVVXFvn37MMaQTqcZHh5+Kbg99zHjSodPuj997cqMjY0hItTW1hKPx9FalzW1LIswDFm0aBEAQRDguu6bJ581hOd5GBNiTEgYhuXa8z1EhIaGBgAymQzpdBqlFKVSiTCc6bcsi5s3bwJQWVlJfX39fMO9XHMAy7LQeibn1o7toJSio6MDAN/36e7uxvd9IpEIlmURjUZJpVKcOXMGpRStra0sXbr0peDfo30+LS+4U2uMMaxcuZLdu3dz7tw5+vv7aWtrY+/evdTX13Pr1i1OnTrF5OQkAIcPH8ayrNeCvx51njTGGE6fPk0mk2FwcJBUKkUqlXqh9/jx42zatKnMzJzhBEArpZT+zjGWZSEiBEHwypzVtbKykosXL3Lo0CEaGxvLpovFYqxZs4ZLly6VJQnDEBEpM6C11kopheu6JpFI+Fpr2bJli/zYGBkZkeHhYZmcnHxlz9atW0VrLYlEwndd19ixWOzx5s2b3z579qzp7+/X7e3ttLa2Yox5QaP5MfenEY1G0VoTBAHFYhFjTJlJrTX37t1jYGAAY4zp6OiQWCz2mCAItj979kyWL1/uAwE/7zERLFu2zH/69KkEQbB99ozaOz4+fqy3t7d2cHAwdF1XKaXe6P7/16AiQjwel/Xr1+uenp6Jurq6T4Av1JwD8j3gQ2BVsVh8S72J8x8QIiIVFRVTQAo4CwwB+r93qCLI9wKZ8AAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNy0wMy0xNVQwNzowNjo1OC0wNDowMBNQsyUAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTctMDMtMTVUMDc6MDY6NTgtMDQ6MDBiDQuZAAAAAElFTkSuQmCC';
+          var logo = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAYCAYAAACbU/80AAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QA/wD/AP+gvaeTAAAAB3RJTUUH4QgDBCAWVVC/hwAABRxJREFUSMetll9oVFcexz/nnDvJRBmSzWTrmD9uNGZsHta0/qFIFQTxRcnCBgTFNlX0YR8W+1AK9lGwCBJYgn0KKr5136S4gpUQTR4caJRslcxYWV3iaphQapJJppO5957z60Mmk4mN1q75wg/OPefc+/v9vt/fueenKEFEqICqsNWAVNiCA7XwaS0iZeejo6OIiCltdIBdJXMLOYp5/PjxsoTVS5nr0mYDJIE/lObeBhaYAn4oJbboAwBvBedHJicnPx8YGGh/8eJF1dvKoJSShoYGf//+/Zl4PP4l8M2yIEoSLErx6c2bN6W1tXVRglWzLVu2SCqVEhE5LiI457SIoEREW2udMaZtcnLy+2QyWZ3L5XRHR4f+4MNdoBUahUJhcWilmZ/NE4ZhOQHn3LIi1lqjtS6vjY6O8uTJE9vc3MyDBw+mYrHYn0Uk63me8gCtlHLA7uHh4bW5XC7oePddPTQ8xHffDjM/PYe3thqMws35iAcHPj5ENBp9Yxmy2Sw7d+40z549C+7du9ewb9++D6y13wDaK+kE0DAzMyNKKbXtvfd5EfzM+Ef/4C+8x23+wzPm+IhtfMf3/Ksuyl+7u9FaY63l+vXrpFIpCoUCmzdvpquri9bWVoIgQClFIpFg48aNPH/+XE9NTQkQLTGmvEXKRERprZWIEIYhQRjQbN6hmUb+tCaPNnM055v40f3If7XBGMPT8af0fNLD0NDQsozPnDlDb28vx44dIwxDRARrLSKCKmUbiUQQkWWnoLJ20UpjFYAjVA6rBJTFV5ZIJIIfBBw4eICxsTHq6uo4dOgQ8XicgYEB7t69y/Hjx4nH43R1dVHB8q+w4hlXSmGd5edwmjCco5DLkZ+aJvTnyIdTrFmzhn9+/TVjY2M0NTVx+/Zt+vv7OXfuHKlUip6eHgBOnz6N7/vlYl0JKzIw78/T+sdGbn6yjf5ZS2HtJgIP+mcC5kySI1uSXPjqAlprTp06RWdnJ8ViEaUUVVVVnD9/nqtXr5LJZHj48CFbt279fQEEYUisZi2fXel9bWU750gmkwRBgNYaz/Ow1lJfX088Hmd2dpZcLvdaBl4pgQChH4B1iHU4a8E6Qj9ARGhpaUFrzeDgIJFIBGMM1lqMMWQyGSYmJohEIqxfv/7314CIoADtGTAaZTTaLI2VUhw+fBjnHBcvXuTy5cs45/A8j3Q6zcmTJ/F9n71799LW1rbgSOs3D+B1lBljcM7R3d3N0aNHKRQKnDhxgs7OTnbt2sX27dsZGRkhHo/T19e3+Kt/fQ1YawFwzolSCs/zUEqVtX1VcJcuXSKRSNDf3086nS6v79mzh76+Pjo6OigWi1RXV2OMWZC29PL8/PxSAL7vE41Gf4rFYkpEePToEb7vU1VVxW+ht7eXs2fPcv/+fQqFAps2baKlpaW8Xl1dTS6XY3x8HBFxtbW1BiiW4hAlInp8fNxt2LChPZvN/ru9vT2Sz+e93bt3qx07diwrzJWYcM5RU1NDNBots5bP53HOlS+kO3fuMDIy4hKJhKTT6ena2tqtxWJxoqamRr98HX9x7do1qaurExYaiXCVzK5bt04GBwdFRP728nVcWZAO+Hsmk/nsxo0bTTMzM5FXHZ83hYhQX1/vHzx48H9tbW1ngSsVvpYCmJ2dJRaLKRbapjpgOxB7K+9LmAbuAnOAnpiYcI2NjUsRLlo2myUMQ1M5t5rmnDO3bt1aNlfmd4W2XL/0/H8pUDF2rNCW/wLRuCkxx8V6wgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNy0wOC0wM1QwNDozMjoyMi0wNDowMO7mdkwAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTctMDgtMDNUMDQ6MzI6MjItMDQ6MDCfu87wAAAAAElFTkSuQmCC';
+          var st = "font-weight:bold !important;color: #FF0075 !important;font-size: 14px !important;text-transform: lowercase !important;cursor:pointer !important;text-align: center !important;Text-Shadow: #000000 1px 0px 0px, #000000 1px 1px 0px, #000000 1px -1px 0px, #000000 -1px 1px 0px, #000000 -1px 0px 0px, #000000 -1px -1px 0px, #000000 0px 1px 0px, #000000 0px -1px 0px !important;";
+          G.$E.ngy2i=jQuery('<div class="nGY2PortInfo"><a href="http://nano.gallery" target="_blank" title="nanogallery2 | easy photo gallery for your website" style="' + st + '"><img src="' + logo + '" style="height:32px !important;width:initial !important;box-shadow: none !important;vertical-align: middle !important;"/> &nbsp; nanogallery2</a></div>').appendTo(G.$E.base);
+          
+          G.$E.ngy2i.find('a').on({
+            mouseenter: function () {
+              jQuery(this).attr('style', st);
+            },
+            mouseleave: function () {
+              jQuery(this).attr('style', st);
+            }
+          });
+        }
+      }
+      
+      // Error console
+      G.$E.conConsole = jQuery('<div class="nGY2ConsoleParent"></div>').appendTo(G.$E.base);
+
+      // i18n translations
+      i18n();
+
+      if( !G.O.lightboxStandalone ) {
+        // cache some thumbnails data (sizes, styles...)
+        ThumbnailDefCaches();
+
+        // do special settings depending for some options
+        // thumbnail display transition
+        switch( G.tn.opt.Get('displayTransition') ) {
+          case 'SCALEDOWN':
+          case 'RANDOMSCALE':
+          default:
+            G.$E.base.css('overflow', 'visible');
+            G.$E.conTnParent.css('overflow', 'visible');
+            G.$E.conTn.css('overflow', 'visible');
+            break;
+        }
+      }
+      
+    }
+    
+    function TriggerCustomEvent ( eventName ) {
+      // G.$E.base.trigger('pageChanged.nanogallery2', new Event('pageChanged.nanogallery2'));
+      var eN = eventName + '.nanogallery2';
+      var event=null;
+      try {
+          event = new Event( eN );
+        } catch(e) {
+          event = document.createEvent('Event');
+          event.initEvent(eN, false, false);
+        }
+        G.$E.base.trigger(eN, event);
+    }
+    
+    
+    /** @function SetGlobalEvents */
+    function SetGlobalEvents() {
+      // GLOBAL EVENT MANAGEMENT
+
+      if( !G.O.lightboxStandalone ) {
+        G.$E.conTnParent.on({
+          mouseenter: GalleryMouseEnter,
+          mouseleave: GalleryMouseLeave
+        }, ".nGY2GThumbnail");    //pass the element as an argument to .on
+      
+        // G.GOM.hammertime = new NGHammer(G.$E.conTn[0], { touchAction: 'none' });
+        G.GOM.hammertime = new NGHammer( G.$E.conTn[0] );
+        // G.GOM.hammertime.domEvents = true;
+      
+        
+        // PAN on gallery (pagination)
+        G.GOM.hammertime.on('pan', function(ev) {
+          if( !G.VOM.viewerDisplayed ) {
+            if( G.O.paginationSwipe && G.layout.support.rows && G.galleryDisplayMode.Get() == 'PAGINATION' ) {
+              if( Math.abs(ev.deltaY) > G.GOM.panThreshold ) {
+                G.GOM.panYOnly = true;
+              }
+              if( !G.GOM.panYOnly ) {
+                G.$E.conTn.css( G.CSStransformName , 'translate('+(ev.deltaX)+'px,0px)');
+              }
+            }
+          }
+        });
+        G.GOM.hammertime.on('panend', function(ev) {
+          if( !G.VOM.viewerDisplayed ) {
+            if( G.O.paginationSwipe && G.layout.support.rows && G.galleryDisplayMode.Get() == 'PAGINATION' ) {
+              if( !G.GOM.panYOnly ) {
+                if( ev.deltaX > 50 ) {
+                  paginationPreviousPage();
+                  return;
+                }
+                if(  ev.deltaX < -50 ) {
+                  paginationNextPage();
+                  return;
+                }
+              }
+              G.GOM.panYOnly = false;
+              G.$E.conTn.css( G.CSStransformName , 'translate(0px,0px)');
+              // pX=0;
+            }
+          }
+        });
+        // tap on gallery
+        G.GOM.hammertime.on('tap', function(ev) {
+          if( !G.VOM.viewerDisplayed ) {
+            ev.srcEvent.stopPropagation();
+            ev.srcEvent.preventDefault();  // cancel  mouseenter event
+
+            if( ev.pointerType == 'mouse') {
+              if( GalleryClicked(ev.srcEvent) == 'exit' ) { return; }
+            }
+            else {
+              var r = GalleryEventRetrieveElementl(ev.srcEvent, false);
+              if( r.GOMidx == -1 ) { return; }
+              if( r.action != 'NONE' && r.action != 'OPEN' ) {
+                // toolbar touched --> execute action
+                GalleryClicked(ev.srcEvent);
+                return;
+              }
+              
+              if( G.GOM.slider.hostIdx == r.GOMidx ) {
+                // touch on thumbnail slider -> open immediately
+                ThumbnailHoverOutAll();
+                ThumbnailOpen(G.GOM.items[G.GOM.slider.currentIdx].thumbnailIdx, true);
+                return;
+              }
+
+              if( (G.GOM.curNavLevel == 'l1' && G.O.touchAnimationL1 == false) ||  (G.GOM.curNavLevel == 'lN' && G.O.touchAnimation == false) ) {
+                // open on single touch (no hover animation)
+                ThumbnailOpen(G.GOM.items[r.GOMidx].thumbnailIdx, true);
+                return;
+              }
+              
+              if( G.O.touchAutoOpenDelay > 0 ) {
+                // open on single touch after end of hover animation (=defined delay)
+                ThumbnailHoverOutAll();
+                ThumbnailHover( r.GOMidx );
+                window.clearInterval( G.touchAutoOpenDelayTimerID );
+                G.touchAutoOpenDelayTimerID = window.setInterval(function(){
+                  window.clearInterval( G.touchAutoOpenDelayTimerID );
+                  ThumbnailOpen( G.GOM.items[r.GOMidx].thumbnailIdx, true );
+                }, G.O.touchAutoOpenDelay );
+              }
+              else {
+                // two touch scenario
+                if( !G.I[G.GOM.items[r.GOMidx].thumbnailIdx].hovered ) {
+                  ThumbnailHoverOutAll();
+                  ThumbnailHover(r.GOMidx);
+                }
+                else {
+                  // second touch
+                  ThumbnailOpen(G.GOM.items[r.GOMidx].thumbnailIdx, true);
+                }
+              }
+            }
+          }
+        });
+      
+        // browser location hash management
+        if( G.O.locationHash ) {
+          // jQuery(window).bind( 'hashchange', function() {
+            // ProcessLocationHash();
+          // });
+          jQuery(window).on('hashchange.nanogallery2.' + G.baseEltID, function() {ProcessLocationHash();} );
+        }
+      }
+      
+      // Page resize / orientation change
+      jQuery(window).on('resize.nanogallery2.' + G.baseEltID + ' orientationChange.nanogallery2.' + G.baseEltID, debounce( ResizeWindowEvent, G.O.eventsDebounceDelay, false) );
+      
+      // Event page scrolled
+      jQuery(window).on('scroll.nanogallery2.' + G.baseEltID, debounce( OnScrollEvent, G.O.eventsDebounceDelay, false) );
+
+      if( !G.O.lightboxStandalone ) {
+        // Scroll event on first scrollable parent element
+        G.$E.scrollableParent = getScrollableParent( G.$E.base[0] );
+        var sp = getScrollableParent( G.$E.base[0] );
+        if( sp !== null ) {
+          G.$E.scrollableParent = jQuery( sp );
+          G.$E.scrollableParent.on('scroll.nanogallery2.' + G.baseEltID, debounce( OnScrollEvent, G.O.eventsDebounceDelay, false) );
+        }
+      }
+			
+      // lightbox: hide tools/gallery after defined delay
+      G.VOM.toolsHide = debounce( ViewerToolsHide, G.O.viewerHideToolsDelay, false );
+      
+      // Keyboard management
+      jQuery(document).keyup(function(e) {
+        if( G.popup.isDisplayed ) {
+          switch( e.keyCode) {
+            case 27:    // Esc key
+              G.popup.close();
+              break;
+          }
+        }
+        else {
+          if( G.VOM.viewerDisplayed ) {
+            ViewerToolsUnHide();
+            switch( e.keyCode) {
+              case 27:    // Escape key
+              case 40:    // DOWN
+              case 38:    // UP
+                LightboxClose();
+                break;
+              case 32:    // SPACE
+              case 13:    // ENTER
+                SlideshowToggle();
+                break;
+              case 39:    // RIGHT
+              case 33:    // PAGE UP
+                DisplayNextMedia();
+                break;
+              case 37:    // LEFT
+              case 34:    // PAGE DOWN
+                DisplayPreviousMedia();
+                break;
+              case 35:    // END
+              case 36:    // BEGIN
+            }
+          }
+        }
+      });
+      
+      // mouse wheel to zoom in/out the image displayed in the internal lightbox
+      jQuery(window).bind('mousewheel wheel', function(e){
+        if( G.VOM.viewerDisplayed ) {
+          var deltaY = 0;
+          e.preventDefault();
+
+          if( ViewerZoomStart() ) {
+            if (e.originalEvent.deltaY) { // FireFox 17+ (IE9+, Chrome 31+?)
+              deltaY = e.originalEvent.deltaY;
+            } else if (e.originalEvent.wheelDelta) {
+              deltaY = -e.originalEvent.wheelDelta;
+            }
+            ViewerZoomIn( deltaY <= 0 ? true : false );
+          }
+        }
+      });
+      
+      // mouse move -> unhide lightbox toolbars
+      jQuery(window).bind('mousemove', function(e){
+        if( G.VOM.viewerDisplayed ) {
+					if( G.VOM.toolbarsDisplayed == false ) {
+						G.VOM.singletapTime = new Date().getTime();		// to avoid conflict with SINGLETAP event
+						debounce( ViewerToolsUnHide, 100, false )();
+					}
+        }
+      });
+      
+      // fullscreen mode on/off --> internal lightbox
+      if( ngscreenfull.enabled ) {
+        // ngscreenfull.onchange(() => {
+        ngscreenfull.onchange( function() {
+          if( G.VOM.viewerDisplayed ) {
+            if( ngscreenfull.isFullscreen ) {
+              G.VOM.viewerIsFullscreen=true;
+              G.VOM.$viewer.find('.fullscreenButton').html(G.O.icons.viewerFullscreenOff);
+            }
+            else {
+              G.VOM.viewerIsFullscreen=false;
+              G.VOM.$viewer.find('.fullscreenButton').html(G.O.icons.viewerFullscreenOn);
+            }
+          }
+        });
+      }
+
+    }
+    
+		
+    //----- Manage browser location hash (deep linking and browser back/forward)
+    function ProcessLocationHash() {
+
+      // standard use case -> location hash processing
+      if( !G.O.locationHash ) { return false; }
+
+      var curGal = '#nanogallery/' + G.baseEltID + '/',
+      newLocationHash = location.hash;
+      if( G.O.debugMode ) {
+        console.log('------------------------ PROCESS LOCATION HASH');
+        console.log('newLocationHash1: ' +newLocationHash);
+        console.log('G.locationHashLastUsed: ' +G.locationHashLastUsed);
+      }
+      
+      if( newLocationHash == '' ) {
+        // if( G.GOM.lastDisplayedIdx != -1 ) {
+        if( G.locationHashLastUsed !== '' ) {
+          // back button and no hash --> display first album
+          if( G.O.debugMode ) { console.log('display root album'  ); }
+          G.locationHashLastUsed = '';
+          if( G.O.debugMode ) { console.log('new3 G.locationHashLastUsed: ' + G.locationHashLastUsed); }
+          DisplayAlbum('', '0');
+          return true;
+        }
+      }
+
+      if( newLocationHash == G.locationHashLastUsed ) { return; }
+      
+      if( newLocationHash.indexOf(curGal) == 0 ) {
+        // item IDs detected
+        var IDs=parseIDs( newLocationHash.substring(curGal.length) );
+        if( IDs.imageID != '0' ) {
+          if( G.O.debugMode ) { console.log('display image: ' + IDs.albumID +'-'+ IDs.imageID ); }
+          DisplayPhoto( IDs.imageID, IDs.albumID );
+          return true;
+        }
+        else {
+          if( G.O.debugMode ) { console.log('display album: ' + IDs.albumID  ); }
+          DisplayAlbum( '-1', IDs.albumID );
+          return true;
+        }
+      }    
+      
+      return false;
+    }
+
+    //---- Set a new browser location hash
+    function SetLocationHash(albumID, imageID ) {
+      if( !G.O.locationHash || G.O.lightboxStandalone ) { return false; }
+
+      if( G.O.debugMode ) {
+        console.log('------------------------ SET LOCATION HASH');
+      }
+      
+      if( imageID == '' && (albumID == '-1' || albumID == '0' || G.O.album == albumID ) ) {
+        // root album level --> do not set top.location.hash if not already set
+        if( location.hash != '' ) {
+          // try to clear the hash if set
+          if ("pushState" in history) {
+            history.pushState("", document.title, window.location.pathname + window.location.search);
+          }
+          else {
+            location.hash='';
+          }
+        }
+        G.locationHashLastUsed='';
+        if( G.O.debugMode ) { console.log('new2 G.locationHashLastUsed: '+G.locationHashLastUsed); }
+        return;
+      }
+      
+      var newLocationHash='#'+'nanogallery/'+G.baseEltID+'/'+ albumID;
+      if( imageID != '' ) {
+        newLocationHash+='/'+imageID;
+      }
+
+      var lH=location.hash;
+      if( G.O.debugMode ) {
+        console.log('newLocationHash2: '+newLocationHash);
+        console.log('location.hash: '+lH);
+      }
+
+      G.locationHashLastUsed=newLocationHash;
+      if( G.O.debugMode ) { console.log('new G.locationHashLastUsed: '+G.locationHashLastUsed); }
+      
+      if(  lH == '' || lH != newLocationHash ) {
+        // G.locationHashLastUsed='#'+newLocationHash;
+        try {
+          top.location.hash=newLocationHash;
+        }
+        catch(e) {
+          // location hash is not supported by current browser --> disable the option
+          G.O.locationHash=false;
+        }
+      }
+    }
+    
+    
+		// WINDOW RESIZE EVENT
+    function ResizeWindowEvent() {
+      CacheViewport();
+
+			var l = G.GOM.curNavLevel;
+			var w = G.GOM.curWidth;
+			
+      if( G.VOM.viewerDisplayed ) {
+				// lightbox
+        ResizeLightbox();
+        G.VOM.gallery.Resize();
+      }
+      else {
+				// gallery
+        if( G.galleryResizeEventEnabled ) {
+          var nw = RetrieveCurWidth();
+
+					if( G.GOM.albumIdx != -1 ) {
+
+						// check if the gallery needs to be rendered again because the width changed
+						
+            var s = G.tn.settings;
+						if( G.layout.engine == "MOSAIC") {
+							// Mosaic layout
+							if( JSON.stringify(s.mosaic[l][w]) !== JSON.stringify(s.mosaic[l][nw]) ) {
+								// mosaic definition changed
+								G.GOM.curWidth = nw;
+								G.GOM.pagination.currentPage = 0;
+								GalleryRender( G.GOM.albumIdx );
+							}
+						}
+						else {
+							// other layouts
+							if( s.height[l][w] != s.height[l][nw] || s.width[l][w] != s.width[l][nw] || s.gutterHeight[l][w] != s.gutterHeight[l][nw]  || s.gutterWidth[l][w] != s.gutterWidth[l][nw]  ) {
+								// thumbnail size / gutter size changed --> render the gallery with the new values
+								G.GOM.curWidth = nw;
+								//G.layout.SetEngine();
+								G.GOM.pagination.currentPage = 0;
+								GalleryRender( G.GOM.albumIdx );
+							}
+						}
+						return;
+          }
+          else {
+            GalleryResize();
+          }
+        }
+      }
+    }
+    
+     
+
+		// SCROLL EVENT -> on WINDOW or SCROLLABLE PARENT CONTAINER
+    function OnScrollEvent() {
+			if( !G.VOM.viewerDisplayed ) {
+        GalleryResizeOnScrollEvent();
+      }
+    }
+		
+    // the gallery may currently be refreshed, so ensure that at the end of the refresh, the gallery is refreshed again because the page may have been scrolled in the meantime
+    function GalleryResizeOnScrollEvent() {
+      if( G.galleryResizeEventEnabled == false) {
+        window.setTimeout(GalleryResizeOnScrollEvent, 10);  // check again in 10ms
+      } else {
+        GalleryResize();
+      }
+    }
+
+
+    
+    // I18N : define text translations
+    function i18n() {
+
+      // browser language
+      G.i18nLang = (navigator.language || navigator.userLanguage).toUpperCase();
+      if( G.i18nLang === 'UNDEFINED') { G.i18nLang=''; }
+
+      var llang=-('_'+G.i18nLang).length;
+      
+      if( toType(G.O.i18n) == 'object' ){
+      
+        for( var key in G.O.i18n ) {
+          //var value = G.O.i18n[key];
+          var s=key.substr(llang);
+          if( s == ('_'+G.i18nLang) ) {
+            G.i18nTranslations[key.substr(0,key.length-s.length)]=G.O.i18n[key];
+          } 
+          else {
+            G.i18nTranslations[key]=G.O.i18n[key];
+          }
+        }
+      }
+    }
+
+    function GetI18nItem( item, property ) {
+      var s='';
+      if( G.i18nLang != '' ) {
+        if( item[property+'_'+G.i18nLang] !== undefined && item[property+'_'+G.i18nLang].length>0 ) {
+          s=item[property+'_'+G.i18nLang];
+          return s;
+        }
+      }
+      s=item[property];
+      return s;
+    }
+
+    
+    function RetrieveCurWidth() {
+      var vpW = G.GOM.cache.viewport.w;
+      
+      if( G.O.breakpointSizeSM > 0 && vpW < G.O.breakpointSizeSM) { return 'xs'; }
+      if( G.O.breakpointSizeME > 0 && vpW < G.O.breakpointSizeME) { return 'sm'; }
+      if( G.O.breakpointSizeLA > 0 && vpW < G.O.breakpointSizeLA) { return 'me'; }
+      if( G.O.breakpointSizeXL > 0 && vpW < G.O.breakpointSizeXL) { return 'la'; }
+      
+      return 'xl';
+    }
+
+    
+    /** @function browserNotification */
+    function browserNotification() {
+      var m = 'Your browser version is not supported anymore. The image gallery cannot be displayed. <br><br>Please update to a more recent one. Download:<br>';
+      m    += '&nbsp;&nbsp;&nbsp; <a href="http://www.google.com/chrome/?hl=en-US)">Chrome</a><br>';
+      m    += '&nbsp;&nbsp;&nbsp; <a href="http://www.mozilla.com/firefox/)">Firefox</a><br>';
+      m    += '&nbsp;&nbsp;&nbsp; <a href="http://www.microsoft.com/windows/internet-explorer/default.aspx">Internet Explorer</a><br>';
+      m    += '&nbsp;&nbsp;&nbsp; <a href="http://www.apple.com/safari/download/">Safari</a>';
+      NanoAlert(G,  m, false);
+    }
+
+    // Original author : John Hrvatin, Lead Program Manager, Internet Explorer - http://blogs.msdn.com/b/ie/archive/2011/10/28/a-best-practice-for-programming-with-vendor-prefixes.aspx
+    function FirstSupportedPropertyName(prefixedPropertyNames) {
+      var tempDiv = document.createElement("div");
+      for (var i = 0; i < prefixedPropertyNames.length; ++i) {
+        if (typeof tempDiv.style[prefixedPropertyNames[i]] != 'undefined')
+          return prefixedPropertyNames[i];
+      }
+      return null;
+    }
+
+    
+    
+    
+  }
+  
+
+
+//##########################################################################################################################
+//## imagesLoaded ##########################################################################################################
+//##########################################################################################################################
+
+// external module EMBEDED in nanogallery
+// NGY BUILD:
+// replace "imagesLoaded" with "ngimagesLoaded"
+// replace "ImagesLoaded" with "ngImagesLoaded"
+// replace "EvEmitter" with "ngEvEmitter"
+// replace "var $ = window.jQuery" with "var $ = jQuery;"
+// 2x (global.ngEvEmitter and window.ngimagesLoaded = f...)ignore package manager and set browser global
+
+
+
+/*!
+ * imagesLoaded PACKAGED v4.1.1
+ * JavaScript is all like "You images are done yet or what?"
+ * MIT License
+ */
+
+/**
+ * EvEmitter v1.0.3
+ * Lil' event emitter
+ * MIT License
+ */
+
+ 
+/* jshint unused: true, undef: true, strict: true */
+
+( function( global, factory ) {
+  // universal module definition
+  /* jshint strict: false */ /* globals define, module, window */
+//  if ( typeof define == 'function' && define.amd ) {
+    // AMD - RequireJS
+//    define( 'ev-emitter/ev-emitter',factory );
+//  } else if ( typeof module == 'object' && module.exports ) {
+    // CommonJS - Browserify, Webpack
+//    module.exports = factory();
+//  } else {
+    // Browser globals
+    global.ngEvEmitter = factory();
+//  }
+
+}( typeof window != 'undefined' ? window : this, function() {
+
+
+
+function ngEvEmitter() {}
+
+var proto = ngEvEmitter.prototype;
+
+proto.on = function( eventName, listener ) {
+  if ( !eventName || !listener ) {
+    return;
+  }
+  // set events hash
+  var events = this._events = this._events || {};
+  // set listeners array
+  var listeners = events[ eventName ] = events[ eventName ] || [];
+  // only add once
+  if ( listeners.indexOf( listener ) == -1 ) {
+    listeners.push( listener );
+  }
+
+  return this;
+};
+
+proto.once = function( eventName, listener ) {
+  if ( !eventName || !listener ) {
+    return;
+  }
+  // add event
+  this.on( eventName, listener );
+  // set once flag
+  // set onceEvents hash
+  var onceEvents = this._onceEvents = this._onceEvents || {};
+  // set onceListeners object
+  var onceListeners = onceEvents[ eventName ] = onceEvents[ eventName ] || {};
+  // set flag
+  onceListeners[ listener ] = true;
+
+  return this;
+};
+
+proto.off = function( eventName, listener ) {
+  var listeners = this._events && this._events[ eventName ];
+  if ( !listeners || !listeners.length ) {
+    return;
+  }
+  var index = listeners.indexOf( listener );
+  if ( index != -1 ) {
+    listeners.splice( index, 1 );
+  }
+
+  return this;
+};
+
+proto.emitEvent = function( eventName, args ) {
+  var listeners = this._events && this._events[ eventName ];
+  if ( !listeners || !listeners.length ) {
+    return;
+  }
+  var i = 0;
+  var listener = listeners[i];
+  args = args || [];
+  // once stuff
+  var onceListeners = this._onceEvents && this._onceEvents[ eventName ];
+
+  while ( listener ) {
+    var isOnce = onceListeners && onceListeners[ listener ];
+    if ( isOnce ) {
+      // remove listener
+      // remove before trigger to prevent recursion
+      this.off( eventName, listener );
+      // unset once flag
+      delete onceListeners[ listener ];
+    }
+    // trigger listener
+    listener.apply( this, args );
+    // get next listener
+    i += isOnce ? 0 : 1;
+    listener = listeners[i];
+  }
+
+  return this;
+};
+
+return ngEvEmitter;
+
+}));
+
+/*!
+ * ngimagesLoaded v4.1.1
+ * JavaScript is all like "You images are done yet or what?"
+ * MIT License
+ */
+
+( function( window, factory ) { 'use strict';
+  // universal module definition
+
+  /*global define: false, module: false, require: false */
+
+//  if ( typeof define == 'function' && define.amd ) {
+    // AMD
+//    define( [
+//      'ev-emitter/ev-emitter'
+//    ], function( ngEvEmitter ) {
+//      return factory( window, ngEvEmitter );
+//    });
+//  } else if ( typeof module == 'object' && module.exports ) {
+    // CommonJS
+//    module.exports = factory(
+//      window,
+//      require('ev-emitter')
+//    );
+//  } else {
+    // browser global
+    window.ngimagesLoaded = factory(
+      window,
+      window.ngEvEmitter
+    );
+  //}
+
+})( window,
+
+// --------------------------  factory -------------------------- //
+
+function factory( window, ngEvEmitter ) {
+
+
+
+// var $ = window.jQuery;
+var $ = jQuery;
+var console = window.console;
+
+// -------------------------- helpers -------------------------- //
+
+// extend objects
+function extend( a, b ) {
+  for ( var prop in b ) {
+    a[ prop ] = b[ prop ];
+  }
+  return a;
+}
+
+// turn element or nodeList into an array
+function makeArray( obj ) {
+  var ary = [];
+  if ( Array.isArray( obj ) ) {
+    // use object if already an array
+    ary = obj;
+  } else if ( typeof obj.length == 'number' ) {
+    // convert nodeList to array
+    for ( var i=0; i < obj.length; i++ ) {
+      ary.push( obj[i] );
+    }
+  } else {
+    // array of single index
+    ary.push( obj );
+  }
+  return ary;
+}
+
+// -------------------------- ngimagesLoaded -------------------------- //
+
+/**
+ * @param {Array, Element, NodeList, String} elem
+ * @param {Object or Function} options - if function, use as callback
+ * @param {Function} onAlways - callback function
+ */
+function ngImagesLoaded( elem, options, onAlways ) {
+  // coerce ngImagesLoaded() without new, to be new ngImagesLoaded()
+  if ( !( this instanceof ngImagesLoaded ) ) {
+    return new ngImagesLoaded( elem, options, onAlways );
+  }
+  // use elem as selector string
+  if ( typeof elem == 'string' ) {
+    elem = document.querySelectorAll( elem );
+  }
+
+  this.elements = makeArray( elem );
+  this.options = extend( {}, this.options );
+
+  if ( typeof options == 'function' ) {
+    onAlways = options;
+  } else {
+    extend( this.options, options );
+  }
+
+  if ( onAlways ) {
+    this.on( 'always', onAlways );
+  }
+
+  this.getImages();
+
+  if ( $ ) {
+    // add jQuery Deferred object
+    this.jqDeferred = new $.Deferred();
+  }
+
+  // HACK check async to allow time to bind listeners
+  setTimeout( function() {
+    this.check();
+  }.bind( this ));
+}
+
+ngImagesLoaded.prototype = Object.create( ngEvEmitter.prototype );
+
+ngImagesLoaded.prototype.options = {};
+
+ngImagesLoaded.prototype.getImages = function() {
+  this.images = [];
+
+  // filter & find items if we have an item selector
+  this.elements.forEach( this.addElementImages, this );
+};
+
+/**
+ * @param {Node} element
+ */
+ngImagesLoaded.prototype.addElementImages = function( elem ) {
+  // filter siblings
+  if ( elem.nodeName == 'IMG' ) {
+    this.addImage( elem );
+  }
+  // get background image on element
+  if ( this.options.background === true ) {
+    this.addElementBackgroundImages( elem );
+  }
+
+  // find children
+  // no non-element nodes, #143
+  var nodeType = elem.nodeType;
+  if ( !nodeType || !elementNodeTypes[ nodeType ] ) {
+    return;
+  }
+  var childImgs = elem.querySelectorAll('img');
+  // concat childElems to filterFound array
+  for ( var i=0; i < childImgs.length; i++ ) {
+    var img = childImgs[i];
+    this.addImage( img );
+  }
+
+  // get child background images
+  if ( typeof this.options.background == 'string' ) {
+    var children = elem.querySelectorAll( this.options.background );
+    for ( i=0; i < children.length; i++ ) {
+      var child = children[i];
+      this.addElementBackgroundImages( child );
+    }
+  }
+};
+
+var elementNodeTypes = {
+  1: true,
+  9: true,
+  11: true
+};
+
+ngImagesLoaded.prototype.addElementBackgroundImages = function( elem ) {
+  var style = getComputedStyle( elem );
+  if ( !style ) {
+    // Firefox returns null if in a hidden iframe https://bugzil.la/548397
+    return;
+  }
+  // get url inside url("...")
+  var reURL = /url\((['"])?(.*?)\1\)/gi;
+  var matches = reURL.exec( style.backgroundImage );
+  while ( matches !== null ) {
+    var url = matches && matches[2];
+    if ( url ) {
+      this.addBackground( url, elem );
+    }
+    matches = reURL.exec( style.backgroundImage );
+  }
+};
+
+/**
+ * @param {Image} img
+ */
+ngImagesLoaded.prototype.addImage = function( img ) {
+  var loadingImage = new LoadingImage( img );
+  this.images.push( loadingImage );
+};
+
+ngImagesLoaded.prototype.addBackground = function( url, elem ) {
+  var background = new Background( url, elem );
+  this.images.push( background );
+};
+
+ngImagesLoaded.prototype.check = function() {
+  var _this = this;
+  this.progressedCount = 0;
+  this.hasAnyBroken = false;
+  // complete if no images
+  if ( !this.images.length ) {
+    this.complete();
+    return;
+  }
+
+  function onProgress( image, elem, message ) {
+    // HACK - Chrome triggers event before object properties have changed. #83
+    setTimeout( function() {
+      _this.progress( image, elem, message );
+    });
+  }
+
+  this.images.forEach( function( loadingImage ) {
+    loadingImage.once( 'progress', onProgress );
+    loadingImage.check();
+  });
+};
+
+ngImagesLoaded.prototype.progress = function( image, elem, message ) {
+  this.progressedCount++;
+  this.hasAnyBroken = this.hasAnyBroken || !image.isLoaded;
+  // progress event
+  this.emitEvent( 'progress', [ this, image, elem ] );
+  if ( this.jqDeferred && this.jqDeferred.notify ) {
+    this.jqDeferred.notify( this, image );
+  }
+  // check if completed
+  if ( this.progressedCount == this.images.length ) {
+    this.complete();
+  }
+
+  if ( this.options.debug && console ) {
+    console.log( 'progress: ' + message, image, elem );
+  }
+};
+
+ngImagesLoaded.prototype.complete = function() {
+  var eventName = this.hasAnyBroken ? 'fail' : 'done';
+  this.isComplete = true;
+  this.emitEvent( eventName, [ this ] );
+  this.emitEvent( 'always', [ this ] );
+  if ( this.jqDeferred ) {
+    var jqMethod = this.hasAnyBroken ? 'reject' : 'resolve';
+    this.jqDeferred[ jqMethod ]( this );
+  }
+};
+
+// --------------------------  -------------------------- //
+
+function LoadingImage( img ) {
+  this.img = img;
+}
+
+LoadingImage.prototype = Object.create( ngEvEmitter.prototype );
+
+LoadingImage.prototype.check = function() {
+  // If complete is true and browser supports natural sizes,
+  // try to check for image status manually.
+  var isComplete = this.getIsImageComplete();
+  if ( isComplete ) {
+    // report based on naturalWidth
+    this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' );
+    return;
+  }
+
+  // If none of the checks above matched, simulate loading on detached element.
+  this.proxyImage = new Image();
+  this.proxyImage.addEventListener( 'load', this );
+  this.proxyImage.addEventListener( 'error', this );
+  // bind to image as well for Firefox. #191
+  this.img.addEventListener( 'load', this );
+  this.img.addEventListener( 'error', this );
+  this.proxyImage.src = this.img.src;
+};
+
+LoadingImage.prototype.getIsImageComplete = function() {
+  return this.img.complete && this.img.naturalWidth !== undefined;
+};
+
+LoadingImage.prototype.confirm = function( isLoaded, message ) {
+  this.isLoaded = isLoaded;
+  this.emitEvent( 'progress', [ this, this.img, message ] );
+};
+
+// ----- events ----- //
+
+// trigger specified handler for event type
+LoadingImage.prototype.handleEvent = function( event ) {
+  var method = 'on' + event.type;
+  if ( this[ method ] ) {
+    this[ method ]( event );
+  }
+};
+
+LoadingImage.prototype.onload = function() {
+  this.confirm( true, 'onload' );
+  this.unbindEvents();
+};
+
+LoadingImage.prototype.onerror = function() {
+  this.confirm( false, 'onerror' );
+  this.unbindEvents();
+};
+
+LoadingImage.prototype.unbindEvents = function() {
+  this.proxyImage.removeEventListener( 'load', this );
+  this.proxyImage.removeEventListener( 'error', this );
+  this.img.removeEventListener( 'load', this );
+  this.img.removeEventListener( 'error', this );
+};
+
+// -------------------------- Background -------------------------- //
+
+function Background( url, element ) {
+  this.url = url;
+  this.element = element;
+  this.img = new Image();
+}
+
+// inherit LoadingImage prototype
+Background.prototype = Object.create( LoadingImage.prototype );
+
+Background.prototype.check = function() {
+  this.img.addEventListener( 'load', this );
+  this.img.addEventListener( 'error', this );
+  this.img.src = this.url;
+  // check if image is already complete
+  var isComplete = this.getIsImageComplete();
+  if ( isComplete ) {
+    this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' );
+    this.unbindEvents();
+  }
+};
+
+Background.prototype.unbindEvents = function() {
+  this.img.removeEventListener( 'load', this );
+  this.img.removeEventListener( 'error', this );
+};
+
+Background.prototype.confirm = function( isLoaded, message ) {
+  this.isLoaded = isLoaded;
+  this.emitEvent( 'progress', [ this, this.element, message ] );
+};
+
+// -------------------------- jQuery -------------------------- //
+
+ngImagesLoaded.makeJQueryPlugin = function( jQuery ) {
+  jQuery = jQuery || window.jQuery;
+  if ( !jQuery ) {
+    return;
+  }
+  // set local variable
+  $ = jQuery;
+  // $().ngimagesLoaded()
+  $.fn.ngimagesLoaded = function( options, callback ) {
+    var instance = new ngImagesLoaded( this, options, callback );
+    return instance.jqDeferred.promise( $(this) );
+  };
+};
+// try making plugin
+ngImagesLoaded.makeJQueryPlugin();
+
+// --------------------------  -------------------------- //
+
+return ngImagesLoaded;
+
+});
+
+
+
+//##########################################################################################################################
+//## screenfull.js #########################################################################################################
+//##########################################################################################################################
+
+// screenfull.js
+// v4.0.1
+// by sindresorhus - https://github.com/sindresorhus
+// from: https://github.com/sindresorhus/screenfull.js
+
+// external module embeded in nanogallery
+// NGY BUILD:
+// replace "screenfull" with "ngscreenfull"
+// 
+
+(function () {
+	'use strict';
+
+	var document = typeof window !== 'undefined' && typeof window.document !== 'undefined' ? window.document : {};
+	var isCommonjs = typeof module !== 'undefined' && module.exports;
+	var keyboardAllowed = typeof Element !== 'undefined' && 'ALLOW_KEYBOARD_INPUT' in Element;
+
+	var fn = (function () {
+		var val;
+
+		var fnMap = [
+			[
+				'requestFullscreen',
+				'exitFullscreen',
+				'fullscreenElement',
+				'fullscreenEnabled',
+				'fullscreenchange',
+				'fullscreenerror'
+			],
+			// New WebKit
+			[
+				'webkitRequestFullscreen',
+				'webkitExitFullscreen',
+				'webkitFullscreenElement',
+				'webkitFullscreenEnabled',
+				'webkitfullscreenchange',
+				'webkitfullscreenerror'
+
+			],
+			// Old WebKit (Safari 5.1)
+			[
+				'webkitRequestFullScreen',
+				'webkitCancelFullScreen',
+				'webkitCurrentFullScreenElement',
+				'webkitCancelFullScreen',
+				'webkitfullscreenchange',
+				'webkitfullscreenerror'
+
+			],
+			[
+				'mozRequestFullScreen',
+				'mozCancelFullScreen',
+				'mozFullScreenElement',
+				'mozFullScreenEnabled',
+				'mozfullscreenchange',
+				'mozfullscreenerror'
+			],
+			[
+				'msRequestFullscreen',
+				'msExitFullscreen',
+				'msFullscreenElement',
+				'msFullscreenEnabled',
+				'MSFullscreenChange',
+				'MSFullscreenError'
+			]
+		];
+
+		var i = 0;
+		var l = fnMap.length;
+		var ret = {};
+
+		for (; i < l; i++) {
+			val = fnMap[i];
+			if (val && val[1] in document) {
+				for (i = 0; i < val.length; i++) {
+					ret[fnMap[0][i]] = val[i];
+				}
+				return ret;
+			}
+		}
+
+		return false;
+	})();
+
+	var eventNameMap = {
+		change: fn.fullscreenchange,
+		error: fn.fullscreenerror
+	};
+
+	var ngscreenfull = {
+		request: function (elem) {
+			return new Promise(function (resolve) {
+				var request = fn.requestFullscreen;
+
+				var onFullScreenEntered = function () {
+					this.off('change', onFullScreenEntered);
+					resolve();
+				}.bind(this);
+
+				elem = elem || document.documentElement;
+
+				// Work around Safari 5.1 bug: reports support for
+				// keyboard in fullscreen even though it doesn't.
+				// Browser sniffing, since the alternative with
+				// setTimeout is even worse.
+				if (/ Version\/5\.1(?:\.\d+)? Safari\//.test(navigator.userAgent)) {
+					elem[request]();
+				} else {
+					elem[request](keyboardAllowed ? Element.ALLOW_KEYBOARD_INPUT : {});
+				}
+
+				this.on('change', onFullScreenEntered);
+			}.bind(this));
+		},
+		exit: function () {
+			return new Promise(function (resolve) {
+				if (!this.isFullscreen) {
+					resolve();
+					return;
+				}
+
+				var onFullScreenExit = function () {
+					this.off('change', onFullScreenExit);
+					resolve();
+				}.bind(this);
+
+				document[fn.exitFullscreen]();
+
+				this.on('change', onFullScreenExit);
+			}.bind(this));
+		},
+		toggle: function (elem) {
+			return this.isFullscreen ? this.exit() : this.request(elem);
+		},
+		onchange: function (callback) {
+			this.on('change', callback);
+		},
+		onerror: function (callback) {
+			this.on('error', callback);
+		},
+		on: function (event, callback) {
+			var eventName = eventNameMap[event];
+			if (eventName) {
+				document.addEventListener(eventName, callback, false);
+			}
+		},
+		off: function (event, callback) {
+			var eventName = eventNameMap[event];
+			if (eventName) {
+				document.removeEventListener(eventName, callback, false);
+			}
+		},
+		raw: fn
+	};
+
+	if (!fn) {
+		if (isCommonjs) {
+			module.exports = false;
+		} else {
+			window.ngscreenfull = false;
+		}
+
+		return;
+	}
+
+	Object.defineProperties(ngscreenfull, {
+		isFullscreen: {
+			get: function () {
+				return Boolean(document[fn.fullscreenElement]);
+			}
+		},
+		element: {
+			enumerable: true,
+			get: function () {
+				return document[fn.fullscreenElement];
+			}
+		},
+		enabled: {
+			enumerable: true,
+			get: function () {
+				// Coerce to boolean in case of old WebKit
+				return Boolean(document[fn.fullscreenEnabled]);
+			}
+		}
+	});
+
+	if (isCommonjs) {
+		module.exports = ngscreenfull;
+	} else {
+		window.ngscreenfull = ngscreenfull;
+	}
+})();
+
+
+  
+//##########################################################################################################################
+//## Shifty ################################################################################################################
+//##########################################################################################################################
+  
+ /*!
+ * Shifty
+ * By Jeremy Kahn - jeremyckahn@gmail.com
+ */
+
+// external module EMBEDED in nanogallery
+// NGY BUILD:
+// 
+// replace "Tweenable" with "NGTweenable"
+// replace "define.amd" with "define.amdDISABLED"
+/* shifty - v1.5.3 - 2016-11-29 - http://jeremyckahn.github.io/shifty */
+;(function () {
+  var root = this || Function('return this')();
+
+/**
+ * Shifty Core
+ * By Jeremy Kahn - jeremyckahn@gmail.com
+ */
+
+var NGTweenable = (function () {
+
+  'use strict';
+
+  // Aliases that get defined later in this function
+  var formula;
+
+  // CONSTANTS
+  var DEFAULT_SCHEDULE_FUNCTION;
+  var DEFAULT_EASING = 'linear';
+  var DEFAULT_DURATION = 500;
+  var UPDATE_TIME = 1000 / 60;
+
+  var _now = Date.now
+       ? Date.now
+       : function () {return +new Date();};
+
+  var now = typeof SHIFTY_DEBUG_NOW !== 'undefined' ? SHIFTY_DEBUG_NOW : _now;
+
+  if (typeof window !== 'undefined') {
+    // requestAnimationFrame() shim by Paul Irish (modified for Shifty)
+    // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
+    DEFAULT_SCHEDULE_FUNCTION = window.requestAnimationFrame
+       || window.webkitRequestAnimationFrame
+       || window.oRequestAnimationFrame
+       || window.msRequestAnimationFrame
+       || (window.mozCancelRequestAnimationFrame
+       && window.mozRequestAnimationFrame)
+       || setTimeout;
+  } else {
+    DEFAULT_SCHEDULE_FUNCTION = setTimeout;
+  }
+
+  function noop () {
+    // NOOP!
+  }
+
+  /**
+   * Handy shortcut for doing a for-in loop. This is not a "normal" each
+   * function, it is optimized for Shifty.  The iterator function only receives
+   * the property name, not the value.
+   * @param {Object} obj
+   * @param {Function(string)} fn
+   * @private
+   */
+  function each (obj, fn) {
+    var key;
+    for (key in obj) {
+      if (Object.hasOwnProperty.call(obj, key)) {
+        fn(key);
+      }
+    }
+  }
+
+  /**
+   * Perform a shallow copy of Object properties.
+   * @param {Object} targetObject The object to copy into
+   * @param {Object} srcObject The object to copy from
+   * @return {Object} A reference to the augmented `targetObj` Object
+   * @private
+   */
+  function shallowCopy (targetObj, srcObj) {
+    each(srcObj, function (prop) {
+      targetObj[prop] = srcObj[prop];
+    });
+
+    return targetObj;
+  }
+
+  /**
+   * Copies each property from src onto target, but only if the property to
+   * copy to target is undefined.
+   * @param {Object} target Missing properties in this Object are filled in
+   * @param {Object} src
+   * @private
+   */
+  function defaults (target, src) {
+    each(src, function (prop) {
+      if (typeof target[prop] === 'undefined') {
+        target[prop] = src[prop];
+      }
+    });
+  }
+
+  /**
+   * Calculates the interpolated tween values of an Object for a given
+   * timestamp.
+   * @param {Number} forPosition The position to compute the state for.
+   * @param {Object} currentState Current state properties.
+   * @param {Object} originalState: The original state properties the Object is
+   * tweening from.
+   * @param {Object} targetState: The destination state properties the Object
+   * is tweening to.
+   * @param {number} duration: The length of the tween in milliseconds.
+   * @param {number} timestamp: The UNIX epoch time at which the tween began.
+   * @param {Object} easing: This Object's keys must correspond to the keys in
+   * targetState.
+   * @private
+   */
+  function tweenProps (forPosition, currentState, originalState, targetState,
+    duration, timestamp, easing) {
+    var normalizedPosition =
+        forPosition < timestamp ? 0 : (forPosition - timestamp) / duration;
+
+
+    var prop;
+    var easingObjectProp;
+    var easingFn;
+    for (prop in currentState) {
+      if (currentState.hasOwnProperty(prop)) {
+        easingObjectProp = easing[prop];
+        easingFn = typeof easingObjectProp === 'function'
+          ? easingObjectProp
+          : formula[easingObjectProp];
+
+        currentState[prop] = tweenProp(
+          originalState[prop],
+          targetState[prop],
+          easingFn,
+          normalizedPosition
+        );
+      }
+    }
+
+    return currentState;
+  }
+
+  /**
+   * Tweens a single property.
+   * @param {number} start The value that the tween started from.
+   * @param {number} end The value that the tween should end at.
+   * @param {Function} easingFunc The easing curve to apply to the tween.
+   * @param {number} position The normalized position (between 0.0 and 1.0) to
+   * calculate the midpoint of 'start' and 'end' against.
+   * @return {number} The tweened value.
+   * @private
+   */
+  function tweenProp (start, end, easingFunc, position) {
+    return start + (end - start) * easingFunc(position);
+  }
+
+  /**
+   * Applies a filter to NGTweenable instance.
+   * @param {NGTweenable} tweenable The `NGTweenable` instance to call the filter
+   * upon.
+   * @param {String} filterName The name of the filter to apply.
+   * @private
+   */
+  function applyFilter (tweenable, filterName) {
+    var filters = NGTweenable.prototype.filter;
+    var args = tweenable._filterArgs;
+
+    each(filters, function (name) {
+      if (typeof filters[name][filterName] !== 'undefined') {
+        filters[name][filterName].apply(tweenable, args);
+      }
+    });
+  }
+
+  var timeoutHandler_endTime;
+  var timeoutHandler_currentTime;
+  var timeoutHandler_isEnded;
+  var timeoutHandler_offset;
+  /**
+   * Handles the update logic for one step of a tween.
+   * @param {NGTweenable} tweenable
+   * @param {number} timestamp
+   * @param {number} delay
+   * @param {number} duration
+   * @param {Object} currentState
+   * @param {Object} originalState
+   * @param {Object} targetState
+   * @param {Object} easing
+   * @param {Function(Object, *, number)} step
+   * @param {Function(Function,number)}} schedule
+   * @param {number=} opt_currentTimeOverride Needed for accurate timestamp in
+   * NGTweenable#seek.
+   * @private
+   */
+  function timeoutHandler (tweenable, timestamp, delay, duration, currentState,
+    originalState, targetState, easing, step, schedule,
+    opt_currentTimeOverride) {
+
+    timeoutHandler_endTime = timestamp + delay + duration;
+
+    timeoutHandler_currentTime =
+    Math.min(opt_currentTimeOverride || now(), timeoutHandler_endTime);
+
+    timeoutHandler_isEnded =
+      timeoutHandler_currentTime >= timeoutHandler_endTime;
+
+    timeoutHandler_offset = duration - (
+      timeoutHandler_endTime - timeoutHandler_currentTime);
+
+    if (tweenable.isPlaying()) {
+      if (timeoutHandler_isEnded) {
+        step(targetState, tweenable._attachment, timeoutHandler_offset);
+        tweenable.stop(true);
+      } else {
+        tweenable._scheduleId =
+          schedule(tweenable._timeoutHandler, UPDATE_TIME);
+
+        applyFilter(tweenable, 'beforeTween');
+
+        // If the animation has not yet reached the start point (e.g., there was
+        // delay that has not yet completed), just interpolate the starting
+        // position of the tween.
+        if (timeoutHandler_currentTime < (timestamp + delay)) {
+          tweenProps(1, currentState, originalState, targetState, 1, 1, easing);
+        } else {
+          tweenProps(timeoutHandler_currentTime, currentState, originalState,
+            targetState, duration, timestamp + delay, easing);
+        }
+
+        applyFilter(tweenable, 'afterTween');
+
+        step(currentState, tweenable._attachment, timeoutHandler_offset);
+      }
+    }
+  }
+
+
+  /**
+   * Creates a usable easing Object from a string, a function or another easing
+   * Object.  If `easing` is an Object, then this function clones it and fills
+   * in the missing properties with `"linear"`.
+   * @param {Object.<string|Function>} fromTweenParams
+   * @param {Object|string|Function} easing
+   * @return {Object.<string|Function>}
+   * @private
+   */
+  function composeEasingObject (fromTweenParams, easing) {
+    var composedEasing = {};
+    var typeofEasing = typeof easing;
+
+    if (typeofEasing === 'string' || typeofEasing === 'function') {
+      each(fromTweenParams, function (prop) {
+        composedEasing[prop] = easing;
+      });
+    } else {
+      each(fromTweenParams, function (prop) {
+        if (!composedEasing[prop]) {
+          composedEasing[prop] = easing[prop] || DEFAULT_EASING;
+        }
+      });
+    }
+
+    return composedEasing;
+  }
+
+  /**
+   * NGTweenable constructor.
+   * @class NGTweenable
+   * @param {Object=} opt_initialState The values that the initial tween should
+   * start at if a `from` object is not provided to `{{#crossLink
+   * "NGTweenable/tween:method"}}{{/crossLink}}` or `{{#crossLink
+   * "NGTweenable/setConfig:method"}}{{/crossLink}}`.
+   * @param {Object=} opt_config Configuration object to be passed to
+   * `{{#crossLink "NGTweenable/setConfig:method"}}{{/crossLink}}`.
+   * @module NGTweenable
+   * @constructor
+   */
+  function NGTweenable (opt_initialState, opt_config) {
+    this._currentState = opt_initialState || {};
+    this._configured = false;
+    this._scheduleFunction = DEFAULT_SCHEDULE_FUNCTION;
+
+    // To prevent unnecessary calls to setConfig do not set default
+    // configuration here.  Only set default configuration immediately before
+    // tweening if none has been set.
+    if (typeof opt_config !== 'undefined') {
+      this.setConfig(opt_config);
+    }
+  }
+
+  /**
+   * Configure and start a tween.
+   * @method tween
+   * @param {Object=} opt_config Configuration object to be passed to
+   * `{{#crossLink "NGTweenable/setConfig:method"}}{{/crossLink}}`.
+   * @chainable
+   */
+  NGTweenable.prototype.tween = function (opt_config) {
+    if (this._isTweening) {
+      return this;
+    }
+
+    // Only set default config if no configuration has been set previously and
+    // none is provided now.
+    if (opt_config !== undefined || !this._configured) {
+      this.setConfig(opt_config);
+    }
+
+    this._timestamp = now();
+    this._start(this.get(), this._attachment);
+    return this.resume();
+  };
+
+  /**
+   * Configure a tween that will start at some point in the future.
+   *
+   * @method setConfig
+   * @param {Object} config The following values are valid:
+   * - __from__ (_Object=_): Starting position.  If omitted, `{{#crossLink
+   *   "NGTweenable/get:method"}}get(){{/crossLink}}` is used.
+   * - __to__ (_Object=_): Ending position.
+   * - __duration__ (_number=_): How many milliseconds to animate for.
+   * - __delay__ (_delay=_): How many milliseconds to wait before starting the
+   *   tween.
+   * - __start__ (_Function(Object, *)_): Function to execute when the tween
+   *   begins.  Receives the state of the tween as the first parameter and
+   *   `attachment` as the second parameter.
+   * - __step__ (_Function(Object, *, number)_): Function to execute on every
+   *   tick.  Receives `{{#crossLink
+   *   "NGTweenable/get:method"}}get(){{/crossLink}}` as the first parameter,
+   *   `attachment` as the second parameter, and the time elapsed since the
+   *   start of the tween as the third. This function is not called on the
+   *   final step of the animation, but `finish` is.
+   * - __finish__ (_Function(Object, *)_): Function to execute upon tween
+   *   completion.  Receives the state of the tween as the first parameter and
+   *   `attachment` as the second parameter.
+   * - __easing__ (_Object.<string|Function>|string|Function=_): Easing curve
+   *   name(s) or function(s) to use for the tween.
+   * - __attachment__ (_*_): Cached value that is passed to the
+   *   `step`/`start`/`finish` methods.
+   * @chainable
+   */
+  NGTweenable.prototype.setConfig = function (config) {
+    config = config || {};
+    this._configured = true;
+
+    // Attach something to this NGTweenable instance (e.g.: a DOM element, an
+    // object, a string, etc.);
+    this._attachment = config.attachment;
+
+    // Init the internal state
+    this._pausedAtTime = null;
+    this._scheduleId = null;
+    this._delay = config.delay || 0;
+    this._start = config.start || noop;
+    this._step = config.step || noop;
+    this._finish = config.finish || noop;
+    this._duration = config.duration || DEFAULT_DURATION;
+    this._currentState = shallowCopy({}, config.from || this.get());
+    this._originalState = this.get();
+    this._targetState = shallowCopy({}, config.to || this.get());
+
+    var self = this;
+    this._timeoutHandler = function () {
+      timeoutHandler(self,
+        self._timestamp,
+        self._delay,
+        self._duration,
+        self._currentState,
+        self._originalState,
+        self._targetState,
+        self._easing,
+        self._step,
+        self._scheduleFunction
+      );
+    };
+
+    // Aliases used below
+    var currentState = this._currentState;
+    var targetState = this._targetState;
+
+    // Ensure that there is always something to tween to.
+    defaults(targetState, currentState);
+
+    this._easing = composeEasingObject(
+      currentState, config.easing || DEFAULT_EASING);
+
+    this._filterArgs =
+      [currentState, this._originalState, targetState, this._easing];
+
+    applyFilter(this, 'tweenCreated');
+    return this;
+  };
+
+  /**
+   * @method get
+   * @return {Object} The current state.
+   */
+  NGTweenable.prototype.get = function () {
+    return shallowCopy({}, this._currentState);
+  };
+
+  /**
+   * @method set
+   * @param {Object} state The current state.
+   */
+  NGTweenable.prototype.set = function (state) {
+    this._currentState = state;
+  };
+
+  /**
+   * Pause a tween.  Paused tweens can be resumed from the point at which they
+   * were paused.  This is different from `{{#crossLink
+   * "NGTweenable/stop:method"}}{{/crossLink}}`, as that method
+   * causes a tween to start over when it is resumed.
+   * @method pause
+   * @chainable
+   */
+  NGTweenable.prototype.pause = function () {
+    this._pausedAtTime = now();
+    this._isPaused = true;
+    return this;
+  };
+
+  /**
+   * Resume a paused tween.
+   * @method resume
+   * @chainable
+   */
+  NGTweenable.prototype.resume = function () {
+    if (this._isPaused) {
+      this._timestamp += now() - this._pausedAtTime;
+    }
+
+    this._isPaused = false;
+    this._isTweening = true;
+
+    this._timeoutHandler();
+
+    return this;
+  };
+
+  /**
+   * Move the state of the animation to a specific point in the tween's
+   * timeline.  If the animation is not running, this will cause the `step`
+   * handlers to be called.
+   * @method seek
+   * @param {millisecond} millisecond The millisecond of the animation to seek
+   * to.  This must not be less than `0`.
+   * @chainable
+   */
+  NGTweenable.prototype.seek = function (millisecond) {
+    millisecond = Math.max(millisecond, 0);
+    var currentTime = now();
+
+    if ((this._timestamp + millisecond) === 0) {
+      return this;
+    }
+
+    this._timestamp = currentTime - millisecond;
+
+    if (!this.isPlaying()) {
+      this._isTweening = true;
+      this._isPaused = false;
+
+      // If the animation is not running, call timeoutHandler to make sure that
+      // any step handlers are run.
+      timeoutHandler(this,
+        this._timestamp,
+        this._delay,
+        this._duration,
+        this._currentState,
+        this._originalState,
+        this._targetState,
+        this._easing,
+        this._step,
+        this._scheduleFunction,
+        currentTime
+      );
+
+      this.pause();
+    }
+
+    return this;
+  };
+
+  /**
+   * Stops and cancels a tween.
+   * @param {boolean=} gotoEnd If `false` or omitted, the tween just stops at
+   * its current state, and the `finish` handler is not invoked.  If `true`,
+   * the tweened object's values are instantly set to the target values, and
+   * `finish` is invoked.
+   * @method stop
+   * @chainable
+   */
+  NGTweenable.prototype.stop = function (gotoEnd) {
+    this._isTweening = false;
+    this._isPaused = false;
+    this._timeoutHandler = noop;
+
+    (root.cancelAnimationFrame            ||
+    root.webkitCancelAnimationFrame     ||
+    root.oCancelAnimationFrame          ||
+    root.msCancelAnimationFrame         ||
+    root.mozCancelRequestAnimationFrame ||
+    root.clearTimeout)(this._scheduleId);
+
+    if (gotoEnd) {
+      applyFilter(this, 'beforeTween');
+      tweenProps(
+        1,
+        this._currentState,
+        this._originalState,
+        this._targetState,
+        1,
+        0,
+        this._easing
+      );
+      applyFilter(this, 'afterTween');
+      applyFilter(this, 'afterTweenEnd');
+      this._finish.call(this, this._currentState, this._attachment);
+    }
+
+    return this;
+  };
+
+  /**
+   * @method isPlaying
+   * @return {boolean} Whether or not a tween is running.
+   */
+  NGTweenable.prototype.isPlaying = function () {
+    return this._isTweening && !this._isPaused;
+  };
+
+  /**
+   * Set a custom schedule function.
+   *
+   * If a custom function is not set,
+   * [`requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/window.requestAnimationFrame)
+   * is used if available, otherwise
+   * [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/Window.setTimeout)
+   * is used.
+   * @method setScheduleFunction
+   * @param {Function(Function,number)} scheduleFunction The function to be
+   * used to schedule the next frame to be rendered.
+   */
+  NGTweenable.prototype.setScheduleFunction = function (scheduleFunction) {
+    this._scheduleFunction = scheduleFunction;
+  };
+
+  /**
+   * `delete` all "own" properties.  Call this when the `NGTweenable` instance
+   * is no longer needed to free memory.
+   * @method dispose
+   */
+  NGTweenable.prototype.dispose = function () {
+    var prop;
+    for (prop in this) {
+      if (this.hasOwnProperty(prop)) {
+        delete this[prop];
+      }
+    }
+  };
+
+  /**
+   * Filters are used for transforming the properties of a tween at various
+   * points in a NGTweenable's life cycle.  See the README for more info on this.
+   * @private
+   */
+  NGTweenable.prototype.filter = {};
+
+  /**
+   * This object contains all of the tweens available to Shifty.  It is
+   * extensible - simply attach properties to the `NGTweenable.prototype.formula`
+   * Object following the same format as `linear`.
+   *
+   * `pos` should be a normalized `number` (between 0 and 1).
+   * @property formula
+   * @type {Object(function)}
+   */
+  NGTweenable.prototype.formula = {
+    linear: function (pos) {
+      return pos;
+    }
+  };
+
+  formula = NGTweenable.prototype.formula;
+
+  shallowCopy(NGTweenable, {
+    'now': now
+    ,'each': each
+    ,'tweenProps': tweenProps
+    ,'tweenProp': tweenProp
+    ,'applyFilter': applyFilter
+    ,'shallowCopy': shallowCopy
+    ,'defaults': defaults
+    ,'composeEasingObject': composeEasingObject
+  });
+
+  // `root` is provided in the intro/outro files.
+
+  // A hook used for unit testing.
+  if (typeof SHIFTY_DEBUG_NOW === 'function') {
+    root.timeoutHandler = timeoutHandler;
+  }
+
+  // Bootstrap NGTweenable appropriately for the environment.
+  if (typeof exports === 'object') {
+    // CommonJS
+    module.exports = NGTweenable;
+  } else if (typeof define === 'function' && define.amdDISABLED) {
+    // AMD
+    define(function () {return NGTweenable;});
+  } else if (typeof root.NGTweenable === 'undefined') {
+    // Browser: Make `NGTweenable` globally accessible.
+    root.NGTweenable = NGTweenable;
+  }
+
+  return NGTweenable;
+
+} ());
+
+/*!
+ * All equations are adapted from Thomas Fuchs'
+ * [Scripty2](https://github.com/madrobby/scripty2/blob/master/src/effects/transitions/penner.js).
+ *
+ * Based on Easing Equations (c) 2003 [Robert
+ * Penner](http://www.robertpenner.com/), all rights reserved. This work is
+ * [subject to terms](http://www.robertpenner.com/easing_terms_of_use.html).
+ */
+
+/*!
+ *  TERMS OF USE - EASING EQUATIONS
+ *  Open source under the BSD License.
+ *  Easing Equations (c) 2003 Robert Penner, all rights reserved.
+ */
+
+;(function () {
+
+  NGTweenable.shallowCopy(NGTweenable.prototype.formula, {
+    easeInQuad: function (pos) {
+      return Math.pow(pos, 2);
+    },
+
+    easeOutQuad: function (pos) {
+      return -(Math.pow((pos - 1), 2) - 1);
+    },
+
+    easeInOutQuad: function (pos) {
+      if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,2);}
+      return -0.5 * ((pos -= 2) * pos - 2);
+    },
+
+    easeInCubic: function (pos) {
+      return Math.pow(pos, 3);
+    },
+
+    easeOutCubic: function (pos) {
+      return (Math.pow((pos - 1), 3) + 1);
+    },
+
+    easeInOutCubic: function (pos) {
+      if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,3);}
+      return 0.5 * (Math.pow((pos - 2),3) + 2);
+    },
+
+    easeInQuart: function (pos) {
+      return Math.pow(pos, 4);
+    },
+
+    easeOutQuart: function (pos) {
+      return -(Math.pow((pos - 1), 4) - 1);
+    },
+
+    easeInOutQuart: function (pos) {
+      if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,4);}
+      return -0.5 * ((pos -= 2) * Math.pow(pos,3) - 2);
+    },
+
+    easeInQuint: function (pos) {
+      return Math.pow(pos, 5);
+    },
+
+    easeOutQuint: function (pos) {
+      return (Math.pow((pos - 1), 5) + 1);
+    },
+
+    easeInOutQuint: function (pos) {
+      if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,5);}
+      return 0.5 * (Math.pow((pos - 2),5) + 2);
+    },
+
+    easeInSine: function (pos) {
+      return -Math.cos(pos * (Math.PI / 2)) + 1;
+    },
+
+    easeOutSine: function (pos) {
+      return Math.sin(pos * (Math.PI / 2));
+    },
+
+    easeInOutSine: function (pos) {
+      return (-0.5 * (Math.cos(Math.PI * pos) - 1));
+    },
+
+    easeInExpo: function (pos) {
+      return (pos === 0) ? 0 : Math.pow(2, 10 * (pos - 1));
+    },
+
+    easeOutExpo: function (pos) {
+      return (pos === 1) ? 1 : -Math.pow(2, -10 * pos) + 1;
+    },
+
+    easeInOutExpo: function (pos) {
+      if (pos === 0) {return 0;}
+      if (pos === 1) {return 1;}
+      if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(2,10 * (pos - 1));}
+      return 0.5 * (-Math.pow(2, -10 * --pos) + 2);
+    },
+
+    easeInCirc: function (pos) {
+      return -(Math.sqrt(1 - (pos * pos)) - 1);
+    },
+
+    easeOutCirc: function (pos) {
+      return Math.sqrt(1 - Math.pow((pos - 1), 2));
+    },
+
+    easeInOutCirc: function (pos) {
+      if ((pos /= 0.5) < 1) {return -0.5 * (Math.sqrt(1 - pos * pos) - 1);}
+      return 0.5 * (Math.sqrt(1 - (pos -= 2) * pos) + 1);
+    },
+
+    easeOutBounce: function (pos) {
+      if ((pos) < (1 / 2.75)) {
+        return (7.5625 * pos * pos);
+      } else if (pos < (2 / 2.75)) {
+        return (7.5625 * (pos -= (1.5 / 2.75)) * pos + 0.75);
+      } else if (pos < (2.5 / 2.75)) {
+        return (7.5625 * (pos -= (2.25 / 2.75)) * pos + 0.9375);
+      } else {
+        return (7.5625 * (pos -= (2.625 / 2.75)) * pos + 0.984375);
+      }
+    },
+
+    easeInBack: function (pos) {
+      var s = 1.70158;
+      return (pos) * pos * ((s + 1) * pos - s);
+    },
+
+    easeOutBack: function (pos) {
+      var s = 1.70158;
+      return (pos = pos - 1) * pos * ((s + 1) * pos + s) + 1;
+    },
+
+    easeInOutBack: function (pos) {
+      var s = 1.70158;
+      if ((pos /= 0.5) < 1) {
+        return 0.5 * (pos * pos * (((s *= (1.525)) + 1) * pos - s));
+      }
+      return 0.5 * ((pos -= 2) * pos * (((s *= (1.525)) + 1) * pos + s) + 2);
+    },
+
+    elastic: function (pos) {
+      // jshint maxlen:90
+      return -1 * Math.pow(4,-8 * pos) * Math.sin((pos * 6 - 1) * (2 * Math.PI) / 2) + 1;
+    },
+
+    swingFromTo: function (pos) {
+      var s = 1.70158;
+      return ((pos /= 0.5) < 1) ?
+          0.5 * (pos * pos * (((s *= (1.525)) + 1) * pos - s)) :
+          0.5 * ((pos -= 2) * pos * (((s *= (1.525)) + 1) * pos + s) + 2);
+    },
+
+    swingFrom: function (pos) {
+      var s = 1.70158;
+      return pos * pos * ((s + 1) * pos - s);
+    },
+
+    swingTo: function (pos) {
+      var s = 1.70158;
+      return (pos -= 1) * pos * ((s + 1) * pos + s) + 1;
+    },
+
+    bounce: function (pos) {
+      if (pos < (1 / 2.75)) {
+        return (7.5625 * pos * pos);
+      } else if (pos < (2 / 2.75)) {
+        return (7.5625 * (pos -= (1.5 / 2.75)) * pos + 0.75);
+      } else if (pos < (2.5 / 2.75)) {
+        return (7.5625 * (pos -= (2.25 / 2.75)) * pos + 0.9375);
+      } else {
+        return (7.5625 * (pos -= (2.625 / 2.75)) * pos + 0.984375);
+      }
+    },
+
+    bouncePast: function (pos) {
+      if (pos < (1 / 2.75)) {
+        return (7.5625 * pos * pos);
+      } else if (pos < (2 / 2.75)) {
+        return 2 - (7.5625 * (pos -= (1.5 / 2.75)) * pos + 0.75);
+      } else if (pos < (2.5 / 2.75)) {
+        return 2 - (7.5625 * (pos -= (2.25 / 2.75)) * pos + 0.9375);
+      } else {
+        return 2 - (7.5625 * (pos -= (2.625 / 2.75)) * pos + 0.984375);
+      }
+    },
+
+    easeFromTo: function (pos) {
+      if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,4);}
+      return -0.5 * ((pos -= 2) * Math.pow(pos,3) - 2);
+    },
+
+    easeFrom: function (pos) {
+      return Math.pow(pos,4);
+    },
+
+    easeTo: function (pos) {
+      return Math.pow(pos,0.25);
+    }
+  });
+
+}());
+
+// jshint maxlen:100
+/**
+ * The Bezier magic in this file is adapted/copied almost wholesale from
+ * [Scripty2](https://github.com/madrobby/scripty2/blob/master/src/effects/transitions/cubic-bezier.js),
+ * which was adapted from Apple code (which probably came from
+ * [here](http://opensource.apple.com/source/WebCore/WebCore-955.66/platform/graphics/UnitBezier.h)).
+ * Special thanks to Apple and Thomas Fuchs for much of this code.
+ */
+
+/**
+ *  Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  3. Neither the name of the copyright holder(s) nor the names of any
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ *  POSSIBILITY OF SUCH DAMAGE.
+ */
+;(function () {
+  // port of webkit cubic bezier handling by http://www.netzgesta.de/dev/
+  function cubicBezierAtTime(t,p1x,p1y,p2x,p2y,duration) {
+    var ax = 0,bx = 0,cx = 0,ay = 0,by = 0,cy = 0;
+    function sampleCurveX(t) {
+      return ((ax * t + bx) * t + cx) * t;
+    }
+    function sampleCurveY(t) {
+      return ((ay * t + by) * t + cy) * t;
+    }
+    function sampleCurveDerivativeX(t) {
+      return (3.0 * ax * t + 2.0 * bx) * t + cx;
+    }
+    function solveEpsilon(duration) {
+      return 1.0 / (200.0 * duration);
+    }
+    function solve(x,epsilon) {
+      return sampleCurveY(solveCurveX(x, epsilon));
+    }
+    function fabs(n) {
+      if (n >= 0) {
+        return n;
+      } else {
+        return 0 - n;
+      }
+    }
+    function solveCurveX(x, epsilon) {
+      var t0,t1,t2,x2,d2,i;
+      for (t2 = x, i = 0; i < 8; i++) {
+        x2 = sampleCurveX(t2) - x;
+        if (fabs(x2) < epsilon) {
+          return t2;
+        }
+        d2 = sampleCurveDerivativeX(t2);
+        if (fabs(d2) < 1e-6) {
+          break;
+        }
+        t2 = t2 - x2 / d2;
+      }
+      t0 = 0.0;
+      t1 = 1.0;
+      t2 = x;
+      if (t2 < t0) {
+        return t0;
+      }
+      if (t2 > t1) {
+        return t1;
+      }
+      while (t0 < t1) {
+        x2 = sampleCurveX(t2);
+        if (fabs(x2 - x) < epsilon) {
+          return t2;
+        }
+        if (x > x2) {
+          t0 = t2;
+        }else {
+          t1 = t2;
+        }
+        t2 = (t1 - t0) * 0.5 + t0;
+      }
+      return t2; // Failure.
+    }
+    cx = 3.0 * p1x;
+    bx = 3.0 * (p2x - p1x) - cx;
+    ax = 1.0 - cx - bx;
+    cy = 3.0 * p1y;
+    by = 3.0 * (p2y - p1y) - cy;
+    ay = 1.0 - cy - by;
+    return solve(t, solveEpsilon(duration));
+  }
+  /**
+   *  getCubicBezierTransition(x1, y1, x2, y2) -> Function
+   *
+   *  Generates a transition easing function that is compatible
+   *  with WebKit's CSS transitions `-webkit-transition-timing-function`
+   *  CSS property.
+   *
+   *  The W3C has more information about CSS3 transition timing functions:
+   *  http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
+   *
+   *  @param {number} x1
+   *  @param {number} y1
+   *  @param {number} x2
+   *  @param {number} y2
+   *  @return {function}
+   *  @private
+   */
+  function getCubicBezierTransition (x1, y1, x2, y2) {
+    return function (pos) {
+      return cubicBezierAtTime(pos,x1,y1,x2,y2,1);
+    };
+  }
+  // End ported code
+
+  /**
+   * Create a Bezier easing function and attach it to `{{#crossLink
+   * "NGTweenable/formula:property"}}NGTweenable#formula{{/crossLink}}`.  This
+   * function gives you total control over the easing curve.  Matthew Lein's
+   * [Ceaser](http://matthewlein.com/ceaser/) is a useful tool for visualizing
+   * the curves you can make with this function.
+   * @method setBezierFunction
+   * @param {string} name The name of the easing curve.  Overwrites the old
+   * easing function on `{{#crossLink
+   * "NGTweenable/formula:property"}}NGTweenable#formula{{/crossLink}}` if it
+   * exists.
+   * @param {number} x1
+   * @param {number} y1
+   * @param {number} x2
+   * @param {number} y2
+   * @return {function} The easing function that was attached to
+   * NGTweenable.prototype.formula.
+   */
+  NGTweenable.setBezierFunction = function (name, x1, y1, x2, y2) {
+    var cubicBezierTransition = getCubicBezierTransition(x1, y1, x2, y2);
+    cubicBezierTransition.displayName = name;
+    cubicBezierTransition.x1 = x1;
+    cubicBezierTransition.y1 = y1;
+    cubicBezierTransition.x2 = x2;
+    cubicBezierTransition.y2 = y2;
+
+    return NGTweenable.prototype.formula[name] = cubicBezierTransition;
+  };
+
+
+  /**
+   * `delete` an easing function from `{{#crossLink
+   * "NGTweenable/formula:property"}}NGTweenable#formula{{/crossLink}}`.  Be
+   * careful with this method, as it `delete`s whatever easing formula matches
+   * `name` (which means you can delete standard Shifty easing functions).
+   * @method unsetBezierFunction
+   * @param {string} name The name of the easing function to delete.
+   * @return {function}
+   */
+  NGTweenable.unsetBezierFunction = function (name) {
+    delete NGTweenable.prototype.formula[name];
+  };
+
+})();
+
+;(function () {
+
+  function getInterpolatedValues (
+    from, current, targetState, position, easing, delay) {
+    return NGTweenable.tweenProps(
+      position, current, from, targetState, 1, delay, easing);
+  }
+
+  // Fake a NGTweenable and patch some internals.  This approach allows us to
+  // skip uneccessary processing and object recreation, cutting down on garbage
+  // collection pauses.
+  var mockNGTweenable = new NGTweenable();
+  mockNGTweenable._filterArgs = [];
+
+  /**
+   * Compute the midpoint of two Objects.  This method effectively calculates a
+   * specific frame of animation that `{{#crossLink
+   * "NGTweenable/tween:method"}}{{/crossLink}}` does many times over the course
+   * of a full tween.
+   *
+   *     var interpolatedValues = NGTweenable.interpolate({
+   *       width: '100px',
+   *       opacity: 0,
+   *       color: '#fff'
+   *     }, {
+   *       width: '200px',
+   *       opacity: 1,
+   *       color: '#000'
+   *     }, 0.5);
+   *
+   *     console.log(interpolatedValues);
+   *     // {opacity: 0.5, width: "150px", color: "rgb(127,127,127)"}
+   *
+   * @static
+   * @method interpolate
+   * @param {Object} from The starting values to tween from.
+   * @param {Object} targetState The ending values to tween to.
+   * @param {number} position The normalized position value (between `0.0` and
+   * `1.0`) to interpolate the values between `from` and `to` for.  `from`
+   * represents `0` and `to` represents `1`.
+   * @param {Object.<string|Function>|string|Function} easing The easing
+   * curve(s) to calculate the midpoint against.  You can reference any easing
+   * function attached to `NGTweenable.prototype.formula`, or provide the easing
+   * function(s) directly.  If omitted, this defaults to "linear".
+   * @param {number=} opt_delay Optional delay to pad the beginning of the
+   * interpolated tween with.  This increases the range of `position` from (`0`
+   * through `1`) to (`0` through `1 + opt_delay`).  So, a delay of `0.5` would
+   * increase all valid values of `position` to numbers between `0` and `1.5`.
+   * @return {Object}
+   */
+  NGTweenable.interpolate = function (
+    from, targetState, position, easing, opt_delay) {
+
+    var current = NGTweenable.shallowCopy({}, from);
+    var delay = opt_delay || 0;
+    var easingObject = NGTweenable.composeEasingObject(
+      from, easing || 'linear');
+
+    mockNGTweenable.set({});
+
+    // Alias and reuse the _filterArgs array instead of recreating it.
+    var filterArgs = mockNGTweenable._filterArgs;
+    filterArgs.length = 0;
+    filterArgs[0] = current;
+    filterArgs[1] = from;
+    filterArgs[2] = targetState;
+    filterArgs[3] = easingObject;
+
+    // Any defined value transformation must be applied
+    NGTweenable.applyFilter(mockNGTweenable, 'tweenCreated');
+    NGTweenable.applyFilter(mockNGTweenable, 'beforeTween');
+
+    var interpolatedValues = getInterpolatedValues(
+      from, current, targetState, position, easingObject, delay);
+
+    // Transform values back into their original format
+    NGTweenable.applyFilter(mockNGTweenable, 'afterTween');
+
+    return interpolatedValues;
+  };
+
+}());
+
+/**
+ * This module adds string interpolation support to Shifty.
+ *
+ * The Token extension allows Shifty to tween numbers inside of strings.  Among
+ * other things, this allows you to animate CSS properties.  For example, you
+ * can do this:
+ *
+ *     var tweenable = new NGTweenable();
+ *     tweenable.tween({
+ *       from: { transform: 'translateX(45px)' },
+ *       to: { transform: 'translateX(90xp)' }
+ *     });
+ *
+ * `translateX(45)` will be tweened to `translateX(90)`.  To demonstrate:
+ *
+ *     var tweenable = new NGTweenable();
+ *     tweenable.tween({
+ *       from: { transform: 'translateX(45px)' },
+ *       to: { transform: 'translateX(90px)' },
+ *       step: function (state) {
+ *         console.log(state.transform);
+ *       }
+ *     });
+ *
+ * The above snippet will log something like this in the console:
+ *
+ *     translateX(60.3px)
+ *     ...
+ *     translateX(76.05px)
+ *     ...
+ *     translateX(90px)
+ *
+ * Another use for this is animating colors:
+ *
+ *     var tweenable = new NGTweenable();
+ *     tweenable.tween({
+ *       from: { color: 'rgb(0,255,0)' },
+ *       to: { color: 'rgb(255,0,255)' },
+ *       step: function (state) {
+ *         console.log(state.color);
+ *       }
+ *     });
+ *
+ * The above snippet will log something like this:
+ *
+ *     rgb(84,170,84)
+ *     ...
+ *     rgb(170,84,170)
+ *     ...
+ *     rgb(255,0,255)
+ *
+ * This extension also supports hexadecimal colors, in both long (`#ff00ff`)
+ * and short (`#f0f`) forms.  Be aware that hexadecimal input values will be
+ * converted into the equivalent RGB output values.  This is done to optimize
+ * for performance.
+ *
+ *     var tweenable = new NGTweenable();
+ *     tweenable.tween({
+ *       from: { color: '#0f0' },
+ *       to: { color: '#f0f' },
+ *       step: function (state) {
+ *         console.log(state.color);
+ *       }
+ *     });
+ *
+ * This snippet will generate the same output as the one before it because
+ * equivalent values were supplied (just in hexadecimal form rather than RGB):
+ *
+ *     rgb(84,170,84)
+ *     ...
+ *     rgb(170,84,170)
+ *     ...
+ *     rgb(255,0,255)
+ *
+ * ## Easing support
+ *
+ * Easing works somewhat differently in the Token extension.  This is because
+ * some CSS properties have multiple values in them, and you might need to
+ * tween each value along its own easing curve.  A basic example:
+ *
+ *     var tweenable = new NGTweenable();
+ *     tweenable.tween({
+ *       from: { transform: 'translateX(0px) translateY(0px)' },
+ *       to: { transform:   'translateX(100px) translateY(100px)' },
+ *       easing: { transform: 'easeInQuad' },
+ *       step: function (state) {
+ *         console.log(state.transform);
+ *       }
+ *     });
+ *
+ * The above snippet will create values like this:
+ *
+ *     translateX(11.56px) translateY(11.56px)
+ *     ...
+ *     translateX(46.24px) translateY(46.24px)
+ *     ...
+ *     translateX(100px) translateY(100px)
+ *
+ * In this case, the values for `translateX` and `translateY` are always the
+ * same for each step of the tween, because they have the same start and end
+ * points and both use the same easing curve.  We can also tween `translateX`
+ * and `translateY` along independent curves:
+ *
+ *     var tweenable = new NGTweenable();
+ *     tweenable.tween({
+ *       from: { transform: 'translateX(0px) translateY(0px)' },
+ *       to: { transform:   'translateX(100px) translateY(100px)' },
+ *       easing: { transform: 'easeInQuad bounce' },
+ *       step: function (state) {
+ *         console.log(state.transform);
+ *       }
+ *     });
+ *
+ * The above snippet will create values like this:
+ *
+ *     translateX(10.89px) translateY(82.35px)
+ *     ...
+ *     translateX(44.89px) translateY(86.73px)
+ *     ...
+ *     translateX(100px) translateY(100px)
+ *
+ * `translateX` and `translateY` are not in sync anymore, because `easeInQuad`
+ * was specified for `translateX` and `bounce` for `translateY`.  Mixing and
+ * matching easing curves can make for some interesting motion in your
+ * animations.
+ *
+ * The order of the space-separated easing curves correspond the token values
+ * they apply to.  If there are more token values than easing curves listed,
+ * the last easing curve listed is used.
+ * @submodule NGTweenable.token
+ */
+
+// token function is defined above only so that dox-foundation sees it as
+// documentation and renders it.  It is never used, and is optimized away at
+// build time.
+
+;(function (NGTweenable) {
+
+  /**
+   * @typedef {{
+   *   formatString: string
+   *   chunkNames: Array.<string>
+   * }}
+   * @private
+   */
+  var formatManifest;
+
+  // CONSTANTS
+
+  var R_NUMBER_COMPONENT = /(\d|\-|\.)/;
+  var R_FORMAT_CHUNKS = /([^\-0-9\.]+)/g;
+  var R_UNFORMATTED_VALUES = /[0-9.\-]+/g;
+  var R_RGB = new RegExp(
+    'rgb\\(' + R_UNFORMATTED_VALUES.source +
+    (/,\s*/.source) + R_UNFORMATTED_VALUES.source +
+    (/,\s*/.source) + R_UNFORMATTED_VALUES.source + '\\)', 'g');
+  var R_RGB_PREFIX = /^.*\(/;
+  var R_HEX = /#([0-9]|[a-f]){3,6}/gi;
+  var VALUE_PLACEHOLDER = 'VAL';
+
+  // HELPERS
+
+  /**
+   * @param {Array.number} rawValues
+   * @param {string} prefix
+   *
+   * @return {Array.<string>}
+   * @private
+   */
+  function getFormatChunksFrom (rawValues, prefix) {
+    var accumulator = [];
+
+    var rawValuesLength = rawValues.length;
+    var i;
+
+    for (i = 0; i < rawValuesLength; i++) {
+      accumulator.push('_' + prefix + '_' + i);
+    }
+
+    return accumulator;
+  }
+
+  /**
+   * @param {string} formattedString
+   *
+   * @return {string}
+   * @private
+   */
+  function getFormatStringFrom (formattedString) {
+    var chunks = formattedString.match(R_FORMAT_CHUNKS);
+
+    if (!chunks) {
+      // chunks will be null if there were no tokens to parse in
+      // formattedString (for example, if formattedString is '2').  Coerce
+      // chunks to be useful here.
+      chunks = ['', ''];
+
+      // If there is only one chunk, assume that the string is a number
+      // followed by a token...
+      // NOTE: This may be an unwise assumption.
+    } else if (chunks.length === 1 ||
+      // ...or if the string starts with a number component (".", "-", or a
+      // digit)...
+    formattedString.charAt(0).match(R_NUMBER_COMPONENT)) {
+      // ...prepend an empty string here to make sure that the formatted number
+      // is properly replaced by VALUE_PLACEHOLDER
+      chunks.unshift('');
+    }
+
+    return chunks.join(VALUE_PLACEHOLDER);
+  }
+
+  /**
+   * Convert all hex color values within a string to an rgb string.
+   *
+   * @param {Object} stateObject
+   *
+   * @return {Object} The modified obj
+   * @private
+   */
+  function sanitizeObjectForHexProps (stateObject) {
+    NGTweenable.each(stateObject, function (prop) {
+      var currentProp = stateObject[prop];
+
+      if (typeof currentProp === 'string' && currentProp.match(R_HEX)) {
+        stateObject[prop] = sanitizeHexChunksToRGB(currentProp);
+      }
+    });
+  }
+
+  /**
+   * @param {string} str
+   *
+   * @return {string}
+   * @private
+   */
+  function  sanitizeHexChunksToRGB (str) {
+    return filterStringChunks(R_HEX, str, convertHexToRGB);
+  }
+
+  /**
+   * @param {string} hexString
+   *
+   * @return {string}
+   * @private
+   */
+  function convertHexToRGB (hexString) {
+    var rgbArr = hexToRGBArray(hexString);
+    return 'rgb(' + rgbArr[0] + ',' + rgbArr[1] + ',' + rgbArr[2] + ')';
+  }
+
+  var hexToRGBArray_returnArray = [];
+  /**
+   * Convert a hexadecimal string to an array with three items, one each for
+   * the red, blue, and green decimal values.
+   *
+   * @param {string} hex A hexadecimal string.
+   *
+   * @returns {Array.<number>} The converted Array of RGB values if `hex` is a
+   * valid string, or an Array of three 0's.
+   * @private
+   */
+  function hexToRGBArray (hex) {
+
+    hex = hex.replace(/#/, '');
+
+    // If the string is a shorthand three digit hex notation, normalize it to
+    // the standard six digit notation
+    if (hex.length === 3) {
+      hex = hex.split('');
+      hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
+    }
+
+    hexToRGBArray_returnArray[0] = hexToDec(hex.substr(0, 2));
+    hexToRGBArray_returnArray[1] = hexToDec(hex.substr(2, 2));
+    hexToRGBArray_returnArray[2] = hexToDec(hex.substr(4, 2));
+
+    return hexToRGBArray_returnArray;
+  }
+
+  /**
+   * Convert a base-16 number to base-10.
+   *
+   * @param {Number|String} hex The value to convert
+   *
+   * @returns {Number} The base-10 equivalent of `hex`.
+   * @private
+   */
+  function hexToDec (hex) {
+    return parseInt(hex, 16);
+  }
+
+  /**
+   * Runs a filter operation on all chunks of a string that match a RegExp
+   *
+   * @param {RegExp} pattern
+   * @param {string} unfilteredString
+   * @param {function(string)} filter
+   *
+   * @return {string}
+   * @private
+   */
+  function filterStringChunks (pattern, unfilteredString, filter) {
+    var pattenMatches = unfilteredString.match(pattern);
+    var filteredString = unfilteredString.replace(pattern, VALUE_PLACEHOLDER);
+
+    if (pattenMatches) {
+      var pattenMatchesLength = pattenMatches.length;
+      var currentChunk;
+
+      for (var i = 0; i < pattenMatchesLength; i++) {
+        currentChunk = pattenMatches.shift();
+        filteredString = filteredString.replace(
+          VALUE_PLACEHOLDER, filter(currentChunk));
+      }
+    }
+
+    return filteredString;
+  }
+
+  /**
+   * Check for floating point values within rgb strings and rounds them.
+   *
+   * @param {string} formattedString
+   *
+   * @return {string}
+   * @private
+   */
+  function sanitizeRGBChunks (formattedString) {
+    return filterStringChunks(R_RGB, formattedString, sanitizeRGBChunk);
+  }
+
+  /**
+   * @param {string} rgbChunk
+   *
+   * @return {string}
+   * @private
+   */
+  function sanitizeRGBChunk (rgbChunk) {
+    var numbers = rgbChunk.match(R_UNFORMATTED_VALUES);
+    var numbersLength = numbers.length;
+    var sanitizedString = rgbChunk.match(R_RGB_PREFIX)[0];
+
+    for (var i = 0; i < numbersLength; i++) {
+      sanitizedString += parseInt(numbers[i], 10) + ',';
+    }
+
+    sanitizedString = sanitizedString.slice(0, -1) + ')';
+
+    return sanitizedString;
+  }
+
+  /**
+   * @param {Object} stateObject
+   *
+   * @return {Object} An Object of formatManifests that correspond to
+   * the string properties of stateObject
+   * @private
+   */
+  function getFormatManifests (stateObject) {
+    var manifestAccumulator = {};
+
+    NGTweenable.each(stateObject, function (prop) {
+      var currentProp = stateObject[prop];
+
+      if (typeof currentProp === 'string') {
+        var rawValues = getValuesFrom(currentProp);
+
+        manifestAccumulator[prop] = {
+          'formatString': getFormatStringFrom(currentProp)
+          ,'chunkNames': getFormatChunksFrom(rawValues, prop)
+        };
+      }
+    });
+
+    return manifestAccumulator;
+  }
+
+  /**
+   * @param {Object} stateObject
+   * @param {Object} formatManifests
+   * @private
+   */
+  function expandFormattedProperties (stateObject, formatManifests) {
+    NGTweenable.each(formatManifests, function (prop) {
+      var currentProp = stateObject[prop];
+      var rawValues = getValuesFrom(currentProp);
+      var rawValuesLength = rawValues.length;
+
+      for (var i = 0; i < rawValuesLength; i++) {
+        stateObject[formatManifests[prop].chunkNames[i]] = +rawValues[i];
+      }
+
+      delete stateObject[prop];
+    });
+  }
+
+  /**
+   * @param {Object} stateObject
+   * @param {Object} formatManifests
+   * @private
+   */
+  function collapseFormattedProperties (stateObject, formatManifests) {
+    NGTweenable.each(formatManifests, function (prop) {
+      var currentProp = stateObject[prop];
+      var formatChunks = extractPropertyChunks(
+        stateObject, formatManifests[prop].chunkNames);
+      var valuesList = getValuesList(
+        formatChunks, formatManifests[prop].chunkNames);
+      currentProp = getFormattedValues(
+        formatManifests[prop].formatString, valuesList);
+      stateObject[prop] = sanitizeRGBChunks(currentProp);
+    });
+  }
+
+  /**
+   * @param {Object} stateObject
+   * @param {Array.<string>} chunkNames
+   *
+   * @return {Object} The extracted value chunks.
+   * @private
+   */
+  function extractPropertyChunks (stateObject, chunkNames) {
+    var extractedValues = {};
+    var currentChunkName, chunkNamesLength = chunkNames.length;
+
+    for (var i = 0; i < chunkNamesLength; i++) {
+      currentChunkName = chunkNames[i];
+      extractedValues[currentChunkName] = stateObject[currentChunkName];
+      delete stateObject[currentChunkName];
+    }
+
+    return extractedValues;
+  }
+
+  var getValuesList_accumulator = [];
+  /**
+   * @param {Object} stateObject
+   * @param {Array.<string>} chunkNames
+   *
+   * @return {Array.<number>}
+   * @private
+   */
+  function getValuesList (stateObject, chunkNames) {
+    getValuesList_accumulator.length = 0;
+    var chunkNamesLength = chunkNames.length;
+
+    for (var i = 0; i < chunkNamesLength; i++) {
+      getValuesList_accumulator.push(stateObject[chunkNames[i]]);
+    }
+
+    return getValuesList_accumulator;
+  }
+
+  /**
+   * @param {string} formatString
+   * @param {Array.<number>} rawValues
+   *
+   * @return {string}
+   * @private
+   */
+  function getFormattedValues (formatString, rawValues) {
+    var formattedValueString = formatString;
+    var rawValuesLength = rawValues.length;
+
+    for (var i = 0; i < rawValuesLength; i++) {
+      formattedValueString = formattedValueString.replace(
+        VALUE_PLACEHOLDER, +rawValues[i].toFixed(4));
+    }
+
+    return formattedValueString;
+  }
+
+  /**
+   * Note: It's the duty of the caller to convert the Array elements of the
+   * return value into numbers.  This is a performance optimization.
+   *
+   * @param {string} formattedString
+   *
+   * @return {Array.<string>|null}
+   * @private
+   */
+  function getValuesFrom (formattedString) {
+    return formattedString.match(R_UNFORMATTED_VALUES);
+  }
+
+  /**
+   * @param {Object} easingObject
+   * @param {Object} tokenData
+   * @private
+   */
+  function expandEasingObject (easingObject, tokenData) {
+    NGTweenable.each(tokenData, function (prop) {
+      var currentProp = tokenData[prop];
+      var chunkNames = currentProp.chunkNames;
+      var chunkLength = chunkNames.length;
+
+      var easing = easingObject[prop];
+      var i;
+
+      if (typeof easing === 'string') {
+        var easingChunks = easing.split(' ');
+        var lastEasingChunk = easingChunks[easingChunks.length - 1];
+
+        for (i = 0; i < chunkLength; i++) {
+          easingObject[chunkNames[i]] = easingChunks[i] || lastEasingChunk;
+        }
+
+      } else {
+        for (i = 0; i < chunkLength; i++) {
+          easingObject[chunkNames[i]] = easing;
+        }
+      }
+
+      delete easingObject[prop];
+    });
+  }
+
+  /**
+   * @param {Object} easingObject
+   * @param {Object} tokenData
+   * @private
+   */
+  function collapseEasingObject (easingObject, tokenData) {
+    NGTweenable.each(tokenData, function (prop) {
+      var currentProp = tokenData[prop];
+      var chunkNames = currentProp.chunkNames;
+      var chunkLength = chunkNames.length;
+
+      var firstEasing = easingObject[chunkNames[0]];
+      var typeofEasings = typeof firstEasing;
+
+      if (typeofEasings === 'string') {
+        var composedEasingString = '';
+
+        for (var i = 0; i < chunkLength; i++) {
+          composedEasingString += ' ' + easingObject[chunkNames[i]];
+          delete easingObject[chunkNames[i]];
+        }
+
+        easingObject[prop] = composedEasingString.substr(1);
+      } else {
+        easingObject[prop] = firstEasing;
+      }
+    });
+  }
+
+  NGTweenable.prototype.filter.token = {
+    'tweenCreated': function (currentState, fromState, toState, easingObject) {
+      sanitizeObjectForHexProps(currentState);
+      sanitizeObjectForHexProps(fromState);
+      sanitizeObjectForHexProps(toState);
+      this._tokenData = getFormatManifests(currentState);
+    },
+
+    'beforeTween': function (currentState, fromState, toState, easingObject) {
+      expandEasingObject(easingObject, this._tokenData);
+      expandFormattedProperties(currentState, this._tokenData);
+      expandFormattedProperties(fromState, this._tokenData);
+      expandFormattedProperties(toState, this._tokenData);
+    },
+
+    'afterTween': function (currentState, fromState, toState, easingObject) {
+      collapseFormattedProperties(currentState, this._tokenData);
+      collapseFormattedProperties(fromState, this._tokenData);
+      collapseFormattedProperties(toState, this._tokenData);
+      collapseEasingObject(easingObject, this._tokenData);
+    }
+  };
+
+} (NGTweenable));
+
+}).call(null);
+
+
+
+
+//##########################################################################################################################
+//## HAMMER.JS #############################################################################################################
+//##########################################################################################################################
+
+// HAMMER.JS
+
+// external module EMBEDED in nanogallery
+// NGY BUILD:
+// replace "Hammer" with "NGHammer" (case sensitive)
+// replace "var SUPPORT_POINTER_EVENTS = prefixed(window, 'PointerEvent') !== undefined;" with "var SUPPORT_POINTER_EVENTS = false;"
+// replace "define.amd" with "define.amdDISABLED"
+
+
+
+/*! NGHammer.JS - v2.0.7 - 2016-04-22
+ * http://hammerjs.github.io/
+ *
+ * Copyright (c) 2016 Jorik Tangelder;
+ * Licensed under the MIT license */
+(function(window, document, exportName, undefined) {
+  'use strict';
+
+var VENDOR_PREFIXES = ['', 'webkit', 'Moz', 'MS', 'ms', 'o'];
+var TEST_ELEMENT = document.createElement('div');
+
+var TYPE_FUNCTION = 'function';
+
+var round = Math.round;
+var abs = Math.abs;
+var now = Date.now;
+
+/**
+ * set a timeout with a given scope
+ * @param {Function} fn
+ * @param {Number} timeout
+ * @param {Object} context
+ * @returns {number}
+ */
+function setTimeoutContext(fn, timeout, context) {
+    return setTimeout(bindFn(fn, context), timeout);
+}
+
+/**
+ * if the argument is an array, we want to execute the fn on each entry
+ * if it aint an array we don't want to do a thing.
+ * this is used by all the methods that accept a single and array argument.
+ * @param {*|Array} arg
+ * @param {String} fn
+ * @param {Object} [context]
+ * @returns {Boolean}
+ */
+function invokeArrayArg(arg, fn, context) {
+    if (Array.isArray(arg)) {
+        each(arg, context[fn], context);
+        return true;
+    }
+    return false;
+}
+
+/**
+ * walk objects and arrays
+ * @param {Object} obj
+ * @param {Function} iterator
+ * @param {Object} context
+ */
+function each(obj, iterator, context) {
+    var i;
+
+    if (!obj) {
+        return;
+    }
+
+    if (obj.forEach) {
+        obj.forEach(iterator, context);
+    } else if (obj.length !== undefined) {
+        i = 0;
+        while (i < obj.length) {
+            iterator.call(context, obj[i], i, obj);
+            i++;
+        }
+    } else {
+        for (i in obj) {
+            obj.hasOwnProperty(i) && iterator.call(context, obj[i], i, obj);
+        }
+    }
+}
+
+/**
+ * wrap a method with a deprecation warning and stack trace
+ * @param {Function} method
+ * @param {String} name
+ * @param {String} message
+ * @returns {Function} A new function wrapping the supplied method.
+ */
+function deprecate(method, name, message) {
+    var deprecationMessage = 'DEPRECATED METHOD: ' + name + '\n' + message + ' AT \n';
+    return function() {
+        var e = new Error('get-stack-trace');
+        var stack = e && e.stack ? e.stack.replace(/^[^\(]+?[\n$]/gm, '')
+            .replace(/^\s+at\s+/gm, '')
+            .replace(/^Object.<anonymous>\s*\(/gm, '{anonymous}()@') : 'Unknown Stack Trace';
+
+        var log = window.console && (window.console.warn || window.console.log);
+        if (log) {
+            log.call(window.console, deprecationMessage, stack);
+        }
+        return method.apply(this, arguments);
+    };
+}
+
+/**
+ * extend object.
+ * means that properties in dest will be overwritten by the ones in src.
+ * @param {Object} target
+ * @param {...Object} objects_to_assign
+ * @returns {Object} target
+ */
+var assign;
+if (typeof Object.assign !== 'function') {
+    assign = function assign(target) {
+        if (target === undefined || target === null) {
+            throw new TypeError('Cannot convert undefined or null to object');
+        }
+
+        var output = Object(target);
+        for (var index = 1; index < arguments.length; index++) {
+            var source = arguments[index];
+            if (source !== undefined && source !== null) {
+                for (var nextKey in source) {
+                    if (source.hasOwnProperty(nextKey)) {
+                        output[nextKey] = source[nextKey];
+                    }
+                }
+            }
+        }
+        return output;
+    };
+} else {
+    assign = Object.assign;
+}
+
+/**
+ * extend object.
+ * means that properties in dest will be overwritten by the ones in src.
+ * @param {Object} dest
+ * @param {Object} src
+ * @param {Boolean} [merge=false]
+ * @returns {Object} dest
+ */
+var extend = deprecate(function extend(dest, src, merge) {
+    var keys = Object.keys(src);
+    var i = 0;
+    while (i < keys.length) {
+        if (!merge || (merge && dest[keys[i]] === undefined)) {
+            dest[keys[i]] = src[keys[i]];
+        }
+        i++;
+    }
+    return dest;
+}, 'extend', 'Use `assign`.');
+
+/**
+ * merge the values from src in the dest.
+ * means that properties that exist in dest will not be overwritten by src
+ * @param {Object} dest
+ * @param {Object} src
+ * @returns {Object} dest
+ */
+var merge = deprecate(function merge(dest, src) {
+    return extend(dest, src, true);
+}, 'merge', 'Use `assign`.');
+
+/**
+ * simple class inheritance
+ * @param {Function} child
+ * @param {Function} base
+ * @param {Object} [properties]
+ */
+function inherit(child, base, properties) {
+    var baseP = base.prototype,
+        childP;
+
+    childP = child.prototype = Object.create(baseP);
+    childP.constructor = child;
+    childP._super = baseP;
+
+    if (properties) {
+        assign(childP, properties);
+    }
+}
+
+/**
+ * simple function bind
+ * @param {Function} fn
+ * @param {Object} context
+ * @returns {Function}
+ */
+function bindFn(fn, context) {
+    return function boundFn() {
+        return fn.apply(context, arguments);
+    };
+}
+
+/**
+ * let a boolean value also be a function that must return a boolean
+ * this first item in args will be used as the context
+ * @param {Boolean|Function} val
+ * @param {Array} [args]
+ * @returns {Boolean}
+ */
+function boolOrFn(val, args) {
+    if (typeof val == TYPE_FUNCTION) {
+        return val.apply(args ? args[0] || undefined : undefined, args);
+    }
+    return val;
+}
+
+/**
+ * use the val2 when val1 is undefined
+ * @param {*} val1
+ * @param {*} val2
+ * @returns {*}
+ */
+function ifUndefined(val1, val2) {
+    return (val1 === undefined) ? val2 : val1;
+}
+
+/**
+ * addEventListener with multiple events at once
+ * @param {EventTarget} target
+ * @param {String} types
+ * @param {Function} handler
+ */
+function addEventListeners(target, types, handler) {
+    each(splitStr(types), function(type) {
+        target.addEventListener(type, handler, false);
+    });
+}
+
+/**
+ * removeEventListener with multiple events at once
+ * @param {EventTarget} target
+ * @param {String} types
+ * @param {Function} handler
+ */
+function removeEventListeners(target, types, handler) {
+    each(splitStr(types), function(type) {
+        target.removeEventListener(type, handler, false);
+    });
+}
+
+/**
+ * find if a node is in the given parent
+ * @method hasParent
+ * @param {HTMLElement} node
+ * @param {HTMLElement} parent
+ * @return {Boolean} found
+ */
+function hasParent(node, parent) {
+    while (node) {
+        if (node == parent) {
+            return true;
+        }
+        node = node.parentNode;
+    }
+    return false;
+}
+
+/**
+ * small indexOf wrapper
+ * @param {String} str
+ * @param {String} find
+ * @returns {Boolean} found
+ */
+function inStr(str, find) {
+    return str.indexOf(find) > -1;
+}
+
+/**
+ * split string on whitespace
+ * @param {String} str
+ * @returns {Array} words
+ */
+function splitStr(str) {
+    return str.trim().split(/\s+/g);
+}
+
+/**
+ * find if a array contains the object using indexOf or a simple polyFill
+ * @param {Array} src
+ * @param {String} find
+ * @param {String} [findByKey]
+ * @return {Boolean|Number} false when not found, or the index
+ */
+function inArray(src, find, findByKey) {
+    if (src.indexOf && !findByKey) {
+        return src.indexOf(find);
+    } else {
+        var i = 0;
+        while (i < src.length) {
+            if ((findByKey && src[i][findByKey] == find) || (!findByKey && src[i] === find)) {
+                return i;
+            }
+            i++;
+        }
+        return -1;
+    }
+}
+
+/**
+ * convert array-like objects to real arrays
+ * @param {Object} obj
+ * @returns {Array}
+ */
+function toArray(obj) {
+    return Array.prototype.slice.call(obj, 0);
+}
+
+/**
+ * unique array with objects based on a key (like 'id') or just by the array's value
+ * @param {Array} src [{id:1},{id:2},{id:1}]
+ * @param {String} [key]
+ * @param {Boolean} [sort=False]
+ * @returns {Array} [{id:1},{id:2}]
+ */
+function uniqueArray(src, key, sort) {
+    var results = [];
+    var values = [];
+    var i = 0;
+
+    while (i < src.length) {
+        var val = key ? src[i][key] : src[i];
+        if (inArray(values, val) < 0) {
+            results.push(src[i]);
+        }
+        values[i] = val;
+        i++;
+    }
+
+    if (sort) {
+        if (!key) {
+            results = results.sort();
+        } else {
+            results = results.sort(function sortUniqueArray(a, b) {
+                return a[key] > b[key];
+            });
+        }
+    }
+
+    return results;
+}
+
+/**
+ * get the prefixed property
+ * @param {Object} obj
+ * @param {String} property
+ * @returns {String|Undefined} prefixed
+ */
+function prefixed(obj, property) {
+    var prefix, prop;
+    var camelProp = property[0].toUpperCase() + property.slice(1);
+
+    var i = 0;
+    while (i < VENDOR_PREFIXES.length) {
+        prefix = VENDOR_PREFIXES[i];
+        prop = (prefix) ? prefix + camelProp : property;
+
+        if (prop in obj) {
+            return prop;
+        }
+        i++;
+    }
+    return undefined;
+}
+
+/**
+ * get a unique id
+ * @returns {number} uniqueId
+ */
+var _uniqueId = 1;
+function uniqueId() {
+    return _uniqueId++;
+}
+
+/**
+ * get the window object of an element
+ * @param {HTMLElement} element
+ * @returns {DocumentView|Window}
+ */
+function getWindowForElement(element) {
+    var doc = element.ownerDocument || element;
+    return (doc.defaultView || doc.parentWindow || window);
+}
+
+var MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android/i;
+
+var SUPPORT_TOUCH = ('ontouchstart' in window);
+// var SUPPORT_POINTER_EVENTS = prefixed(window, 'PointerEvent') !== undefined;
+var SUPPORT_POINTER_EVENTS = false;
+var SUPPORT_ONLY_TOUCH = SUPPORT_TOUCH && MOBILE_REGEX.test(navigator.userAgent);
+
+var INPUT_TYPE_TOUCH = 'touch';
+var INPUT_TYPE_PEN = 'pen';
+var INPUT_TYPE_MOUSE = 'mouse';
+var INPUT_TYPE_KINECT = 'kinect';
+
+var COMPUTE_INTERVAL = 25;
+
+var INPUT_START = 1;
+var INPUT_MOVE = 2;
+var INPUT_END = 4;
+var INPUT_CANCEL = 8;
+
+var DIRECTION_NONE = 1;
+var DIRECTION_LEFT = 2;
+var DIRECTION_RIGHT = 4;
+var DIRECTION_UP = 8;
+var DIRECTION_DOWN = 16;
+
+var DIRECTION_HORIZONTAL = DIRECTION_LEFT | DIRECTION_RIGHT;
+var DIRECTION_VERTICAL = DIRECTION_UP | DIRECTION_DOWN;
+var DIRECTION_ALL = DIRECTION_HORIZONTAL | DIRECTION_VERTICAL;
+
+var PROPS_XY = ['x', 'y'];
+var PROPS_CLIENT_XY = ['clientX', 'clientY'];
+
+/**
+ * create new input type manager
+ * @param {Manager} manager
+ * @param {Function} callback
+ * @returns {Input}
+ * @constructor
+ */
+function Input(manager, callback) {
+    var self = this;
+    this.manager = manager;
+    this.callback = callback;
+    this.element = manager.element;
+    this.target = manager.options.inputTarget;
+
+    // smaller wrapper around the handler, for the scope and the enabled state of the manager,
+    // so when disabled the input events are completely bypassed.
+    this.domHandler = function(ev) {
+        if (boolOrFn(manager.options.enable, [manager])) {
+            self.handler(ev);
+        }
+    };
+
+    this.init();
+
+}
+
+Input.prototype = {
+    /**
+     * should handle the inputEvent data and trigger the callback
+     * @virtual
+     */
+    handler: function() { },
+
+    /**
+     * bind the events
+     */
+    init: function() {
+        this.evEl && addEventListeners(this.element, this.evEl, this.domHandler);
+        this.evTarget && addEventListeners(this.target, this.evTarget, this.domHandler);
+        this.evWin && addEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler);
+    },
+
+    /**
+     * unbind the events
+     */
+    destroy: function() {
+        this.evEl && removeEventListeners(this.element, this.evEl, this.domHandler);
+        this.evTarget && removeEventListeners(this.target, this.evTarget, this.domHandler);
+        this.evWin && removeEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler);
+    }
+};
+
+/**
+ * create new input type manager
+ * called by the Manager constructor
+ * @param {NGHammer} manager
+ * @returns {Input}
+ */
+function createInputInstance(manager) {
+    var Type;
+    var inputClass = manager.options.inputClass;
+
+    if (inputClass) {
+        Type = inputClass;
+    } else if (SUPPORT_POINTER_EVENTS) {
+        Type = PointerEventInput;
+    } else if (SUPPORT_ONLY_TOUCH) {
+        Type = TouchInput;
+    } else if (!SUPPORT_TOUCH) {
+        Type = MouseInput;
+    } else {
+        Type = TouchMouseInput;
+    }
+    return new (Type)(manager, inputHandler);
+}
+
+/**
+ * handle input events
+ * @param {Manager} manager
+ * @param {String} eventType
+ * @param {Object} input
+ */
+function inputHandler(manager, eventType, input) {
+    var pointersLen = input.pointers.length;
+    var changedPointersLen = input.changedPointers.length;
+    var isFirst = (eventType & INPUT_START && (pointersLen - changedPointersLen === 0));
+    var isFinal = (eventType & (INPUT_END | INPUT_CANCEL) && (pointersLen - changedPointersLen === 0));
+
+    input.isFirst = !!isFirst;
+    input.isFinal = !!isFinal;
+
+    if (isFirst) {
+        manager.session = {};
+    }
+
+    // source event is the normalized value of the domEvents
+    // like 'touchstart, mouseup, pointerdown'
+    input.eventType = eventType;
+
+    // compute scale, rotation etc
+    computeInputData(manager, input);
+
+    // emit secret event
+    manager.emit('hammer.input', input);
+
+    manager.recognize(input);
+    manager.session.prevInput = input;
+}
+
+/**
+ * extend the data with some usable properties like scale, rotate, velocity etc
+ * @param {Object} manager
+ * @param {Object} input
+ */
+function computeInputData(manager, input) {
+    var session = manager.session;
+    var pointers = input.pointers;
+    var pointersLength = pointers.length;
+
+    // store the first input to calculate the distance and direction
+    if (!session.firstInput) {
+        session.firstInput = simpleCloneInputData(input);
+    }
+
+    // to compute scale and rotation we need to store the multiple touches
+    if (pointersLength > 1 && !session.firstMultiple) {
+        session.firstMultiple = simpleCloneInputData(input);
+    } else if (pointersLength === 1) {
+        session.firstMultiple = false;
+    }
+
+    var firstInput = session.firstInput;
+    var firstMultiple = session.firstMultiple;
+    var offsetCenter = firstMultiple ? firstMultiple.center : firstInput.center;
+
+    var center = input.center = getCenter(pointers);
+    input.timeStamp = now();
+    input.deltaTime = input.timeStamp - firstInput.timeStamp;
+
+    input.angle = getAngle(offsetCenter, center);
+    input.distance = getDistance(offsetCenter, center);
+
+    computeDeltaXY(session, input);
+    input.offsetDirection = getDirection(input.deltaX, input.deltaY);
+
+    var overallVelocity = getVelocity(input.deltaTime, input.deltaX, input.deltaY);
+    input.overallVelocityX = overallVelocity.x;
+    input.overallVelocityY = overallVelocity.y;
+    input.overallVelocity = (abs(overallVelocity.x) > abs(overallVelocity.y)) ? overallVelocity.x : overallVelocity.y;
+
+    input.scale = firstMultiple ? getScale(firstMultiple.pointers, pointers) : 1;
+    input.rotation = firstMultiple ? getRotation(firstMultiple.pointers, pointers) : 0;
+
+    input.maxPointers = !session.prevInput ? input.pointers.length : ((input.pointers.length >
+        session.prevInput.maxPointers) ? input.pointers.length : session.prevInput.maxPointers);
+
+    computeIntervalInputData(session, input);
+
+    // find the correct target
+    var target = manager.element;
+    if (hasParent(input.srcEvent.target, target)) {
+        target = input.srcEvent.target;
+    }
+    input.target = target;
+}
+
+function computeDeltaXY(session, input) {
+    var center = input.center;
+    var offset = session.offsetDelta || {};
+    var prevDelta = session.prevDelta || {};
+    var prevInput = session.prevInput || {};
+
+    if (input.eventType === INPUT_START || prevInput.eventType === INPUT_END) {
+        prevDelta = session.prevDelta = {
+            x: prevInput.deltaX || 0,
+            y: prevInput.deltaY || 0
+        };
+
+        offset = session.offsetDelta = {
+            x: center.x,
+            y: center.y
+        };
+    }
+
+    input.deltaX = prevDelta.x + (center.x - offset.x);
+    input.deltaY = prevDelta.y + (center.y - offset.y);
+}
+
+/**
+ * velocity is calculated every x ms
+ * @param {Object} session
+ * @param {Object} input
+ */
+function computeIntervalInputData(session, input) {
+    var last = session.lastInterval || input,
+        deltaTime = input.timeStamp - last.timeStamp,
+        velocity, velocityX, velocityY, direction;
+
+    if (input.eventType != INPUT_CANCEL && (deltaTime > COMPUTE_INTERVAL || last.velocity === undefined)) {
+        var deltaX = input.deltaX - last.deltaX;
+        var deltaY = input.deltaY - last.deltaY;
+
+        var v = getVelocity(deltaTime, deltaX, deltaY);
+        velocityX = v.x;
+        velocityY = v.y;
+        velocity = (abs(v.x) > abs(v.y)) ? v.x : v.y;
+        direction = getDirection(deltaX, deltaY);
+
+        session.lastInterval = input;
+    } else {
+        // use latest velocity info if it doesn't overtake a minimum period
+        velocity = last.velocity;
+        velocityX = last.velocityX;
+        velocityY = last.velocityY;
+        direction = last.direction;
+    }
+
+    input.velocity = velocity;
+    input.velocityX = velocityX;
+    input.velocityY = velocityY;
+    input.direction = direction;
+}
+
+/**
+ * create a simple clone from the input used for storage of firstInput and firstMultiple
+ * @param {Object} input
+ * @returns {Object} clonedInputData
+ */
+function simpleCloneInputData(input) {
+    // make a simple copy of the pointers because we will get a reference if we don't
+    // we only need clientXY for the calculations
+    var pointers = [];
+    var i = 0;
+    while (i < input.pointers.length) {
+        pointers[i] = {
+            clientX: round(input.pointers[i].clientX),
+            clientY: round(input.pointers[i].clientY)
+        };
+        i++;
+    }
+
+    return {
+        timeStamp: now(),
+        pointers: pointers,
+        center: getCenter(pointers),
+        deltaX: input.deltaX,
+        deltaY: input.deltaY
+    };
+}
+
+/**
+ * get the center of all the pointers
+ * @param {Array} pointers
+ * @return {Object} center contains `x` and `y` properties
+ */
+function getCenter(pointers) {
+    var pointersLength = pointers.length;
+
+    // no need to loop when only one touch
+    if (pointersLength === 1) {
+        return {
+            x: round(pointers[0].clientX),
+            y: round(pointers[0].clientY)
+        };
+    }
+
+    var x = 0, y = 0, i = 0;
+    while (i < pointersLength) {
+        x += pointers[i].clientX;
+        y += pointers[i].clientY;
+        i++;
+    }
+
+    return {
+        x: round(x / pointersLength),
+        y: round(y / pointersLength)
+    };
+}
+
+/**
+ * calculate the velocity between two points. unit is in px per ms.
+ * @param {Number} deltaTime
+ * @param {Number} x
+ * @param {Number} y
+ * @return {Object} velocity `x` and `y`
+ */
+function getVelocity(deltaTime, x, y) {
+    return {
+        x: x / deltaTime || 0,
+        y: y / deltaTime || 0
+    };
+}
+
+/**
+ * get the direction between two points
+ * @param {Number} x
+ * @param {Number} y
+ * @return {Number} direction
+ */
+function getDirection(x, y) {
+    if (x === y) {
+        return DIRECTION_NONE;
+    }
+
+    if (abs(x) >= abs(y)) {
+        return x < 0 ? DIRECTION_LEFT : DIRECTION_RIGHT;
+    }
+    return y < 0 ? DIRECTION_UP : DIRECTION_DOWN;
+}
+
+/**
+ * calculate the absolute distance between two points
+ * @param {Object} p1 {x, y}
+ * @param {Object} p2 {x, y}
+ * @param {Array} [props] containing x and y keys
+ * @return {Number} distance
+ */
+function getDistance(p1, p2, props) {
+    if (!props) {
+        props = PROPS_XY;
+    }
+    var x = p2[props[0]] - p1[props[0]],
+        y = p2[props[1]] - p1[props[1]];
+
+    return Math.sqrt((x * x) + (y * y));
+}
+
+/**
+ * calculate the angle between two coordinates
+ * @param {Object} p1
+ * @param {Object} p2
+ * @param {Array} [props] containing x and y keys
+ * @return {Number} angle
+ */
+function getAngle(p1, p2, props) {
+    if (!props) {
+        props = PROPS_XY;
+    }
+    var x = p2[props[0]] - p1[props[0]],
+        y = p2[props[1]] - p1[props[1]];
+    return Math.atan2(y, x) * 180 / Math.PI;
+}
+
+/**
+ * calculate the rotation degrees between two pointersets
+ * @param {Array} start array of pointers
+ * @param {Array} end array of pointers
+ * @return {Number} rotation
+ */
+function getRotation(start, end) {
+    return getAngle(end[1], end[0], PROPS_CLIENT_XY) + getAngle(start[1], start[0], PROPS_CLIENT_XY);
+}
+
+/**
+ * calculate the scale factor between two pointersets
+ * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
+ * @param {Array} start array of pointers
+ * @param {Array} end array of pointers
+ * @return {Number} scale
+ */
+function getScale(start, end) {
+    return getDistance(end[0], end[1], PROPS_CLIENT_XY) / getDistance(start[0], start[1], PROPS_CLIENT_XY);
+}
+
+var MOUSE_INPUT_MAP = {
+    mousedown: INPUT_START,
+    mousemove: INPUT_MOVE,
+    mouseup: INPUT_END
+};
+
+var MOUSE_ELEMENT_EVENTS = 'mousedown';
+var MOUSE_WINDOW_EVENTS = 'mousemove mouseup';
+
+/**
+ * Mouse events input
+ * @constructor
+ * @extends Input
+ */
+function MouseInput() {
+    this.evEl = MOUSE_ELEMENT_EVENTS;
+    this.evWin = MOUSE_WINDOW_EVENTS;
+
+    this.pressed = false; // mousedown state
+
+    Input.apply(this, arguments);
+}
+
+inherit(MouseInput, Input, {
+    /**
+     * handle mouse events
+     * @param {Object} ev
+     */
+    handler: function MEhandler(ev) {
+        var eventType = MOUSE_INPUT_MAP[ev.type];
+
+        // on start we want to have the left mouse button down
+        if (eventType & INPUT_START && ev.button === 0) {
+            this.pressed = true;
+        }
+
+        if (eventType & INPUT_MOVE && ev.which !== 1) {
+            eventType = INPUT_END;
+        }
+
+        // mouse must be down
+        if (!this.pressed) {
+            return;
+        }
+
+        if (eventType & INPUT_END) {
+            this.pressed = false;
+        }
+
+        this.callback(this.manager, eventType, {
+            pointers: [ev],
+            changedPointers: [ev],
+            pointerType: INPUT_TYPE_MOUSE,
+            srcEvent: ev
+        });
+    }
+});
+
+var POINTER_INPUT_MAP = {
+    pointerdown: INPUT_START,
+    pointermove: INPUT_MOVE,
+    pointerup: INPUT_END,
+    pointercancel: INPUT_CANCEL,
+    pointerout: INPUT_CANCEL
+};
+
+// in IE10 the pointer types is defined as an enum
+var IE10_POINTER_TYPE_ENUM = {
+    2: INPUT_TYPE_TOUCH,
+    3: INPUT_TYPE_PEN,
+    4: INPUT_TYPE_MOUSE,
+    5: INPUT_TYPE_KINECT // see https://twitter.com/jacobrossi/status/480596438489890816
+};
+
+var POINTER_ELEMENT_EVENTS = 'pointerdown';
+var POINTER_WINDOW_EVENTS = 'pointermove pointerup pointercancel';
+
+// IE10 has prefixed support, and case-sensitive
+if (window.MSPointerEvent && !window.PointerEvent) {
+    POINTER_ELEMENT_EVENTS = 'MSPointerDown';
+    POINTER_WINDOW_EVENTS = 'MSPointerMove MSPointerUp MSPointerCancel';
+}
+
+/**
+ * Pointer events input
+ * @constructor
+ * @extends Input
+ */
+function PointerEventInput() {
+    this.evEl = POINTER_ELEMENT_EVENTS;
+    this.evWin = POINTER_WINDOW_EVENTS;
+
+    Input.apply(this, arguments);
+
+    this.store = (this.manager.session.pointerEvents = []);
+}
+
+inherit(PointerEventInput, Input, {
+    /**
+     * handle mouse events
+     * @param {Object} ev
+     */
+    handler: function PEhandler(ev) {
+        var store = this.store;
+        var removePointer = false;
+
+        var eventTypeNormalized = ev.type.toLowerCase().replace('ms', '');
+        var eventType = POINTER_INPUT_MAP[eventTypeNormalized];
+        var pointerType = IE10_POINTER_TYPE_ENUM[ev.pointerType] || ev.pointerType;
+
+        var isTouch = (pointerType == INPUT_TYPE_TOUCH);
+
+        // get index of the event in the store
+        var storeIndex = inArray(store, ev.pointerId, 'pointerId');
+
+        // start and mouse must be down
+        if (eventType & INPUT_START && (ev.button === 0 || isTouch)) {
+            if (storeIndex < 0) {
+                store.push(ev);
+                storeIndex = store.length - 1;
+            }
+        } else if (eventType & (INPUT_END | INPUT_CANCEL)) {
+            removePointer = true;
+        }
+
+        // it not found, so the pointer hasn't been down (so it's probably a hover)
+        if (storeIndex < 0) {
+            return;
+        }
+
+        // update the event in the store
+        store[storeIndex] = ev;
+
+        this.callback(this.manager, eventType, {
+            pointers: store,
+            changedPointers: [ev],
+            pointerType: pointerType,
+            srcEvent: ev
+        });
+
+        if (removePointer) {
+            // remove from the store
+            store.splice(storeIndex, 1);
+        }
+    }
+});
+
+var SINGLE_TOUCH_INPUT_MAP = {
+    touchstart: INPUT_START,
+    touchmove: INPUT_MOVE,
+    touchend: INPUT_END,
+    touchcancel: INPUT_CANCEL
+};
+
+var SINGLE_TOUCH_TARGET_EVENTS = 'touchstart';
+var SINGLE_TOUCH_WINDOW_EVENTS = 'touchstart touchmove touchend touchcancel';
+
+/**
+ * Touch events input
+ * @constructor
+ * @extends Input
+ */
+function SingleTouchInput() {
+    this.evTarget = SINGLE_TOUCH_TARGET_EVENTS;
+    this.evWin = SINGLE_TOUCH_WINDOW_EVENTS;
+    this.started = false;
+
+    Input.apply(this, arguments);
+}
+
+inherit(SingleTouchInput, Input, {
+    handler: function TEhandler(ev) {
+        var type = SINGLE_TOUCH_INPUT_MAP[ev.type];
+
+        // should we handle the touch events?
+        if (type === INPUT_START) {
+            this.started = true;
+        }
+
+        if (!this.started) {
+            return;
+        }
+
+        var touches = normalizeSingleTouches.call(this, ev, type);
+
+        // when done, reset the started state
+        if (type & (INPUT_END | INPUT_CANCEL) && touches[0].length - touches[1].length === 0) {
+            this.started = false;
+        }
+
+        this.callback(this.manager, type, {
+            pointers: touches[0],
+            changedPointers: touches[1],
+            pointerType: INPUT_TYPE_TOUCH,
+            srcEvent: ev
+        });
+    }
+});
+
+/**
+ * @this {TouchInput}
+ * @param {Object} ev
+ * @param {Number} type flag
+ * @returns {undefined|Array} [all, changed]
+ */
+function normalizeSingleTouches(ev, type) {
+    var all = toArray(ev.touches);
+    var changed = toArray(ev.changedTouches);
+
+    if (type & (INPUT_END | INPUT_CANCEL)) {
+        all = uniqueArray(all.concat(changed), 'identifier', true);
+    }
+
+    return [all, changed];
+}
+
+var TOUCH_INPUT_MAP = {
+    touchstart: INPUT_START,
+    touchmove: INPUT_MOVE,
+    touchend: INPUT_END,
+    touchcancel: INPUT_CANCEL
+};
+
+var TOUCH_TARGET_EVENTS = 'touchstart touchmove touchend touchcancel';
+
+/**
+ * Multi-user touch events input
+ * @constructor
+ * @extends Input
+ */
+function TouchInput() {
+    this.evTarget = TOUCH_TARGET_EVENTS;
+    this.targetIds = {};
+
+    Input.apply(this, arguments);
+}
+
+inherit(TouchInput, Input, {
+    handler: function MTEhandler(ev) {
+        var type = TOUCH_INPUT_MAP[ev.type];
+        var touches = getTouches.call(this, ev, type);
+        if (!touches) {
+            return;
+        }
+
+        this.callback(this.manager, type, {
+            pointers: touches[0],
+            changedPointers: touches[1],
+            pointerType: INPUT_TYPE_TOUCH,
+            srcEvent: ev
+        });
+    }
+});
+
+/**
+ * @this {TouchInput}
+ * @param {Object} ev
+ * @param {Number} type flag
+ * @returns {undefined|Array} [all, changed]
+ */
+function getTouches(ev, type) {
+    var allTouches = toArray(ev.touches);
+    var targetIds = this.targetIds;
+
+    // when there is only one touch, the process can be simplified
+    if (type & (INPUT_START | INPUT_MOVE) && allTouches.length === 1) {
+        targetIds[allTouches[0].identifier] = true;
+        return [allTouches, allTouches];
+    }
+
+    var i,
+        targetTouches,
+        changedTouches = toArray(ev.changedTouches),
+        changedTargetTouches = [],
+        target = this.target;
+
+    // get target touches from touches
+    targetTouches = allTouches.filter(function(touch) {
+        return hasParent(touch.target, target);
+    });
+
+    // collect touches
+    if (type === INPUT_START) {
+        i = 0;
+        while (i < targetTouches.length) {
+            targetIds[targetTouches[i].identifier] = true;
+            i++;
+        }
+    }
+
+    // filter changed touches to only contain touches that exist in the collected target ids
+    i = 0;
+    while (i < changedTouches.length) {
+        if (targetIds[changedTouches[i].identifier]) {
+            changedTargetTouches.push(changedTouches[i]);
+        }
+
+        // cleanup removed touches
+        if (type & (INPUT_END | INPUT_CANCEL)) {
+            delete targetIds[changedTouches[i].identifier];
+        }
+        i++;
+    }
+
+    if (!changedTargetTouches.length) {
+        return;
+    }
+
+    return [
+        // merge targetTouches with changedTargetTouches so it contains ALL touches, including 'end' and 'cancel'
+        uniqueArray(targetTouches.concat(changedTargetTouches), 'identifier', true),
+        changedTargetTouches
+    ];
+}
+
+/**
+ * Combined touch and mouse input
+ *
+ * Touch has a higher priority then mouse, and while touching no mouse events are allowed.
+ * This because touch devices also emit mouse events while doing a touch.
+ *
+ * @constructor
+ * @extends Input
+ */
+
+var DEDUP_TIMEOUT = 2500;
+var DEDUP_DISTANCE = 25;
+
+function TouchMouseInput() {
+    Input.apply(this, arguments);
+
+    var handler = bindFn(this.handler, this);
+    this.touch = new TouchInput(this.manager, handler);
+    this.mouse = new MouseInput(this.manager, handler);
+
+    this.primaryTouch = null;
+    this.lastTouches = [];
+}
+
+inherit(TouchMouseInput, Input, {
+    /**
+     * handle mouse and touch events
+     * @param {NGHammer} manager
+     * @param {String} inputEvent
+     * @param {Object} inputData
+     */
+    handler: function TMEhandler(manager, inputEvent, inputData) {
+        var isTouch = (inputData.pointerType == INPUT_TYPE_TOUCH),
+            isMouse = (inputData.pointerType == INPUT_TYPE_MOUSE);
+
+        if (isMouse && inputData.sourceCapabilities && inputData.sourceCapabilities.firesTouchEvents) {
+            return;
+        }
+
+        // when we're in a touch event, record touches to  de-dupe synthetic mouse event
+        if (isTouch) {
+            recordTouches.call(this, inputEvent, inputData);
+        } else if (isMouse && isSyntheticEvent.call(this, inputData)) {
+            return;
+        }
+
+        this.callback(manager, inputEvent, inputData);
+    },
+
+    /**
+     * remove the event listeners
+     */
+    destroy: function destroy() {
+        this.touch.destroy();
+        this.mouse.destroy();
+    }
+});
+
+function recordTouches(eventType, eventData) {
+    if (eventType & INPUT_START) {
+        this.primaryTouch = eventData.changedPointers[0].identifier;
+        setLastTouch.call(this, eventData);
+    } else if (eventType & (INPUT_END | INPUT_CANCEL)) {
+        setLastTouch.call(this, eventData);
+    }
+}
+
+function setLastTouch(eventData) {
+    var touch = eventData.changedPointers[0];
+
+    if (touch.identifier === this.primaryTouch) {
+        var lastTouch = {x: touch.clientX, y: touch.clientY};
+        this.lastTouches.push(lastTouch);
+        var lts = this.lastTouches;
+        var removeLastTouch = function() {
+            var i = lts.indexOf(lastTouch);
+            if (i > -1) {
+                lts.splice(i, 1);
+            }
+        };
+        setTimeout(removeLastTouch, DEDUP_TIMEOUT);
+    }
+}
+
+function isSyntheticEvent(eventData) {
+    var x = eventData.srcEvent.clientX, y = eventData.srcEvent.clientY;
+    for (var i = 0; i < this.lastTouches.length; i++) {
+        var t = this.lastTouches[i];
+        var dx = Math.abs(x - t.x), dy = Math.abs(y - t.y);
+        if (dx <= DEDUP_DISTANCE && dy <= DEDUP_DISTANCE) {
+            return true;
+        }
+    }
+    return false;
+}
+
+var PREFIXED_TOUCH_ACTION = prefixed(TEST_ELEMENT.style, 'touchAction');
+var NATIVE_TOUCH_ACTION = PREFIXED_TOUCH_ACTION !== undefined;
+
+// magical touchAction value
+var TOUCH_ACTION_COMPUTE = 'compute';
+var TOUCH_ACTION_AUTO = 'auto';
+var TOUCH_ACTION_MANIPULATION = 'manipulation'; // not implemented
+var TOUCH_ACTION_NONE = 'none';
+var TOUCH_ACTION_PAN_X = 'pan-x';
+var TOUCH_ACTION_PAN_Y = 'pan-y';
+var TOUCH_ACTION_MAP = getTouchActionProps();
+
+/**
+ * Touch Action
+ * sets the touchAction property or uses the js alternative
+ * @param {Manager} manager
+ * @param {String} value
+ * @constructor
+ */
+function TouchAction(manager, value) {
+    this.manager = manager;
+    this.set(value);
+}
+
+TouchAction.prototype = {
+    /**
+     * set the touchAction value on the element or enable the polyfill
+     * @param {String} value
+     */
+    set: function(value) {
+        // find out the touch-action by the event handlers
+        if (value == TOUCH_ACTION_COMPUTE) {
+            value = this.compute();
+        }
+
+        if (NATIVE_TOUCH_ACTION && this.manager.element.style && TOUCH_ACTION_MAP[value]) {
+            this.manager.element.style[PREFIXED_TOUCH_ACTION] = value;
+        }
+        this.actions = value.toLowerCase().trim();
+    },
+
+    /**
+     * just re-set the touchAction value
+     */
+    update: function() {
+        this.set(this.manager.options.touchAction);
+    },
+
+    /**
+     * compute the value for the touchAction property based on the recognizer's settings
+     * @returns {String} value
+     */
+    compute: function() {
+        var actions = [];
+        each(this.manager.recognizers, function(recognizer) {
+            if (boolOrFn(recognizer.options.enable, [recognizer])) {
+                actions = actions.concat(recognizer.getTouchAction());
+            }
+        });
+        return cleanTouchActions(actions.join(' '));
+    },
+
+    /**
+     * this method is called on each input cycle and provides the preventing of the browser behavior
+     * @param {Object} input
+     */
+    preventDefaults: function(input) {
+        var srcEvent = input.srcEvent;
+        var direction = input.offsetDirection;
+
+        // if the touch action did prevented once this session
+        if (this.manager.session.prevented) {
+            srcEvent.preventDefault();
+            return;
+        }
+
+        var actions = this.actions;
+        var hasNone = inStr(actions, TOUCH_ACTION_NONE) && !TOUCH_ACTION_MAP[TOUCH_ACTION_NONE];
+        var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_Y];
+        var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_X];
+
+        if (hasNone) {
+            //do not prevent defaults if this is a tap gesture
+
+            var isTapPointer = input.pointers.length === 1;
+            var isTapMovement = input.distance < 2;
+            var isTapTouchTime = input.deltaTime < 250;
+
+            if (isTapPointer && isTapMovement && isTapTouchTime) {
+                return;
+            }
+        }
+
+        if (hasPanX && hasPanY) {
+            // `pan-x pan-y` means browser handles all scrolling/panning, do not prevent
+            return;
+        }
+
+        if (hasNone ||
+            (hasPanY && direction & DIRECTION_HORIZONTAL) ||
+            (hasPanX && direction & DIRECTION_VERTICAL)) {
+            return this.preventSrc(srcEvent);
+        }
+    },
+
+    /**
+     * call preventDefault to prevent the browser's default behavior (scrolling in most cases)
+     * @param {Object} srcEvent
+     */
+    preventSrc: function(srcEvent) {
+        this.manager.session.prevented = true;
+        srcEvent.preventDefault();
+    }
+};
+
+/**
+ * when the touchActions are collected they are not a valid value, so we need to clean things up. *
+ * @param {String} actions
+ * @returns {*}
+ */
+function cleanTouchActions(actions) {
+    // none
+    if (inStr(actions, TOUCH_ACTION_NONE)) {
+        return TOUCH_ACTION_NONE;
+    }
+
+    var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X);
+    var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y);
+
+    // if both pan-x and pan-y are set (different recognizers
+    // for different directions, e.g. horizontal pan but vertical swipe?)
+    // we need none (as otherwise with pan-x pan-y combined none of these
+    // recognizers will work, since the browser would handle all panning
+    if (hasPanX && hasPanY) {
+        return TOUCH_ACTION_NONE;
+    }
+
+    // pan-x OR pan-y
+    if (hasPanX || hasPanY) {
+        return hasPanX ? TOUCH_ACTION_PAN_X : TOUCH_ACTION_PAN_Y;
+    }
+
+    // manipulation
+    if (inStr(actions, TOUCH_ACTION_MANIPULATION)) {
+        return TOUCH_ACTION_MANIPULATION;
+    }
+
+    return TOUCH_ACTION_AUTO;
+}
+
+function getTouchActionProps() {
+    if (!NATIVE_TOUCH_ACTION) {
+        return false;
+    }
+    var touchMap = {};
+    var cssSupports = window.CSS && window.CSS.supports;
+    ['auto', 'manipulation', 'pan-y', 'pan-x', 'pan-x pan-y', 'none'].forEach(function(val) {
+
+        // If css.supports is not supported but there is native touch-action assume it supports
+        // all values. This is the case for IE 10 and 11.
+        touchMap[val] = cssSupports ? window.CSS.supports('touch-action', val) : true;
+    });
+    return touchMap;
+}
+
+/**
+ * Recognizer flow explained; *
+ * All recognizers have the initial state of POSSIBLE when a input session starts.
+ * The definition of a input session is from the first input until the last input, with all it's movement in it. *
+ * Example session for mouse-input: mousedown -> mousemove -> mouseup
+ *
+ * On each recognizing cycle (see Manager.recognize) the .recognize() method is executed
+ * which determines with state it should be.
+ *
+ * If the recognizer has the state FAILED, CANCELLED or RECOGNIZED (equals ENDED), it is reset to
+ * POSSIBLE to give it another change on the next cycle.
+ *
+ *               Possible
+ *                  |
+ *            +-----+---------------+
+ *            |                     |
+ *      +-----+-----+               |
+ *      |           |               |
+ *   Failed      Cancelled          |
+ *                          +-------+------+
+ *                          |              |
+ *                      Recognized       Began
+ *                                         |
+ *                                      Changed
+ *                                         |
+ *                                  Ended/Recognized
+ */
+var STATE_POSSIBLE = 1;
+var STATE_BEGAN = 2;
+var STATE_CHANGED = 4;
+var STATE_ENDED = 8;
+var STATE_RECOGNIZED = STATE_ENDED;
+var STATE_CANCELLED = 16;
+var STATE_FAILED = 32;
+
+/**
+ * Recognizer
+ * Every recognizer needs to extend from this class.
+ * @constructor
+ * @param {Object} options
+ */
+function Recognizer(options) {
+    this.options = assign({}, this.defaults, options || {});
+
+    this.id = uniqueId();
+
+    this.manager = null;
+
+    // default is enable true
+    this.options.enable = ifUndefined(this.options.enable, true);
+
+    this.state = STATE_POSSIBLE;
+
+    this.simultaneous = {};
+    this.requireFail = [];
+}
+
+Recognizer.prototype = {
+    /**
+     * @virtual
+     * @type {Object}
+     */
+    defaults: {},
+
+    /**
+     * set options
+     * @param {Object} options
+     * @return {Recognizer}
+     */
+    set: function(options) {
+        assign(this.options, options);
+
+        // also update the touchAction, in case something changed about the directions/enabled state
+        this.manager && this.manager.touchAction.update();
+        return this;
+    },
+
+    /**
+     * recognize simultaneous with an other recognizer.
+     * @param {Recognizer} otherRecognizer
+     * @returns {Recognizer} this
+     */
+    recognizeWith: function(otherRecognizer) {
+        if (invokeArrayArg(otherRecognizer, 'recognizeWith', this)) {
+            return this;
+        }
+
+        var simultaneous = this.simultaneous;
+        otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
+        if (!simultaneous[otherRecognizer.id]) {
+            simultaneous[otherRecognizer.id] = otherRecognizer;
+            otherRecognizer.recognizeWith(this);
+        }
+        return this;
+    },
+
+    /**
+     * drop the simultaneous link. it doesnt remove the link on the other recognizer.
+     * @param {Recognizer} otherRecognizer
+     * @returns {Recognizer} this
+     */
+    dropRecognizeWith: function(otherRecognizer) {
+        if (invokeArrayArg(otherRecognizer, 'dropRecognizeWith', this)) {
+            return this;
+        }
+
+        otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
+        delete this.simultaneous[otherRecognizer.id];
+        return this;
+    },
+
+    /**
+     * recognizer can only run when an other is failing
+     * @param {Recognizer} otherRecognizer
+     * @returns {Recognizer} this
+     */
+    requireFailure: function(otherRecognizer) {
+        if (invokeArrayArg(otherRecognizer, 'requireFailure', this)) {
+            return this;
+        }
+
+        var requireFail = this.requireFail;
+        otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
+        if (inArray(requireFail, otherRecognizer) === -1) {
+            requireFail.push(otherRecognizer);
+            otherRecognizer.requireFailure(this);
+        }
+        return this;
+    },
+
+    /**
+     * drop the requireFailure link. it does not remove the link on the other recognizer.
+     * @param {Recognizer} otherRecognizer
+     * @returns {Recognizer} this
+     */
+    dropRequireFailure: function(otherRecognizer) {
+        if (invokeArrayArg(otherRecognizer, 'dropRequireFailure', this)) {
+            return this;
+        }
+
+        otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
+        var index = inArray(this.requireFail, otherRecognizer);
+        if (index > -1) {
+            this.requireFail.splice(index, 1);
+        }
+        return this;
+    },
+
+    /**
+     * has require failures boolean
+     * @returns {boolean}
+     */
+    hasRequireFailures: function() {
+        return this.requireFail.length > 0;
+    },
+
+    /**
+     * if the recognizer can recognize simultaneous with an other recognizer
+     * @param {Recognizer} otherRecognizer
+     * @returns {Boolean}
+     */
+    canRecognizeWith: function(otherRecognizer) {
+        return !!this.simultaneous[otherRecognizer.id];
+    },
+
+    /**
+     * You should use `tryEmit` instead of `emit` directly to check
+     * that all the needed recognizers has failed before emitting.
+     * @param {Object} input
+     */
+    emit: function(input) {
+        var self = this;
+        var state = this.state;
+
+        function emit(event) {
+            self.manager.emit(event, input);
+        }
+
+        // 'panstart' and 'panmove'
+        if (state < STATE_ENDED) {
+            emit(self.options.event + stateStr(state));
+        }
+
+        emit(self.options.event); // simple 'eventName' events
+
+        if (input.additionalEvent) { // additional event(panleft, panright, pinchin, pinchout...)
+            emit(input.additionalEvent);
+        }
+
+        // panend and pancancel
+        if (state >= STATE_ENDED) {
+            emit(self.options.event + stateStr(state));
+        }
+    },
+
+    /**
+     * Check that all the require failure recognizers has failed,
+     * if true, it emits a gesture event,
+     * otherwise, setup the state to FAILED.
+     * @param {Object} input
+     */
+    tryEmit: function(input) {
+        if (this.canEmit()) {
+            return this.emit(input);
+        }
+        // it's failing anyway
+        this.state = STATE_FAILED;
+    },
+
+    /**
+     * can we emit?
+     * @returns {boolean}
+     */
+    canEmit: function() {
+        var i = 0;
+        while (i < this.requireFail.length) {
+            if (!(this.requireFail[i].state & (STATE_FAILED | STATE_POSSIBLE))) {
+                return false;
+            }
+            i++;
+        }
+        return true;
+    },
+
+    /**
+     * update the recognizer
+     * @param {Object} inputData
+     */
+    recognize: function(inputData) {
+        // make a new copy of the inputData
+        // so we can change the inputData without messing up the other recognizers
+        var inputDataClone = assign({}, inputData);
+
+        // is is enabled and allow recognizing?
+        if (!boolOrFn(this.options.enable, [this, inputDataClone])) {
+            this.reset();
+            this.state = STATE_FAILED;
+            return;
+        }
+
+        // reset when we've reached the end
+        if (this.state & (STATE_RECOGNIZED | STATE_CANCELLED | STATE_FAILED)) {
+            this.state = STATE_POSSIBLE;
+        }
+
+        this.state = this.process(inputDataClone);
+
+        // the recognizer has recognized a gesture
+        // so trigger an event
+        if (this.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED | STATE_CANCELLED)) {
+            this.tryEmit(inputDataClone);
+        }
+    },
+
+    /**
+     * return the state of the recognizer
+     * the actual recognizing happens in this method
+     * @virtual
+     * @param {Object} inputData
+     * @returns {Const} STATE
+     */
+    process: function(inputData) { }, // jshint ignore:line
+
+    /**
+     * return the preferred touch-action
+     * @virtual
+     * @returns {Array}
+     */
+    getTouchAction: function() { },
+
+    /**
+     * called when the gesture isn't allowed to recognize
+     * like when another is being recognized or it is disabled
+     * @virtual
+     */
+    reset: function() { }
+};
+
+/**
+ * get a usable string, used as event postfix
+ * @param {Const} state
+ * @returns {String} state
+ */
+function stateStr(state) {
+    if (state & STATE_CANCELLED) {
+        return 'cancel';
+    } else if (state & STATE_ENDED) {
+        return 'end';
+    } else if (state & STATE_CHANGED) {
+        return 'move';
+    } else if (state & STATE_BEGAN) {
+        return 'start';
+    }
+    return '';
+}
+
+/**
+ * direction cons to string
+ * @param {Const} direction
+ * @returns {String}
+ */
+function directionStr(direction) {
+    if (direction == DIRECTION_DOWN) {
+        return 'down';
+    } else if (direction == DIRECTION_UP) {
+        return 'up';
+    } else if (direction == DIRECTION_LEFT) {
+        return 'left';
+    } else if (direction == DIRECTION_RIGHT) {
+        return 'right';
+    }
+    return '';
+}
+
+/**
+ * get a recognizer by name if it is bound to a manager
+ * @param {Recognizer|String} otherRecognizer
+ * @param {Recognizer} recognizer
+ * @returns {Recognizer}
+ */
+function getRecognizerByNameIfManager(otherRecognizer, recognizer) {
+    var manager = recognizer.manager;
+    if (manager) {
+        return manager.get(otherRecognizer);
+    }
+    return otherRecognizer;
+}
+
+/**
+ * This recognizer is just used as a base for the simple attribute recognizers.
+ * @constructor
+ * @extends Recognizer
+ */
+function AttrRecognizer() {
+    Recognizer.apply(this, arguments);
+}
+
+inherit(AttrRecognizer, Recognizer, {
+    /**
+     * @namespace
+     * @memberof AttrRecognizer
+     */
+    defaults: {
+        /**
+         * @type {Number}
+         * @default 1
+         */
+        pointers: 1
+    },
+
+    /**
+     * Used to check if it the recognizer receives valid input, like input.distance > 10.
+     * @memberof AttrRecognizer
+     * @param {Object} input
+     * @returns {Boolean} recognized
+     */
+    attrTest: function(input) {
+        var optionPointers = this.options.pointers;
+        return optionPointers === 0 || input.pointers.length === optionPointers;
+    },
+
+    /**
+     * Process the input and return the state for the recognizer
+     * @memberof AttrRecognizer
+     * @param {Object} input
+     * @returns {*} State
+     */
+    process: function(input) {
+        var state = this.state;
+        var eventType = input.eventType;
+
+        var isRecognized = state & (STATE_BEGAN | STATE_CHANGED);
+        var isValid = this.attrTest(input);
+
+        // on cancel input and we've recognized before, return STATE_CANCELLED
+        if (isRecognized && (eventType & INPUT_CANCEL || !isValid)) {
+            return state | STATE_CANCELLED;
+        } else if (isRecognized || isValid) {
+            if (eventType & INPUT_END) {
+                return state | STATE_ENDED;
+            } else if (!(state & STATE_BEGAN)) {
+                return STATE_BEGAN;
+            }
+            return state | STATE_CHANGED;
+        }
+        return STATE_FAILED;
+    }
+});
+
+/**
+ * Pan
+ * Recognized when the pointer is down and moved in the allowed direction.
+ * @constructor
+ * @extends AttrRecognizer
+ */
+function PanRecognizer() {
+    AttrRecognizer.apply(this, arguments);
+
+    this.pX = null;
+    this.pY = null;
+}
+
+inherit(PanRecognizer, AttrRecognizer, {
+    /**
+     * @namespace
+     * @memberof PanRecognizer
+     */
+    defaults: {
+        event: 'pan',
+        threshold: 10,
+        pointers: 1,
+        direction: DIRECTION_ALL
+    },
+
+    getTouchAction: function() {
+        var direction = this.options.direction;
+        var actions = [];
+        if (direction & DIRECTION_HORIZONTAL) {
+            actions.push(TOUCH_ACTION_PAN_Y);
+        }
+        if (direction & DIRECTION_VERTICAL) {
+            actions.push(TOUCH_ACTION_PAN_X);
+        }
+        return actions;
+    },
+
+    directionTest: function(input) {
+        var options = this.options;
+        var hasMoved = true;
+        var distance = input.distance;
+        var direction = input.direction;
+        var x = input.deltaX;
+        var y = input.deltaY;
+
+        // lock to axis?
+        if (!(direction & options.direction)) {
+            if (options.direction & DIRECTION_HORIZONTAL) {
+                direction = (x === 0) ? DIRECTION_NONE : (x < 0) ? DIRECTION_LEFT : DIRECTION_RIGHT;
+                hasMoved = x != this.pX;
+                distance = Math.abs(input.deltaX);
+            } else {
+                direction = (y === 0) ? DIRECTION_NONE : (y < 0) ? DIRECTION_UP : DIRECTION_DOWN;
+                hasMoved = y != this.pY;
+                distance = Math.abs(input.deltaY);
+            }
+        }
+        input.direction = direction;
+        return hasMoved && distance > options.threshold && direction & options.direction;
+    },
+
+    attrTest: function(input) {
+        return AttrRecognizer.prototype.attrTest.call(this, input) &&
+            (this.state & STATE_BEGAN || (!(this.state & STATE_BEGAN) && this.directionTest(input)));
+    },
+
+    emit: function(input) {
+
+        this.pX = input.deltaX;
+        this.pY = input.deltaY;
+
+        var direction = directionStr(input.direction);
+
+        if (direction) {
+            input.additionalEvent = this.options.event + direction;
+        }
+        this._super.emit.call(this, input);
+    }
+});
+
+/**
+ * Pinch
+ * Recognized when two or more pointers are moving toward (zoom-in) or away from each other (zoom-out).
+ * @constructor
+ * @extends AttrRecognizer
+ */
+function PinchRecognizer() {
+    AttrRecognizer.apply(this, arguments);
+}
+
+inherit(PinchRecognizer, AttrRecognizer, {
+    /**
+     * @namespace
+     * @memberof PinchRecognizer
+     */
+    defaults: {
+        event: 'pinch',
+        threshold: 0,
+        pointers: 2
+    },
+
+    getTouchAction: function() {
+        return [TOUCH_ACTION_NONE];
+    },
+
+    attrTest: function(input) {
+        return this._super.attrTest.call(this, input) &&
+            (Math.abs(input.scale - 1) > this.options.threshold || this.state & STATE_BEGAN);
+    },
+
+    emit: function(input) {
+        if (input.scale !== 1) {
+            var inOut = input.scale < 1 ? 'in' : 'out';
+            input.additionalEvent = this.options.event + inOut;
+        }
+        this._super.emit.call(this, input);
+    }
+});
+
+/**
+ * Press
+ * Recognized when the pointer is down for x ms without any movement.
+ * @constructor
+ * @extends Recognizer
+ */
+function PressRecognizer() {
+    Recognizer.apply(this, arguments);
+
+    this._timer = null;
+    this._input = null;
+}
+
+inherit(PressRecognizer, Recognizer, {
+    /**
+     * @namespace
+     * @memberof PressRecognizer
+     */
+    defaults: {
+        event: 'press',
+        pointers: 1,
+        time: 251, // minimal time of the pointer to be pressed
+        threshold: 9 // a minimal movement is ok, but keep it low
+    },
+
+    getTouchAction: function() {
+        return [TOUCH_ACTION_AUTO];
+    },
+
+    process: function(input) {
+        var options = this.options;
+        var validPointers = input.pointers.length === options.pointers;
+        var validMovement = input.distance < options.threshold;
+        var validTime = input.deltaTime > options.time;
+
+        this._input = input;
+
+        // we only allow little movement
+        // and we've reached an end event, so a tap is possible
+        if (!validMovement || !validPointers || (input.eventType & (INPUT_END | INPUT_CANCEL) && !validTime)) {
+            this.reset();
+        } else if (input.eventType & INPUT_START) {
+            this.reset();
+            this._timer = setTimeoutContext(function() {
+                this.state = STATE_RECOGNIZED;
+                this.tryEmit();
+            }, options.time, this);
+        } else if (input.eventType & INPUT_END) {
+            return STATE_RECOGNIZED;
+        }
+        return STATE_FAILED;
+    },
+
+    reset: function() {
+        clearTimeout(this._timer);
+    },
+
+    emit: function(input) {
+        if (this.state !== STATE_RECOGNIZED) {
+            return;
+        }
+
+        if (input && (input.eventType & INPUT_END)) {
+            this.manager.emit(this.options.event + 'up', input);
+        } else {
+            this._input.timeStamp = now();
+            this.manager.emit(this.options.event, this._input);
+        }
+    }
+});
+
+/**
+ * Rotate
+ * Recognized when two or more pointer are moving in a circular motion.
+ * @constructor
+ * @extends AttrRecognizer
+ */
+function RotateRecognizer() {
+    AttrRecognizer.apply(this, arguments);
+}
+
+inherit(RotateRecognizer, AttrRecognizer, {
+    /**
+     * @namespace
+     * @memberof RotateRecognizer
+     */
+    defaults: {
+        event: 'rotate',
+        threshold: 0,
+        pointers: 2
+    },
+
+    getTouchAction: function() {
+        return [TOUCH_ACTION_NONE];
+    },
+
+    attrTest: function(input) {
+        return this._super.attrTest.call(this, input) &&
+            (Math.abs(input.rotation) > this.options.threshold || this.state & STATE_BEGAN);
+    }
+});
+
+/**
+ * Swipe
+ * Recognized when the pointer is moving fast (velocity), with enough distance in the allowed direction.
+ * @constructor
+ * @extends AttrRecognizer
+ */
+function SwipeRecognizer() {
+    AttrRecognizer.apply(this, arguments);
+}
+
+inherit(SwipeRecognizer, AttrRecognizer, {
+    /**
+     * @namespace
+     * @memberof SwipeRecognizer
+     */
+    defaults: {
+        event: 'swipe',
+        threshold: 10,
+        velocity: 0.3,
+        direction: DIRECTION_HORIZONTAL | DIRECTION_VERTICAL,
+        pointers: 1
+    },
+
+    getTouchAction: function() {
+        return PanRecognizer.prototype.getTouchAction.call(this);
+    },
+
+    attrTest: function(input) {
+        var direction = this.options.direction;
+        var velocity;
+
+        if (direction & (DIRECTION_HORIZONTAL | DIRECTION_VERTICAL)) {
+            velocity = input.overallVelocity;
+        } else if (direction & DIRECTION_HORIZONTAL) {
+            velocity = input.overallVelocityX;
+        } else if (direction & DIRECTION_VERTICAL) {
+            velocity = input.overallVelocityY;
+        }
+
+        return this._super.attrTest.call(this, input) &&
+            direction & input.offsetDirection &&
+            input.distance > this.options.threshold &&
+            input.maxPointers == this.options.pointers &&
+            abs(velocity) > this.options.velocity && input.eventType & INPUT_END;
+    },
+
+    emit: function(input) {
+        var direction = directionStr(input.offsetDirection);
+        if (direction) {
+            this.manager.emit(this.options.event + direction, input);
+        }
+
+        this.manager.emit(this.options.event, input);
+    }
+});
+
+/**
+ * A tap is ecognized when the pointer is doing a small tap/click. Multiple taps are recognized if they occur
+ * between the given interval and position. The delay option can be used to recognize multi-taps without firing
+ * a single tap.
+ *
+ * The eventData from the emitted event contains the property `tapCount`, which contains the amount of
+ * multi-taps being recognized.
+ * @constructor
+ * @extends Recognizer
+ */
+function TapRecognizer() {
+    Recognizer.apply(this, arguments);
+
+    // previous time and center,
+    // used for tap counting
+    this.pTime = false;
+    this.pCenter = false;
+
+    this._timer = null;
+    this._input = null;
+    this.count = 0;
+}
+
+inherit(TapRecognizer, Recognizer, {
+    /**
+     * @namespace
+     * @memberof PinchRecognizer
+     */
+    defaults: {
+        event: 'tap',
+        pointers: 1,
+        taps: 1,
+        interval: 300, // max time between the multi-tap taps
+        time: 250, // max time of the pointer to be down (like finger on the screen)
+        threshold: 9, // a minimal movement is ok, but keep it low
+        posThreshold: 10 // a multi-tap can be a bit off the initial position
+    },
+
+    getTouchAction: function() {
+        return [TOUCH_ACTION_MANIPULATION];
+    },
+
+    process: function(input) {
+        var options = this.options;
+
+        var validPointers = input.pointers.length === options.pointers;
+        var validMovement = input.distance < options.threshold;
+        var validTouchTime = input.deltaTime < options.time;
+
+        this.reset();
+
+        if ((input.eventType & INPUT_START) && (this.count === 0)) {
+            return this.failTimeout();
+        }
+
+        // we only allow little movement
+        // and we've reached an end event, so a tap is possible
+        if (validMovement && validTouchTime && validPointers) {
+            if (input.eventType != INPUT_END) {
+                return this.failTimeout();
+            }
+
+            var validInterval = this.pTime ? (input.timeStamp - this.pTime < options.interval) : true;
+            var validMultiTap = !this.pCenter || getDistance(this.pCenter, input.center) < options.posThreshold;
+
+            this.pTime = input.timeStamp;
+            this.pCenter = input.center;
+
+            if (!validMultiTap || !validInterval) {
+                this.count = 1;
+            } else {
+                this.count += 1;
+            }
+
+            this._input = input;
+
+            // if tap count matches we have recognized it,
+            // else it has began recognizing...
+            var tapCount = this.count % options.taps;
+            if (tapCount === 0) {
+                // no failing requirements, immediately trigger the tap event
+                // or wait as long as the multitap interval to trigger
+                if (!this.hasRequireFailures()) {
+                    return STATE_RECOGNIZED;
+                } else {
+                    this._timer = setTimeoutContext(function() {
+                        this.state = STATE_RECOGNIZED;
+                        this.tryEmit();
+                    }, options.interval, this);
+                    return STATE_BEGAN;
+                }
+            }
+        }
+        return STATE_FAILED;
+    },
+
+    failTimeout: function() {
+        this._timer = setTimeoutContext(function() {
+            this.state = STATE_FAILED;
+        }, this.options.interval, this);
+        return STATE_FAILED;
+    },
+
+    reset: function() {
+        clearTimeout(this._timer);
+    },
+
+    emit: function() {
+        if (this.state == STATE_RECOGNIZED) {
+            this._input.tapCount = this.count;
+            this.manager.emit(this.options.event, this._input);
+        }
+    }
+});
+
+/**
+ * Simple way to create a manager with a default set of recognizers.
+ * @param {HTMLElement} element
+ * @param {Object} [options]
+ * @constructor
+ */
+function NGHammer(element, options) {
+    options = options || {};
+    options.recognizers = ifUndefined(options.recognizers, NGHammer.defaults.preset);
+    return new Manager(element, options);
+}
+
+/**
+ * @const {string}
+ */
+NGHammer.VERSION = '2.0.7';
+
+/**
+ * default settings
+ * @namespace
+ */
+NGHammer.defaults = {
+    /**
+     * set if DOM events are being triggered.
+     * But this is slower and unused by simple implementations, so disabled by default.
+     * @type {Boolean}
+     * @default false
+     */
+    domEvents: false,
+
+    /**
+     * The value for the touchAction property/fallback.
+     * When set to `compute` it will magically set the correct value based on the added recognizers.
+     * @type {String}
+     * @default compute
+     */
+    touchAction: TOUCH_ACTION_COMPUTE,
+
+    /**
+     * @type {Boolean}
+     * @default true
+     */
+    enable: true,
+
+    /**
+     * EXPERIMENTAL FEATURE -- can be removed/changed
+     * Change the parent input target element.
+     * If Null, then it is being set the to main element.
+     * @type {Null|EventTarget}
+     * @default null
+     */
+    inputTarget: null,
+
+    /**
+     * force an input class
+     * @type {Null|Function}
+     * @default null
+     */
+    inputClass: null,
+
+    /**
+     * Default recognizer setup when calling `NGHammer()`
+     * When creating a new Manager these will be skipped.
+     * @type {Array}
+     */
+    preset: [
+        // RecognizerClass, options, [recognizeWith, ...], [requireFailure, ...]
+        [RotateRecognizer, {enable: false}],
+        [PinchRecognizer, {enable: false}, ['rotate']],
+        [SwipeRecognizer, {direction: DIRECTION_HORIZONTAL}],
+        [PanRecognizer, {direction: DIRECTION_HORIZONTAL}, ['swipe']],
+        [TapRecognizer],
+        [TapRecognizer, {event: 'doubletap', taps: 2}, ['tap']],
+        [PressRecognizer]
+    ],
+
+    /**
+     * Some CSS properties can be used to improve the working of NGHammer.
+     * Add them to this method and they will be set when creating a new Manager.
+     * @namespace
+     */
+    cssProps: {
+        /**
+         * Disables text selection to improve the dragging gesture. Mainly for desktop browsers.
+         * @type {String}
+         * @default 'none'
+         */
+        userSelect: 'none',
+
+        /**
+         * Disable the Windows Phone grippers when pressing an element.
+         * @type {String}
+         * @default 'none'
+         */
+        touchSelect: 'none',
+
+        /**
+         * Disables the default callout shown when you touch and hold a touch target.
+         * On iOS, when you touch and hold a touch target such as a link, Safari displays
+         * a callout containing information about the link. This property allows you to disable that callout.
+         * @type {String}
+         * @default 'none'
+         */
+        touchCallout: 'none',
+
+        /**
+         * Specifies whether zooming is enabled. Used by IE10>
+         * @type {String}
+         * @default 'none'
+         */
+        contentZooming: 'none',
+
+        /**
+         * Specifies that an entire element should be draggable instead of its contents. Mainly for desktop browsers.
+         * @type {String}
+         * @default 'none'
+         */
+        userDrag: 'none',
+
+        /**
+         * Overrides the highlight color shown when the user taps a link or a JavaScript
+         * clickable element in iOS. This property obeys the alpha value, if specified.
+         * @type {String}
+         * @default 'rgba(0,0,0,0)'
+         */
+        tapHighlightColor: 'rgba(0,0,0,0)'
+    }
+};
+
+var STOP = 1;
+var FORCED_STOP = 2;
+
+/**
+ * Manager
+ * @param {HTMLElement} element
+ * @param {Object} [options]
+ * @constructor
+ */
+function Manager(element, options) {
+    this.options = assign({}, NGHammer.defaults, options || {});
+
+    this.options.inputTarget = this.options.inputTarget || element;
+
+    this.handlers = {};
+    this.session = {};
+    this.recognizers = [];
+    this.oldCssProps = {};
+
+    this.element = element;
+    this.input = createInputInstance(this);
+    this.touchAction = new TouchAction(this, this.options.touchAction);
+
+    toggleCssProps(this, true);
+
+    each(this.options.recognizers, function(item) {
+        var recognizer = this.add(new (item[0])(item[1]));
+        item[2] && recognizer.recognizeWith(item[2]);
+        item[3] && recognizer.requireFailure(item[3]);
+    }, this);
+}
+
+Manager.prototype = {
+    /**
+     * set options
+     * @param {Object} options
+     * @returns {Manager}
+     */
+    set: function(options) {
+        assign(this.options, options);
+
+        // Options that need a little more setup
+        if (options.touchAction) {
+            this.touchAction.update();
+        }
+        if (options.inputTarget) {
+            // Clean up existing event listeners and reinitialize
+            this.input.destroy();
+            this.input.target = options.inputTarget;
+            this.input.init();
+        }
+        return this;
+    },
+
+    /**
+     * stop recognizing for this session.
+     * This session will be discarded, when a new [input]start event is fired.
+     * When forced, the recognizer cycle is stopped immediately.
+     * @param {Boolean} [force]
+     */
+    stop: function(force) {
+        this.session.stopped = force ? FORCED_STOP : STOP;
+    },
+
+    /**
+     * run the recognizers!
+     * called by the inputHandler function on every movement of the pointers (touches)
+     * it walks through all the recognizers and tries to detect the gesture that is being made
+     * @param {Object} inputData
+     */
+    recognize: function(inputData) {
+        var session = this.session;
+        if (session.stopped) {
+            return;
+        }
+
+        // run the touch-action polyfill
+        this.touchAction.preventDefaults(inputData);
+
+        var recognizer;
+        var recognizers = this.recognizers;
+
+        // this holds the recognizer that is being recognized.
+        // so the recognizer's state needs to be BEGAN, CHANGED, ENDED or RECOGNIZED
+        // if no recognizer is detecting a thing, it is set to `null`
+        var curRecognizer = session.curRecognizer;
+
+        // reset when the last recognizer is recognized
+        // or when we're in a new session
+        if (!curRecognizer || (curRecognizer && curRecognizer.state & STATE_RECOGNIZED)) {
+            curRecognizer = session.curRecognizer = null;
+        }
+
+        var i = 0;
+        while (i < recognizers.length) {
+            recognizer = recognizers[i];
+
+            // find out if we are allowed try to recognize the input for this one.
+            // 1.   allow if the session is NOT forced stopped (see the .stop() method)
+            // 2.   allow if we still haven't recognized a gesture in this session, or the this recognizer is the one
+            //      that is being recognized.
+            // 3.   allow if the recognizer is allowed to run simultaneous with the current recognized recognizer.
+            //      this can be setup with the `recognizeWith()` method on the recognizer.
+            if (session.stopped !== FORCED_STOP && ( // 1
+                    !curRecognizer || recognizer == curRecognizer || // 2
+                    recognizer.canRecognizeWith(curRecognizer))) { // 3
+                recognizer.recognize(inputData);
+            } else {
+                recognizer.reset();
+            }
+
+            // if the recognizer has been recognizing the input as a valid gesture, we want to store this one as the
+            // current active recognizer. but only if we don't already have an active recognizer
+            if (!curRecognizer && recognizer.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED)) {
+                curRecognizer = session.curRecognizer = recognizer;
+            }
+            i++;
+        }
+    },
+
+    /**
+     * get a recognizer by its event name.
+     * @param {Recognizer|String} recognizer
+     * @returns {Recognizer|Null}
+     */
+    get: function(recognizer) {
+        if (recognizer instanceof Recognizer) {
+            return recognizer;
+        }
+
+        var recognizers = this.recognizers;
+        for (var i = 0; i < recognizers.length; i++) {
+            if (recognizers[i].options.event == recognizer) {
+                return recognizers[i];
+            }
+        }
+        return null;
+    },
+
+    /**
+     * add a recognizer to the manager
+     * existing recognizers with the same event name will be removed
+     * @param {Recognizer} recognizer
+     * @returns {Recognizer|Manager}
+     */
+    add: function(recognizer) {
+        if (invokeArrayArg(recognizer, 'add', this)) {
+            return this;
+        }
+
+        // remove existing
+        var existing = this.get(recognizer.options.event);
+        if (existing) {
+            this.remove(existing);
+        }
+
+        this.recognizers.push(recognizer);
+        recognizer.manager = this;
+
+        this.touchAction.update();
+        return recognizer;
+    },
+
+    /**
+     * remove a recognizer by name or instance
+     * @param {Recognizer|String} recognizer
+     * @returns {Manager}
+     */
+    remove: function(recognizer) {
+        if (invokeArrayArg(recognizer, 'remove', this)) {
+            return this;
+        }
+
+        recognizer = this.get(recognizer);
+
+        // let's make sure this recognizer exists
+        if (recognizer) {
+            var recognizers = this.recognizers;
+            var index = inArray(recognizers, recognizer);
+
+            if (index !== -1) {
+                recognizers.splice(index, 1);
+                this.touchAction.update();
+            }
+        }
+
+        return this;
+    },
+
+    /**
+     * bind event
+     * @param {String} events
+     * @param {Function} handler
+     * @returns {EventEmitter} this
+     */
+    on: function(events, handler) {
+        if (events === undefined) {
+            return;
+        }
+        if (handler === undefined) {
+            return;
+        }
+
+        var handlers = this.handlers;
+        each(splitStr(events), function(event) {
+            handlers[event] = handlers[event] || [];
+            handlers[event].push(handler);
+        });
+        return this;
+    },
+
+    /**
+     * unbind event, leave emit blank to remove all handlers
+     * @param {String} events
+     * @param {Function} [handler]
+     * @returns {EventEmitter} this
+     */
+    off: function(events, handler) {
+        if (events === undefined) {
+            return;
+        }
+
+        var handlers = this.handlers;
+        each(splitStr(events), function(event) {
+            if (!handler) {
+                delete handlers[event];
+            } else {
+                handlers[event] && handlers[event].splice(inArray(handlers[event], handler), 1);
+            }
+        });
+        return this;
+    },
+
+    /**
+     * emit event to the listeners
+     * @param {String} event
+     * @param {Object} data
+     */
+    emit: function(event, data) {
+        // we also want to trigger dom events
+        if (this.options.domEvents) {
+            triggerDomEvent(event, data);
+        }
+
+        // no handlers, so skip it all
+        var handlers = this.handlers[event] && this.handlers[event].slice();
+        if (!handlers || !handlers.length) {
+            return;
+        }
+
+        data.type = event;
+        data.preventDefault = function() {
+            data.srcEvent.preventDefault();
+        };
+
+        var i = 0;
+        while (i < handlers.length) {
+            handlers[i](data);
+            i++;
+        }
+    },
+
+    /**
+     * destroy the manager and unbinds all events
+     * it doesn't unbind dom events, that is the user own responsibility
+     */
+    destroy: function() {
+        this.element && toggleCssProps(this, false);
+
+        this.handlers = {};
+        this.session = {};
+        this.input.destroy();
+        this.element = null;
+    }
+};
+
+/**
+ * add/remove the css properties as defined in manager.options.cssProps
+ * @param {Manager} manager
+ * @param {Boolean} add
+ */
+function toggleCssProps(manager, add) {
+    var element = manager.element;
+    if (!element.style) {
+        return;
+    }
+    var prop;
+    each(manager.options.cssProps, function(value, name) {
+        prop = prefixed(element.style, name);
+        if (add) {
+            manager.oldCssProps[prop] = element.style[prop];
+            element.style[prop] = value;
+        } else {
+            element.style[prop] = manager.oldCssProps[prop] || '';
+        }
+    });
+    if (!add) {
+        manager.oldCssProps = {};
+    }
+}
+
+/**
+ * trigger dom event
+ * @param {String} event
+ * @param {Object} data
+ */
+function triggerDomEvent(event, data) {
+    var gestureEvent = document.createEvent('Event');
+    gestureEvent.initEvent(event, true, true);
+    gestureEvent.gesture = data;
+    data.target.dispatchEvent(gestureEvent);
+}
+
+assign(NGHammer, {
+    INPUT_START: INPUT_START,
+    INPUT_MOVE: INPUT_MOVE,
+    INPUT_END: INPUT_END,
+    INPUT_CANCEL: INPUT_CANCEL,
+
+    STATE_POSSIBLE: STATE_POSSIBLE,
+    STATE_BEGAN: STATE_BEGAN,
+    STATE_CHANGED: STATE_CHANGED,
+    STATE_ENDED: STATE_ENDED,
+    STATE_RECOGNIZED: STATE_RECOGNIZED,
+    STATE_CANCELLED: STATE_CANCELLED,
+    STATE_FAILED: STATE_FAILED,
+
+    DIRECTION_NONE: DIRECTION_NONE,
+    DIRECTION_LEFT: DIRECTION_LEFT,
+    DIRECTION_RIGHT: DIRECTION_RIGHT,
+    DIRECTION_UP: DIRECTION_UP,
+    DIRECTION_DOWN: DIRECTION_DOWN,
+    DIRECTION_HORIZONTAL: DIRECTION_HORIZONTAL,
+    DIRECTION_VERTICAL: DIRECTION_VERTICAL,
+    DIRECTION_ALL: DIRECTION_ALL,
+
+    Manager: Manager,
+    Input: Input,
+    TouchAction: TouchAction,
+
+    TouchInput: TouchInput,
+    MouseInput: MouseInput,
+    PointerEventInput: PointerEventInput,
+    TouchMouseInput: TouchMouseInput,
+    SingleTouchInput: SingleTouchInput,
+
+    Recognizer: Recognizer,
+    AttrRecognizer: AttrRecognizer,
+    Tap: TapRecognizer,
+    Pan: PanRecognizer,
+    Swipe: SwipeRecognizer,
+    Pinch: PinchRecognizer,
+    Rotate: RotateRecognizer,
+    Press: PressRecognizer,
+
+    on: addEventListeners,
+    off: removeEventListeners,
+    each: each,
+    merge: merge,
+    extend: extend,
+    assign: assign,
+    inherit: inherit,
+    bindFn: bindFn,
+    prefixed: prefixed
+});
+
+// this prevents errors when NGHammer is loaded in the presence of an AMD
+//  style loader but by script tag, not by the loader.
+var freeGlobal = (typeof window !== 'undefined' ? window : (typeof self !== 'undefined' ? self : {})); // jshint ignore:line
+freeGlobal.NGHammer = NGHammer;
+
+if (typeof define === 'function' && define.amdDISABLED) {
+    define(function() {
+        return NGHammer;
+    });
+} else if (typeof module != 'undefined' && module.exports) {
+    module.exports = NGHammer;
+} else {
+    window[exportName] = NGHammer;
+}
+
+})(window, document, 'NGHammer');
+
+
+
+
+
+
+// END NANOGALLERY2
+// }( jQuery )));
+}));
+
+
+//##########################################################################################################################
+//##########################################################################################################################
+//##########################################################################################################################
+//##########################################################################################################################
+//##########################################################################################################################
+
+// nanogallery2 auto start whithout javascript call
+(function(){
+  'use strict';
+	
+	function document_ready(callback){
+		// in case the document is already rendered
+		if (document.readyState!='loading') callback();
+		// modern browsers
+		else if (document.addEventListener) document.addEventListener('DOMContentLoaded', callback);
+		// IE <= 8
+		else document.attachEvent('onreadystatechange', function(){
+				if (document.readyState=='complete') callback();
+		});
+	}
+
+	document_ready(function(){
+		
+		// retrieve GALLERIES
+		var t=document.querySelectorAll('[data-nanogallery2]');
+		for( var i=0; i < t.length; i++ ) {
+			jQuery( t[i] ).nanogallery2( jQuery(t[i]).data('nanogallery2') );
+		}
+		
+		// retrieve SINGLE ELEMENTS -> ONLY LIGHTBOX / NO GALLERY
+		var t = document.querySelectorAll('[data-nanogallery2-lightbox]');
+		for( var i=0; i < t.length; i++ ) {
+			
+      // set mouse pointer
+      t[i].classList.add('NGY2ThumbnailLightbox');
+
+      // add click event
+      t[i].addEventListener('click', function(e) {
+        // disable link tag if A element
+        e.preventDefault();
+      
+        // default options for standalone lightbox
+        var options = { 
+          lightboxStandalone: true,
+          viewerToolbar: { display: false }
+        };
+
+        // group of images
+        var g = this.dataset.nanogallery2Lgroup;
+
+        // Retrieve the lightbox configuration
+        // it just need to be defined on one of the elements, which will be displayed in the lightbox
+        var t = document.querySelectorAll('[data-nanogallery2-lightbox]');
+        for( var i=0; i < t.length; i++ ) {
+          if( t[i].dataset.nanogallery2Lgroup == g ) {
+            if( t[i].dataset.nanogallery2Lightbox !== "" ) {
+              options = jQuery.extend(true, {}, options, jQuery(t[i]).data('nanogallery2Lightbox'));
+              break;
+            }
+          }
+        }
+        jQuery( this ).nanogallery2( options );
+
+      });
+
+		}
+	});
+	
+	
+	
+  // jQuery(document).ready(function () {
+  
+    // var t=document.querySelectorAll('[data-nanogallery2-portable]');
+    // if( t.length > 0 ) {
+      // portable mode
+      // var link = document.createElement('link');
+      // link.setAttribute("rel", "stylesheet");
+      // link.setAttribute("type", "text/css");
+      // link.onload = function(){
+        // for( var i=0; i < t.length; i++ ) {
+          // jQuery(t[i]).nanogallery2(jQuery(t[i]).data('nanogallery2-portable'));
+        // }
+      // }
+      // link.setAttribute("href", '//nano.gallery/css/nanogallery2.css');
+      // document.getElementsByTagName("head")[0].appendChild(link);
+    // }
+    // else {
+      // standard mode
+			
+			// GALLERIES
+      // var t=document.querySelectorAll('[data-nanogallery2]');
+      // for( var i=0; i < t.length; i++ ) {
+        // jQuery( t[i] ).nanogallery2( jQuery(t[i]).data('nanogallery2') );
+      // }
+			
+		
+    // }
+    
+  // });
+}).call(null);
+
diff --git a/src/jquery.nanogallery2.data_flickr.js b/src/jquery.nanogallery2.data_flickr.js
index 29ed7f84..73825e14 100644
--- a/src/jquery.nanogallery2.data_flickr.js
+++ b/src/jquery.nanogallery2.data_flickr.js
@@ -88,6 +88,9 @@
         clearTimeout(tId);
         PreloaderDisplay(false);
         
+        // go through sourceData, and exclude blacklisted tags
+        sourceData = NGY2Tools.FilterByTags(sourceData, G.tagBlackList);
+
         if( kind == 'album' ) {
           FlickrParsePhotoSets(albumIdx, albumID, sourceData);
         }