PageSourceSearch

https://www.smallservers.pl/cache/jsn_teki_pro/7b60d37dc5c7fc35b3615b4cc1f2116d.js

js smallservers.pl collected 2026-10-10 01:27:39 UTC 181,322 bytes, 5,978 lines download raw bytes

1
2
3/* FILE: /plugins/jsnimageshow/themeclassic/assets/js/galleria/galleria-1.2.8.js */
4/**
5 * Galleria v 1.2.8 2012-08-09
6 * http://galleria.io
7 *
8 * Licensed under the MIT license
9 * https://raw.github.com/aino/galleria/master/LICENSE
10 *
11 */
12
13(function( $ ) {
14
15/*global jQuery, navigator, Galleria:true, Image */
16
17// some references
18var undef,
19    window = this,
20    doc    = window.document,
21    $doc   = $( doc ),
22    $win   = $( window ),
23
24// native prototypes
25    protoArray = Array.prototype,
26
27// internal constants
28    VERSION = 1.28,
29    DEBUG = true,
30    TIMEOUT = 30000,
31    DUMMY = false,
32    NAV = navigator.userAgent.toLowerCase(),
33    HASH = window.location.hash.replace(/#\//, ''),
34    F = function(){},
35    FALSE = function() { return false; },
36    IE = (function() {
37
38        var v = 3,
39            div = doc.createElement( 'div' ),
40            all = div.getElementsByTagName( 'i' );
41
42        do {
43            div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->';
44        } while ( all[0] );
45
46        return v > 4 ? v : undef;
47
48    }() ),
49    DOM = function() {
50        return {
51            html:  doc.documentElement,
52            body:  doc.body,
53            head:  doc.getElementsByTagName('head')[0],
54            title: doc.title
55        };
56    },
57
58    // list of Galleria events
59    _eventlist = 'data ready thumbnail loadstart loadfinish image play pause progress ' +
60                 'fullscreen_enter fullscreen_exit idle_enter idle_exit rescale ' +
61                 'lightbox_open lightbox_close lightbox_image',
62
63    _events = (function() {
64
65        var evs = [];
66
67        $.each( _eventlist.split(' '), function( i, ev ) {
68            evs.push( ev );
69
70            // legacy events
71            if ( /_/.test( ev ) ) {
72                evs.push( ev.replace( /_/g, '' ) );
73            }
74        });
75
76        return evs;
77
78    }()),
79
80    // legacy options
81    // allows the old my_setting syntax and converts it to camel case
82
83    _legacyOptions = function( options ) {
84
85        var n;
86
87        if ( typeof options !== 'object' ) {
88
89            // return whatever it was...
90            return options;
91        }
92
93        $.each( options, function( key, value ) {
94            if ( /^[a-z]+_/.test( key ) ) {
95                n = '';
96                $.each( key.split('_'), function( i, k ) {
97                    n += i > 0 ? k.substr( 0, 1 ).toUpperCase() + k.substr( 1 ) : k;
98                });
99                options[ n ] = value;
100                delete options[ key ];
101            }
102        });
103
104        return options;
105    },
106
107    _patchEvent = function( type ) {
108
109        // allow 'image' instead of Galleria.IMAGE
110        if ( $.inArray( type, _events ) > -1 ) {
111            return Galleria[ type.toUpperCase() ];
112        }
113
114        return type;
115    },
116
117    // video providers
118    _video = {
119        youtube: {
120            reg: /https?:\/\/(?:[a-zA_Z]{2,3}.)?(?:youtube\.com\/watch\?)((?:[\w\d\-\_\=]+&amp;(?:amp;)?)*v(?:&lt;[A-Z]+&gt;)?=([0-9a-zA-Z\-\_]+))/i,
121            embed: function(id) {
122                return 'http://www.youtube.com/embed/'+id;
123            },
124            getThumb: function( id, success, fail ) {
125                fail = fail || F;
126                $.getJSON('http://gdata.youtube.com/feeds/api/videos/' + id + '?v=2&alt=json-in-script&callback=?', function(data) {
127                    try {
128                        success( data.entry.media$group.media$thumbnail[0].url );
129                    } catch(e) {
130                        fail();
131                    }
132                }).error(fail);
133            }
134        },
135        vimeo: {
136            reg: /https?:\/\/(?:www\.)?(vimeo\.com)\/(?:hd#)?([0-9]+)/i,
137            embed: function(id) {
138                return 'http://player.vimeo.com/video/'+id;
139            },
140            getThumb: function( id, success, fail ) {
141                fail = fail || F;
142                $.getJSON('http://vimeo.com/api/v2/video/' + id + '.json?callback=?', function(data) {
143                    try {
144                        success( data[0].thumbnail_medium );
145                    } catch(e) {
146                        fail();
147                    }
148                }).error(fail);
149            }
150        },
151        dailymotion: {
152            reg: /https?:\/\/(?:www\.)?(dailymotion\.com)\/video\/([^_]+)/,
153            embed: function(id) {
154                return 'http://www.dailymotion.com/embed/video/'+id;
155            },
156            getThumb: function( id, success, fail ) {
157                fail = fail || F;
158                $.getJSON('https://api.dailymotion.com/video/'+id+'?fields=thumbnail_medium_url&callback=?', function(data) {
159                    try {
160                        success( data.thumbnail_medium_url );
161                    } catch(e) {
162                        fail();
163                    }
164                }).error(fail);
165            }
166        }
167    },
168
169    // utility for testing the video URL and getting the video ID
170    _videoTest = function( url ) {
171        var match;
172        for ( var v in _video ) {
173            match = url && url.match( _video[v].reg );
174            if( match && match.length ) {
175                return {
176                    id: match[2],
177                    provider: v
178                };
179            }
180        }
181        return false;
182    },
183
184    // native fullscreen handler
185    _nativeFullscreen = {
186
187        support: (function() {
188            var html = DOM().html;
189            return html.requestFullscreen || html.mozRequestFullScreen || html.webkitRequestFullScreen;
190        }()),
191
192        callback: F,
193
194        enter: function( instance, callback ) {
195
196            this.instance = instance;
197
198            this.callback = callback || F;
199
200            var html = DOM().html;
201            if ( html.requestFullscreen ) {
202                html.requestFullscreen();
203            }
204            else if ( html.mozRequestFullScreen ) {
205                html.mozRequestFullScreen();
206            }
207            else if ( html.webkitRequestFullScreen ) {
208                html.webkitRequestFullScreen();
209            }
210        },
211
212        exit: function( callback ) {
213
214            this.callback = callback || F;
215
216            if ( doc.exitFullscreen ) {
217                doc.exitFullscreen();
218            }
219            else if ( doc.mozCancelFullScreen ) {
220                doc.mozCancelFullScreen();
221            }
222            else if ( doc.webkitCancelFullScreen ) {
223                doc.webkitCancelFullScreen();
224            }
225        },
226
227        instance: null,
228
229        listen: function() {
230
231            if ( !this.support ) {
232                return;
233            }
234
235            var handler = function() {
236
237                if ( !_nativeFullscreen.instance ) {
238                    return;
239                }
240                var fs = _nativeFullscreen.instance._fullscreen;
241
242                if ( doc.fullscreen || doc.mozFullScreen || doc.webkitIsFullScreen ) {
243                    fs._enter( _nativeFullscreen.callback );
244                } else {
245                    fs._exit( _nativeFullscreen.callback );
246                }
247            };
248            doc.addEventListener( 'fullscreenchange', handler, false );
249            doc.addEventListener( 'mozfullscreenchange', handler, false );
250            doc.addEventListener( 'webkitfullscreenchange', handler, false );
251        }
252    },
253
254    // the internal gallery holder
255    _galleries = [],
256
257    // the internal instance holder
258    _instances = [],
259
260    // flag for errors
261    _hasError = false,
262
263    // canvas holder
264    _canvas = false,
265
266    // instance pool, holds the galleries until themeLoad is triggered
267    _pool = [],
268
269    // themeLoad trigger
270    _themeLoad = function( theme ) {
271        Galleria.theme = theme;
272
273        // run the instances we have in the pool
274        $.each( _pool, function( i, instance ) {
275            if ( !instance._initialized ) {
276                instance._init.call( instance );
277            }
278        });
279    },
280
281    // the Utils singleton
282    Utils = (function() {
283
284        return {
285
286            // legacy support for clearTimer
287            clearTimer: function( id ) {
288                $.each( Galleria.get(), function() {
289                    this.clearTimer( id );
290                });
291            },
292
293            // legacy support for addTimer
294            addTimer: function( id ) {
295                $.each( Galleria.get(), function() {
296                    this.addTimer( id );
297                });
298            },
299
300            array : function( obj ) {
301                return protoArray.slice.call(obj, 0);
302            },
303
304            create : function( className, nodeName ) {
305                nodeName = nodeName || 'div';
306                var elem = doc.createElement( nodeName );
307                elem.className = className;
308                return elem;
309            },
310
311            getScriptPath : function( src ) {
312
313                // the currently executing script is always the last
314                src = src || $('script:last').attr('src');
315                var slices = src.split('/');
316
317                if (slices.length == 1) {
318                    return '';
319                }
320
321                slices.pop();
322
323                return slices.join('/') + '/';
324            },
325
326            // CSS3 transitions, added in 1.2.4
327            animate : (function() {
328
329                // detect transition
330                var transition = (function( style ) {
331                    var props = 'transition WebkitTransition MozTransition OTransition'.split(' '),
332                        i;
333
334                    // disable css3 animations in opera until stable
335                    if ( window.opera ) {
336                        return false;
vendor: 4,170 bytes, lines 337-442
337                    }
338
339                    for ( i = 0; props[i]; i++ ) {
340                        if ( typeof style[ props[ i ] ] !== 'undefined' ) {
341                            return props[ i ];
342                        }
343                    }
344                    return false;
345                }(( doc.body || doc.documentElement).style ));
346
347                // map transitionend event
348                var endEvent = {
349                    MozTransition: 'transitionend',
350                    OTransition: 'oTransitionEnd',
351                    WebkitTransition: 'webkitTransitionEnd',
352                    transition: 'transitionend'
353                }[ transition ];
354
355                // map bezier easing conversions
356                var easings = {
357                    _default: [0.25, 0.1, 0.25, 1],
358                    galleria: [0.645, 0.045, 0.355, 1],
359                    galleriaIn: [0.55, 0.085, 0.68, 0.53],
360                    galleriaOut: [0.25, 0.46, 0.45, 0.94],
361                    ease: [0.25, 0, 0.25, 1],
362                    linear: [0.25, 0.25, 0.75, 0.75],
363                    'ease-in': [0.42, 0, 1, 1],
364                    'ease-out': [0, 0, 0.58, 1],
365                    'ease-in-out': [0.42, 0, 0.58, 1]
366                };
367
368                // function for setting transition css for all browsers
369                var setStyle = function( elem, value, suffix ) {
370                    var css = {};
371                    suffix = suffix || 'transition';
372                    $.each( 'webkit moz ms o'.split(' '), function() {
373                        css[ '-' + this + '-' + suffix ] = value;
374                    });
375                    elem.css( css );
376                };
377
378                // clear styles
379                var clearStyle = function( elem ) {
380                    setStyle( elem, 'none', 'transition' );
381                    if ( Galleria.WEBKIT && Galleria.TOUCH ) {
382                        setStyle( elem, 'translate3d(0,0,0)', 'transform' );
383                        if ( elem.data('revert') ) {
384                            elem.css( elem.data('revert') );
385                            elem.data('revert', null);
386                        }
387                    }
388                };
389
390                // various variables
391                var change, strings, easing, syntax, revert, form, css;
392
393                // the actual animation method
394                return function( elem, to, options ) {
395
396                    // extend defaults
397                    options = $.extend({
398                        duration: 400,
399                        complete: F,
400                        stop: false
401                    }, options);
402
403                    // cache jQuery instance
404                    elem = $( elem );
405
406                    if ( !options.duration ) {
407                        elem.css( to );
408                        options.complete.call( elem[0] );
409                        return;
410                    }
411
412                    // fallback to jQuery's animate if transition is not supported
413                    if ( !transition ) {
414                        elem.animate(to, options);
415                        return;
416                    }
417
418                    // stop
419                    if ( options.stop ) {
420                        // clear the animation
421                        elem.unbind( endEvent );
422                        clearStyle( elem );
423                    }
424
425                    // see if there is a change
426                    change = false;
427                    $.each( to, function( key, val ) {
428                        css = elem.css( key );
429                        if ( Utils.parseValue( css ) != Utils.parseValue( val ) ) {
430                            change = true;
431                        }
432                        // also add computed styles for FF
433                        elem.css( key, css );
434                    });
435                    if ( !change ) {
436                        window.setTimeout( function() {
437                            options.complete.call( elem[0] );
438                        }, options.duration );
439                        return;
440                    }
441
442                    // the css strings to be applied
443                    strings = [];
444
445                    // the easing bezier
446                    easing = options.easing in easings ? easings[ options.easing ] : easings._default;
447
448                    // the syntax
449                    syntax = ' ' + options.duration + 'ms' + ' cubic-bezier('  + easing.join(',') + ')';
450
451                    // add a tiny timeout so that the browsers catches any css changes before animating
452                    window.setTimeout( (function(elem, endEvent, to, syntax) {
453                        return function() {
454
455                            // attach the end event
456                            elem.one(endEvent, (function( elem ) {
457                                return function() {
458
459                                    // clear the animation
460                                    clearStyle(elem);
461
462                                    // run the complete method
463                                    options.complete.call(elem[0]);
464                                };
465                            }( elem )));
466
467                            // do the webkit translate3d for better performance on iOS
468                            if( Galleria.WEBKIT && Galleria.TOUCH ) {
469
470                                revert = {};
471                                form = [0,0,0];
472
473                                $.each( ['left', 'top'], function(i, m) {
474                                    if ( m in to ) {
475                                        form[ i ] = ( Utils.parseValue( to[ m ] ) - Utils.parseValue(elem.css( m )) ) + 'px';
476                                        revert[ m ] = to[ m ];
477                                        delete to[ m ];
478                                    }
479                                });
480
481                                if ( form[0] || form[1]) {
482
483                                    elem.data('revert', revert);
484
485                                    strings.push('-webkit-transform' + syntax);
486
487                                    // 3d animate
488                                    setStyle( elem, 'translate3d(' + form.join(',') + ')', 'transform');
489                                }
490                            }
491
492                            // push the animation props
493                            $.each(to, function( p, val ) {
494                                strings.push(p + syntax);
495                            });
496
497                            // set the animation styles
498                            setStyle( elem, strings.join(',') );
499
500                            // animate
501                            elem.css( to );
502
503                        };
504                    }(elem, endEvent, to, syntax)), 2);
505                };
506            }()),
507
508            removeAlpha : function( elem ) {
509                if ( IE < 9 && elem ) {
510
511                    var style = elem.style,
512                        currentStyle = elem.currentStyle,
513                        filter = currentStyle && currentStyle.filter || style.filter || "";
514
515                    if ( /alpha/.test( filter ) ) {
516                        style.filter = filter.replace( /alpha\([^)]*\)/i, '' );
517                    }
518                }
519            },
520
521            forceStyles : function( elem, styles ) {
522                elem = $(elem);
523                if ( elem.attr( 'style' ) ) {
524                    elem.data( 'styles', elem.attr( 'style' ) ).removeAttr( 'style' );
525                }
526                elem.css( styles );
527            },
528
529            revertStyles : function() {
530                $.each( Utils.array( arguments ), function( i, elem ) {
531
532                    elem = $( elem );
533                    elem.removeAttr( 'style' );
534
535                    elem.attr('style',''); // "fixes" webkit bug
536
537                    if ( elem.data( 'styles' ) ) {
538                        elem.attr( 'style', elem.data('styles') ).data( 'styles', null );
539                    }
540                });
541            },
542
543            moveOut : function( elem ) {
544                Utils.forceStyles( elem, {
545                    position: 'absolute',
546                    left: -10000
547                });
548            },
549
550            moveIn : function() {
551                Utils.revertStyles.apply( Utils, Utils.array( arguments ) );
552            },
553
554            elem : function( elem ) {
555                if (elem instanceof $) {
556                    return {
557                        $: elem,
558                        dom: elem[0]
559                    };
560                } else {
561                    return {
562                        $: $(elem),
563                        dom: elem
564                    };
565                }
566            },
567
568            hide : function( elem, speed, callback ) {
569
570                callback = callback || F;
571
572                var el = Utils.elem( elem ),
573                    $elem = el.$;
574
575                elem = el.dom;
576
577                // save the value if not exist
578                if (! $elem.data('opacity') ) {
579                    $elem.data('opacity', $elem.css('opacity') );
580                }
581
582                // always hide
583                var style = { opacity: 0 };
584
585                if (speed) {
586
587                    var complete = IE < 9 && elem ? function() {
588                        Utils.removeAlpha( elem );
589                        elem.style.visibility = 'hidden';
590                        callback.call( elem );
591                    } : callback;
592
593                    Utils.animate( elem, style, {
594                        duration: speed,
595                        complete: complete,
596                        stop: true
597                    });
598                } else {
599                    if ( IE < 9 && elem ) {
600                        Utils.removeAlpha( elem );
601                        elem.style.visibility = 'hidden';
602                    } else {
603                        $elem.css( style );
604                    }
605                }
606            },
607
608            show : function( elem, speed, callback ) {
609
610                callback = callback || F;
611
612                var el = Utils.elem( elem ),
613                    $elem = el.$;
614
615                elem = el.dom;
616
617                // bring back saved opacity
618                var saved = parseFloat( $elem.data('opacity') ) || 1,
619                    style = { opacity: saved };
620
621                // animate or toggle
622                if (speed) {
623
624                    if ( IE < 9 ) {
625                        $elem.css('opacity', 0);
626                        elem.style.visibility = 'visible';
627                    }
628
629                    var complete = IE < 9 && elem ? function() {
630                        if ( style.opacity == 1 ) {
631                            Utils.removeAlpha( elem );
632                        }
633                        callback.call( elem );
634                    } : callback;
635
636                    Utils.animate( elem, style, {
637                        duration: speed,
638                        complete: complete,
639                        stop: true
640                    });
641                } else {
642                    if ( IE < 9 && style.opacity == 1 && elem ) {
643                        Utils.removeAlpha( elem );
644                        elem.style.visibility = 'visible';
645                    } else {
646                        $elem.css( style );
647                    }
648                }
649            },
650
651
652            // enhanced click for mobile devices
653            // we bind a touchend and hijack any click event in the bubble
654            // then we execute the click directly and save it in a separate data object for later
655            optimizeTouch: (function() {
656
657                var node,
658                    evs,
659                    fakes,
660                    travel,
661                    evt = {},
662                    handler = function( e ) {
663                        e.preventDefault();
664                        evt = $.extend({}, e, true);
665                    },
666                    attach = function() {
667                        this.evt = evt;
668                    },
669                    fake = function() {
670                        this.handler.call(node, this.evt);
671                    };
672
673                return function( elem ) {
674
675                    $(elem).bind('touchend', function( e ) {
676
677                        node = e.target;
678                        travel = true;
679
680                        while( node.parentNode && node != e.currentTarget && travel ) {
681
682                            evs =   $(node).data('events');
683                            fakes = $(node).data('fakes');
684
685                            if (evs && 'click' in evs) {
686
687                                travel = false;
688                                e.preventDefault();
689
690                                // fake the click and save the event object
691                                $(node).click(handler).click();
692
693                                // remove the faked click
694                                evs.click.pop();
695
696                                // attach the faked event
697                                $.each( evs.click, attach);
698
699                                // save the faked clicks in a new data object
700                                $(node).data('fakes', evs.click);
701
702                                // remove all clicks
703                                delete evs.click;
704
705                            } else if ( fakes ) {
706
707                                travel = false;
708                                e.preventDefault();
709
710                                // fake all clicks
711                                $.each( fakes, fake );
712                            }
713
714                            // bubble
715                            node = node.parentNode;
716                        }
717                    });
718                };
719            }()),
720
721            wait : function(options) {
722                options = $.extend({
723                    until : FALSE,
724                    success : F,
725                    error : function() { Galleria.raise('Could not complete wait function.'); },
726                    timeout: 3000
727                }, options);
728
729                var start = Utils.timestamp(),
730                    elapsed,
731                    now,
732                    fn = function() {
733                        now = Utils.timestamp();
734                        elapsed = now - start;
735                        if ( options.until( elapsed ) ) {
736                            options.success();
737                            return false;
738                        }
739
740                        if (typeof options.timeout == 'number' && now >= start + options.timeout) {
741                            options.error();
742                            return false;
743                        }
744                        window.setTimeout(fn, 10);
745                    };
746
747                window.setTimeout(fn, 10);
748            },
749
750            toggleQuality : function( img, force ) {
751
752                if ( ( IE !== 7 && IE !== 8 ) || !img || img.nodeName.toUpperCase() != 'IMG' ) {
753                    return;
754                }
755
756                if ( typeof force === 'undefined' ) {
757                    force = img.style.msInterpolationMode === 'nearest-neighbor';
758                }
759
760                img.style.msInterpolationMode = force ? 'bicubic' : 'nearest-neighbor';
761            },
762
763            insertStyleTag : function( styles ) {
764                var style = doc.createElement( 'style' );
765                DOM().head.appendChild( style );
766
767                if ( style.styleSheet ) { // IE
768                    style.styleSheet.cssText = styles;
769                } else {
770                    var cssText = doc.createTextNode( styles );
771                    style.appendChild( cssText );
772                }
773            },
774
775            // a loadscript method that works for local scripts
776            loadScript: function( url, callback ) {
777
778                var done = false,
779                    script = $('<scr'+'ipt>').attr({
780                        src: url,
781                        async: true
782                    }).get(0);
783
784               // Attach handlers for all browsers
785               script.onload = script.onreadystatechange = function() {
786                   if ( !done && (!this.readyState ||
787                       this.readyState === 'loaded' || this.readyState === 'complete') ) {
788
789                       done = true;
790
791                       // Handle memory leak in IE
792                       script.onload = script.onreadystatechange = null;
793
794                       if (typeof callback === 'function') {
795                           callback.call( this, this );
796                       }
797                   }
798               };
799
800               DOM().head.appendChild( script );
801            },
802
vendor: 19,778 bytes, lines 803-1456
803            // parse anything into a number
804            parseValue: function( val ) {
805                if (typeof val === 'number') {
806                    return val;
807                } else if (typeof val === 'string') {
808                    var arr = val.match(/\-?\d|\./g);
809                    return arr && arr.constructor === Array ? arr.join('')*1 : 0;
810                } else {
811                    return 0;
812                }
813            },
814
815            // timestamp abstraction
816            timestamp: function() {
817                return new Date().getTime();
818            },
819
820            loadCSS : function( href, id, callback ) {
821
822                var link,
823                    length;
824
825                // look for manual css
826                $('link[rel=stylesheet]').each(function() {
827                    if ( new RegExp( href ).test( this.href ) ) {
828                        link = this;
829                        return false;
830                    }
831                });
832
833                if ( typeof id === 'function' ) {
834                    callback = id;
835                    id = undef;
836                }
837
838                callback = callback || F; // dirty
839
840                // if already present, return
841                if ( link ) {
842                    callback.call( link, link );
843                    return link;
844                }
845
846                // save the length of stylesheets to check against
847                length = doc.styleSheets.length;
848
849                // check for existing id
850                if( $( '#' + id ).length ) {
851
852                    $( '#' + id ).attr( 'href', href );
853                    length--;
854
855                } else {
856                    link = $( '<link>' ).attr({
857                        rel: 'stylesheet',
858                        href: href,
859                        id: id
860                    }).get(0);
861
862                    var styles = $('link[rel="stylesheet"], style');
863                    if ( styles.length ) {
864                        styles.get(0).parentNode.insertBefore( link, styles[0] );
865                    } else {
866                        DOM().head.appendChild( link );
867                    }
868
869                    if ( IE ) {
870
871                        // IE has a limit of 31 stylesheets in one document
872                        if( length >= 31 ) {
873                            Galleria.raise( 'You have reached the browser stylesheet limit (31)', true );
874                            return;
875                        }
876                    }
877                }
878
879                if ( typeof callback === 'function' ) {
880
881                    // First check for dummy element (new in 1.2.8)
882                    var $loader = $('<s>').attr( 'id', 'galleria-loader' ).hide().appendTo( DOM().body );
883
884                    Utils.wait({
885                        until: function() {
886                            return $loader.height() == 1;
887                        },
888                        success: function() {
889                            $loader.remove();
890                            callback.call( link, link );
891                        },
892                        error: function() {
893                            $loader.remove();
894
895                            // If failed, tell the dev to download the latest theme
896                            Galleria.raise( 'Theme CSS could not load after 20 sec. Please download the latest theme at http://galleria.io/customer/', true );
897
898                        },
899                        timeout: 20000
900                    });
901                }
902                return link;
903            }
904        };
905    }()),
906
907    // the transitions holder
908    _transitions = (function() {
909
910        var _slide = function(params, complete, fade, door) {
911
912            var easing = this.getOptions('easing'),
913                distance = this.getStageWidth(),
914                from = { left: distance * ( params.rewind ? -1 : 1 ) },
915                to = { left: 0 };
916
917            if ( fade ) {
918                from.opacity = 0;
919                to.opacity = 1;
920            } else {
921                from.opacity = 1;
922            }
923
924            $(params.next).css(from);
925
926            Utils.animate(params.next, to, {
927                duration: params.speed,
928                complete: (function( elems ) {
929                    return function() {
930                        complete();
931                        elems.css({
932                            left: 0
933                        });
934                    };
935                }( $( params.next ).add( params.prev ) )),
936                queue: false,
937                easing: easing
938            });
939
940            if (door) {
941                params.rewind = !params.rewind;
942            }
943
944            if (params.prev) {
945
946                from = { left: 0 };
947                to = { left: distance * ( params.rewind ? 1 : -1 ) };
948
949                if ( fade ) {
950                    from.opacity = 1;
951                    to.opacity = 0;
952                }
953
954                $(params.prev).css(from);
955                Utils.animate(params.prev, to, {
956                    duration: params.speed,
957                    queue: false,
958                    easing: easing,
959                    complete: function() {
960                        $(this).css('opacity', 0);
961                    }
962                });
963            }
964        };
965
966        return {
967
968            active: false,
969
970            init: function( effect, params, complete ) {
971                if ( _transitions.effects.hasOwnProperty( effect ) ) {
972                    _transitions.effects[ effect ].call( this, params, complete );
973                }
974            },
975
976            effects: {
977
978                fade: function(params, complete) {
979                    $(params.next).css({
980                        opacity: 0,
981                        left: 0
982                    }).show();
983                    Utils.animate(params.next, {
984                        opacity: 1
985                    },{
986                        duration: params.speed,
987                        complete: complete
988                    });
989                    if (params.prev) {
990                        $(params.prev).css('opacity',1).show();
991                        Utils.animate(params.prev, {
992                            opacity: 0
993                        },{
994                            duration: params.speed
995                        });
996                    }
997                },
998
999                flash: function(params, complete) {
1000                    $(params.next).css({
1001                        opacity: 0,
1002                        left: 0
1003                    });
1004                    if (params.prev) {
1005                        Utils.animate( params.prev, {
1006                            opacity: 0
1007                        },{
1008                            duration: params.speed/2,
1009                            complete: function() {
1010                                Utils.animate( params.next, {
1011                                    opacity:1
1012                                },{
1013                                    duration: params.speed,
1014                                    complete: complete
1015                                });
1016                            }
1017                        });
1018                    } else {
1019                        Utils.animate( params.next, {
1020                            opacity: 1
1021                        },{
1022                            duration: params.speed,
1023                            complete: complete
1024                        });
1025                    }
1026                },
1027
1028                pulse: function(params, complete) {
1029                    if (params.prev) {
1030                        $(params.prev).hide();
1031                    }
1032                    $(params.next).css({
1033                        opacity: 0,
1034                        left: 0
1035                    }).show();
1036                    Utils.animate(params.next, {
1037                        opacity:1
1038                    },{
1039                        duration: params.speed,
1040                        complete: complete
1041                    });
1042                },
1043
1044                slide: function(params, complete) {
1045                    _slide.apply( this, Utils.array( arguments ) );
1046                },
1047
1048                fadeslide: function(params, complete) {
1049                    _slide.apply( this, Utils.array( arguments ).concat( [true] ) );
1050                },
1051
1052                doorslide: function(params, complete) {
1053                    _slide.apply( this, Utils.array( arguments ).concat( [false, true] ) );
1054                }
1055            }
1056        };
1057    }());
1058
1059_nativeFullscreen.listen();
1060
1061/**
1062    The main Galleria class
1063
1064    @class
1065    @constructor
1066
1067    @example var gallery = new Galleria();
1068
1069    @author http://aino.se
1070
1071    @requires jQuery
1072
1073*/
1074
1075Galleria = function() {
1076
1077    var self = this;
1078
1079    // internal options
1080    this._options = {};
1081
1082    // flag for controlling play/pause
1083    this._playing = false;
1084
1085    // internal interval for slideshow
1086    this._playtime = 5000;
1087
1088    // internal variable for the currently active image
1089    this._active = null;
1090
1091    // the internal queue, arrayified
1092    this._queue = { length: 0 };
1093
1094    // the internal data array
1095    this._data = [];
1096
1097    // the internal dom collection
1098    this._dom = {};
1099
1100    // the internal thumbnails array
1101    this._thumbnails = [];
1102
1103    // the internal layers array
1104    this._layers = [];
1105
1106    // internal init flag
1107    this._initialized = false;
1108
1109    // internal firstrun flag
1110    this._firstrun = false;
1111
1112    // global stagewidth/height
1113    this._stageWidth = 0;
1114    this._stageHeight = 0;
1115
1116    // target holder
1117    this._target = undef;
1118
1119    // bind hashes
1120    this._binds = [];
1121
1122    // instance id
1123    this._id = parseInt(Math.random()*10000, 10);
1124
1125    // add some elements
1126    var divs =  'container stage images image-nav image-nav-left image-nav-right ' +
1127                'info info-text info-title info-description ' +
1128                'thumbnails thumbnails-list thumbnails-container thumb-nav-left thumb-nav-right ' +
1129                'loader counter tooltip',
1130        spans = 'current total';
1131
1132    $.each( divs.split(' '), function( i, elemId ) {
1133        self._dom[ elemId ] = Utils.create( 'galleria-' + elemId );
1134    });
1135
1136    $.each( spans.split(' '), function( i, elemId ) {
1137        self._dom[ elemId ] = Utils.create( 'galleria-' + elemId, 'span' );
1138    });
1139
1140    // the internal keyboard object
1141    // keeps reference of the keybinds and provides helper methods for binding keys
1142    var keyboard = this._keyboard = {
1143
1144        keys : {
1145            'UP': 38,
1146            'DOWN': 40,
1147            'LEFT': 37,
1148            'RIGHT': 39,
1149            'RETURN': 13,
1150            'ESCAPE': 27,
1151            'BACKSPACE': 8,
1152            'SPACE': 32
1153        },
1154
1155        map : {},
1156
1157        bound: false,
1158
1159        press: function(e) {
1160            var key = e.keyCode || e.which;
1161            if ( key in keyboard.map && typeof keyboard.map[key] === 'function' ) {
1162                keyboard.map[key].call(self, e);
1163            }
1164        },
1165
1166        attach: function(map) {
1167
1168            var key, up;
1169
1170            for( key in map ) {
1171                if ( map.hasOwnProperty( key ) ) {
1172                    up = key.toUpperCase();
1173                    if ( up in keyboard.keys ) {
1174                        keyboard.map[ keyboard.keys[up] ] = map[key];
1175                    } else {
1176                        keyboard.map[ up ] = map[key];
1177                    }
1178                }
1179            }
1180            if ( !keyboard.bound ) {
1181                keyboard.bound = true;
1182                $doc.bind('keydown', keyboard.press);
1183            }
1184        },
1185
1186        detach: function() {
1187            keyboard.bound = false;
1188            keyboard.map = {};
1189            $doc.unbind('keydown', keyboard.press);
1190        }
1191    };
1192
1193    // internal controls for keeping track of active / inactive images
1194    var controls = this._controls = {
1195
1196        0: undef,
1197
1198        1: undef,
1199
1200        active : 0,
1201
1202        swap : function() {
1203            controls.active = controls.active ? 0 : 1;
1204        },
1205
1206        getActive : function() {
1207            return controls[ controls.active ];
1208        },
1209
1210        getNext : function() {
1211            return controls[ 1 - controls.active ];
1212        }
1213    };
1214
1215    // internal carousel object
1216    var carousel = this._carousel = {
1217
1218        // shortcuts
1219        next: self.$('thumb-nav-right'),
1220        prev: self.$('thumb-nav-left'),
1221
1222        // cache the width
1223        width: 0,
1224
1225        // track the current position
1226        current: 0,
1227
1228        // cache max value
1229        max: 0,
1230
1231        // save all hooks for each width in an array
1232        hooks: [],
1233
1234        // update the carousel
1235        // you can run this method anytime, f.ex on window.resize
1236        update: function() {
1237            var w = 0,
1238                h = 0,
1239                hooks = [0];
1240
1241            $.each( self._thumbnails, function( i, thumb ) {
1242                if ( thumb.ready ) {
1243                    w += thumb.outerWidth || $( thumb.container ).outerWidth( true );
1244                    hooks[ i+1 ] = w;
1245                    h = Math.max( h, thumb.outerHeight || $( thumb.container).outerHeight( true ) );
1246                }
1247            });
1248
1249            self.$( 'thumbnails' ).css({
1250                width: w,
1251                height: h
1252            });
1253
1254            carousel.max = w;
1255            carousel.hooks = hooks;
1256            carousel.width = self.$( 'thumbnails-list' ).width();
1257            carousel.setClasses();
1258
1259            self.$( 'thumbnails-container' ).toggleClass( 'galleria-carousel', w > carousel.width );
1260
1261            // one extra calculation
1262            carousel.width = self.$( 'thumbnails-list' ).width();
1263
1264            // todo: fix so the carousel moves to the left
1265        },
1266
1267        bindControls: function() {
1268
1269            var i;
1270
1271            carousel.next.bind( 'click', function(e) {
1272                e.preventDefault();
1273
1274                if ( self._options.carouselSteps === 'auto' ) {
1275
1276                    for ( i = carousel.current; i < carousel.hooks.length; i++ ) {
1277                        if ( carousel.hooks[i] - carousel.hooks[ carousel.current ] > carousel.width ) {
1278                            carousel.set(i - 2);
1279                            break;
1280                        }
1281                    }
1282
1283                } else {
1284                    carousel.set( carousel.current + self._options.carouselSteps);
1285                }
1286            });
1287
1288            carousel.prev.bind( 'click', function(e) {
1289                e.preventDefault();
1290
1291                if ( self._options.carouselSteps === 'auto' ) {
1292
1293                    for ( i = carousel.current; i >= 0; i-- ) {
1294                        if ( carousel.hooks[ carousel.current ] - carousel.hooks[i] > carousel.width ) {
1295                            carousel.set( i + 2 );
1296                            break;
1297                        } else if ( i === 0 ) {
1298                            carousel.set( 0 );
1299                            break;
1300                        }
1301                    }
1302                } else {
1303                    carousel.set( carousel.current - self._options.carouselSteps );
1304                }
1305            });
1306        },
1307
1308        // calculate and set positions
1309        set: function( i ) {
1310            i = Math.max( i, 0 );
1311            while ( carousel.hooks[i - 1] + carousel.width >= carousel.max && i >= 0 ) {
1312                i--;
1313            }
1314            carousel.current = i;
1315            carousel.animate();
1316        },
1317
1318        // get the last position
1319        getLast: function(i) {
1320            return ( i || carousel.current ) - 1;
1321        },
1322
1323        // follow the active image
1324        follow: function(i) {
1325
1326            //don't follow if position fits
1327            if ( i === 0 || i === carousel.hooks.length - 2 ) {
1328                carousel.set( i );
1329                return;
1330            }
1331
1332            // calculate last position
1333            var last = carousel.current;
1334            while( carousel.hooks[last] - carousel.hooks[ carousel.current ] <
1335                   carousel.width && last <= carousel.hooks.length ) {
1336                last ++;
1337            }
1338
1339            // set position
1340            if ( i - 1 < carousel.current ) {
1341                carousel.set( i - 1 );
1342            } else if ( i + 2 > last) {
1343                carousel.set( i - last + carousel.current + 2 );
1344            }
1345        },
1346
1347        // helper for setting disabled classes
1348        setClasses: function() {
1349            carousel.prev.toggleClass( 'disabled', !carousel.current );
1350            carousel.next.toggleClass( 'disabled', carousel.hooks[ carousel.current ] + carousel.width >= carousel.max );
1351        },
1352
1353        // the animation method
1354        animate: function(to) {
1355            carousel.setClasses();
1356            var num = carousel.hooks[ carousel.current ] * -1;
1357
1358            if ( isNaN( num ) ) {
1359                return;
1360            }
1361
1362            Utils.animate(self.get( 'thumbnails' ), {
1363                left: num
1364            },{
1365                duration: self._options.carouselSpeed,
1366                easing: self._options.easing,
1367                queue: false
1368            });
1369        }
1370    };
1371
1372    // tooltip control
1373    // added in 1.2
1374    var tooltip = this._tooltip = {
1375
1376        initialized : false,
1377
1378        open: false,
1379
1380        timer: 'tooltip' + self._id,
1381
1382        swapTimer: 'swap' + self._id,
1383
1384        init: function() {
1385
1386            tooltip.initialized = true;
1387
1388            var css = '.galleria-tooltip{padding:3px 8px;max-width:50%;background:#ffe;color:#000;z-index:3;position:absolute;font-size:11px;line-height:1.3;' +
1389                      'opacity:0;box-shadow:0 0 2px rgba(0,0,0,.4);-moz-box-shadow:0 0 2px rgba(0,0,0,.4);-webkit-box-shadow:0 0 2px rgba(0,0,0,.4);}';
1390
1391            Utils.insertStyleTag(css);
1392
1393            self.$( 'tooltip' ).css({
1394                opacity: 0.8,
1395                visibility: 'visible',
1396                display: 'none'
1397            });
1398
1399        },
1400
1401        // move handler
1402        move: function( e ) {
1403            var mouseX = self.getMousePosition(e).x,
1404                mouseY = self.getMousePosition(e).y,
1405                $elem = self.$( 'tooltip' ),
1406                x = mouseX,
1407                y = mouseY,
1408                height = $elem.outerHeight( true ) + 1,
1409                width = $elem.outerWidth( true ),
1410                limitY = height + 15;
1411
1412            var maxX = self.$( 'container').width() - width - 2,
1413                maxY = self.$( 'container').height() - height - 2;
1414
1415            if ( !isNaN(x) && !isNaN(y) ) {
1416
1417                x += 10;
1418                y -= 30;
1419
1420                x = Math.max( 0, Math.min( maxX, x ) );
1421                y = Math.max( 0, Math.min( maxY, y ) );
1422
1423                if( mouseY < limitY ) {
1424                    y = limitY;
1425                }
1426
1427                $elem.css({ left: x, top: y });
1428            }
1429        },
1430
1431        // bind elements to the tooltip
1432        // you can bind multiple elementIDs using { elemID : function } or { elemID : string }
1433        // you can also bind single DOM elements using bind(elem, string)
1434        bind: function( elem, value ) {
1435
1436            // todo: revise if alternative tooltip is needed for mobile devices
1437            if (Galleria.TOUCH) {
1438                return;
1439            }
1440
1441            if (! tooltip.initialized ) {
1442                tooltip.init();
1443            }
1444
1445            var mouseout = function() {
1446                self.$( 'container' ).unbind( 'mousemove', tooltip.move );
1447                self.clearTimer( tooltip.timer );
1448
1449                self.$( 'tooltip' ).stop().animate({
1450                    opacity: 0
1451                }, 200, function() {
1452
1453                    self.$( 'tooltip' ).hide();
1454
1455                    self.addTimer( tooltip.swapTimer, function() {
1456                        tooltip.open = false;
1457                    }, 1000);
1458                });
1459            };
1460
1461            var hover = function( elem, value) {
1462
1463                tooltip.define( elem, value );
1464
1465                $( elem ).hover(function() {
1466
1467                    self.clearTimer( tooltip.swapTimer );
1468                    self.$('container').unbind( 'mousemove', tooltip.move ).bind( 'mousemove', tooltip.move ).trigger( 'mousemove' );
1469                    tooltip.show( elem );
1470
1471                    self.addTimer( tooltip.timer, function() {
1472                        self.$( 'tooltip' ).stop().show().animate({
1473                            opacity: 1
1474                        });
1475                        tooltip.open = true;
1476
1477                    }, tooltip.open ? 0 : 500);
1478
1479                }, mouseout).click(mouseout);
1480            };
1481
1482            if ( typeof value === 'string' ) {
1483                hover( ( elem in self._dom ? self.get( elem ) : elem ), value );
1484            } else {
1485                // asume elemID here
1486                $.each( elem, function( elemID, val ) {
1487                    hover( self.get(elemID), val );
1488                });
1489            }
1490        },
1491
1492        show: function( elem ) {
1493
1494            elem = $( elem in self._dom ? self.get(elem) : elem );
1495
1496            var text = elem.data( 'tt' ),
1497                mouseup = function( e ) {
1498
1499                    // attach a tiny settimeout to make sure the new tooltip is filled
1500                    window.setTimeout( (function( ev ) {
1501                        return function() {
1502                            tooltip.move( ev );
1503                        };
1504                    }( e )), 10);
1505
1506                    elem.unbind( 'mouseup', mouseup );
1507
1508                };
1509
1510            text = typeof text === 'function' ? text() : text;
1511
1512            if ( ! text ) {
1513                return;
1514            }
1515
1516            self.$( 'tooltip' ).html( text.replace(/\s/, '&nbsp;') );
1517
1518            // trigger mousemove on mouseup in case of click
1519            elem.bind( 'mouseup', mouseup );
1520        },
1521
1522        define: function( elem, value ) {
1523
1524            // we store functions, not strings
1525            if (typeof value !== 'function') {
1526                var s = value;
1527                value = function() {
1528                    return s;
1529                };
1530            }
1531
1532            elem = $( elem in self._dom ? self.get(elem) : elem ).data('tt', value);
1533
1534            tooltip.show( elem );
1535
1536        }
1537    };
1538
1539    // internal fullscreen control
1540    var fullscreen = this._fullscreen = {
1541
1542        scrolled: 0,
1543
1544        crop: undef,
1545
1546        transition: undef,
1547
1548        active: false,
1549
1550        keymap: self._keyboard.map,
1551
1552        parseCallback: function( callback, enter ) {
1553
1554            return _transitions.active ? function() {
1555                if ( typeof callback == 'function' ) {
1556                    callback();
1557                }
1558                var active = self._controls.getActive(),
1559                    next = self._controls.getNext();
1560
1561                self._scaleImage( next );
1562                self._scaleImage( active );
1563
1564                if ( enter && self._options.trueFullscreen ) {
1565                    // Firefox bug, revise later
1566                    $( active.container ).add( next.container ).trigger( 'transitionend' );
1567                }
1568
1569            } : callback;
1570
1571        },
1572
1573        enter: function( callback ) {
1574
1575            callback = fullscreen.parseCallback( callback, true );
1576
1577            if ( self._options.trueFullscreen && _nativeFullscreen.support ) {
1578                _nativeFullscreen.enter( self, callback );
1579            } else {
1580                fullscreen._enter( callback );
1581            }
1582        },
1583
1584        _enter: function( callback ) {
1585
1586            fullscreen.active = true;
1587
1588            // hide the image until rescale is complete
1589            Utils.hide( self.getActiveImage() );
1590
1591            self.$( 'container' ).addClass( 'fullscreen' );
1592
1593            fullscreen.scrolled = $win.scrollTop();
1594
1595            // begin styleforce
1596            Utils.forceStyles(self.get('container'), {
1597                position: 'fixed',
1598                top: 0,
1599                left: 0,
1600                width: '100%',
1601                height: '100%',
1602                zIndex: 10000
1603            });
1604
1605            var htmlbody = {
1606                    height: '100%',
1607                    overflow: 'hidden',
1608                    margin:0,
1609                    padding:0
1610                },
1611
1612                data = self.getData(),
1613
1614                options = self._options;
1615
1616            Utils.forceStyles( DOM().html, htmlbody );
1617            Utils.forceStyles( DOM().body, htmlbody );
1618
1619            // temporarily attach some keys
1620            // save the old ones first in a cloned object
1621            fullscreen.keymap = $.extend({}, self._keyboard.map);
1622
1623            self.attachKeyboard({
1624                escape: self.exitFullscreen,
1625                right: self.next,
1626                left: self.prev
1627            });
1628
1629            // temporarily save the crop
1630            fullscreen.crop = options.imageCrop;
1631
1632            // set fullscreen options
1633            if ( options.fullscreenCrop != undef ) {
1634                options.imageCrop = options.fullscreenCrop;
1635            }
1636
1637            // swap to big image if it's different from the display image
1638            if ( data && data.big && data.image !== data.big ) {
1639                var big    = new Galleria.Picture(),
1640                    cached = big.isCached( data.big ),
1641                    index  = self.getIndex(),
1642                    thumb  = self._thumbnails[ index ];
1643
1644                self.trigger( {
1645                    type: Galleria.LOADSTART,
1646                    cached: cached,
1647                    rewind: false,
1648                    index: index,
1649                    imageTarget: self.getActiveImage(),
1650                    thumbTarget: thumb,
1651                    galleriaData: data
1652                });
1653
1654                big.load( data.big, function( big ) {
1655                    self._scaleImage( big, {
1656                        complete: function( big ) {
1657                            self.trigger({
1658                                type: Galleria.LOADFINISH,
1659                                cached: cached,
1660                                index: index,
1661                                rewind: false,
1662                                imageTarget: big.image,
1663                                thumbTarget: thumb
1664                            });
1665                            var image = self._controls.getActive().image;
1666                            if ( image ) {
1667                                $( image ).width( big.image.width ).height( big.image.height )
1668                                    .attr( 'style', $( big.image ).attr('style') )
1669                                    .attr( 'src', big.image.src );
1670                            }
1671                        }
1672                    });
1673                });
1674            }
1675
1676            // init the first rescale and attach callbacks
1677            self.rescale(function() {
1678
1679                self.addTimer(false, function() {
1680                    // show the image after 50 ms
1681                    Utils.show( self.getActiveImage() );
1682
1683                    if (typeof callback === 'function') {
1684                        callback.call( self );
1685                    }
1686
1687                }, 100);
1688
1689                self.trigger( Galleria.FULLSCREEN_ENTER );
1690            });
1691
1692            // bind the scaling to the resize event
1693            $win.resize( function() {
1694                fullscreen.scale();
1695            } );
1696        },
1697
1698        scale : function() {
1699            self.rescale();
1700        },
1701
1702        exit: function( callback ) {
1703
1704            callback = fullscreen.parseCallback( callback );
1705
1706            if ( self._options.trueFullscreen && _nativeFullscreen.support ) {
1707                _nativeFullscreen.exit( callback );
1708            } else {
1709                fullscreen._exit( callback );
1710            }
1711        },
1712
1713        _exit: function( callback ) {
1714
1715            fullscreen.active = false;
1716
1717            Utils.hide( self.getActiveImage() );
1718
1719            self.$('container').removeClass( 'fullscreen' );
1720
1721            // revert all styles
1722            Utils.revertStyles( self.get('container'), DOM().html, DOM().body );
1723
1724            // scroll back
1725            window.scrollTo(0, fullscreen.scrolled);
1726
1727            // detach all keyboard events and apply the old keymap
1728            self.detachKeyboard();
1729            self.attachKeyboard( fullscreen.keymap );
1730
1731            // bring back cached options
1732            self._options.imageCrop = fullscreen.crop;
1733            //self._options.transition = fullscreen.transition;
1734
1735            // return to original image
1736            var big = self.getData().big,
1737                image = self._controls.getActive().image;
1738
1739            if ( !self.getData().iframe && image && big && big == image.src ) {
1740
1741                window.setTimeout(function(src) {
1742                    return function() {
1743                        image.src = src;
1744                    };
1745                }( self.getData().image ), 1 );
1746
1747            }
1748
1749            self.rescale(function() {
1750                self.addTimer(false, function() {
1751
1752                    // show the image after 50 ms
1753                    Utils.show( self.getActiveImage() );
1754
1755                    if ( typeof callback === 'function' ) {
1756                        callback.call( self );
1757                    }
1758
1759                    $win.trigger( 'resize' );
1760
1761                }, 50);
1762                self.trigger( Galleria.FULLSCREEN_EXIT );
1763            });
1764
1765
1766            $win.unbind('resize', fullscreen.scale);
1767        }
1768    };
1769
1770    // the internal idle object for controlling idle states
1771    var idle = this._idle = {
1772
1773        trunk: [],
1774
1775        bound: false,
1776
1777        active: false,
1778
1779        add: function(elem, to, from, hide) {
1780            if (!elem) {
1781                return;
1782            }
1783            if (!idle.bound) {
1784                idle.addEvent();
1785            }
1786            elem = $(elem);
1787
1788            if ( typeof from == 'boolean' ) {
1789                hide = from;
1790                from = {};
1791            }
1792
1793            from = from || {};
1794
1795            var extract = {},
1796                style;
1797
1798            for ( style in to ) {
1799                if ( to.hasOwnProperty( style ) ) {
1800                    extract[ style ] = elem.css( style );
1801                }
1802            }
1803
1804            elem.data('idle', {
1805                from: $.extend( extract, from ),
1806                to: to,
1807                complete: true,
1808                busy: false
1809            });
1810
1811            if ( !hide ) {
1812                idle.addTimer();
1813            } else {
1814                elem.css( to );
1815            }
1816            idle.trunk.push(elem);
1817        },
1818
1819        remove: function(elem) {
1820
1821            elem = $(elem);
1822
1823            $.each(idle.trunk, function(i, el) {
1824                if ( el && el.length && !el.not(elem).length ) {
1825                    elem.css( elem.data( 'idle' ).from );
1826                    idle.trunk.splice(i, 1);
1827                }
1828            });
1829
1830            if (!idle.trunk.length) {
1831                idle.removeEvent();
1832                self.clearTimer( idle.timer );
1833            }
1834        },
1835
1836        addEvent : function() {
1837            idle.bound = true;
1838            self.$('container').bind( 'mousemove click', idle.showAll );
1839            if ( self._options.idleMode == 'hover' ) {
1840                self.$('container').bind( 'mouseleave', idle.hide );
1841            }
1842        },
1843
1844        removeEvent : function() {
1845            idle.bound = false;
1846            self.$('container').bind( 'mousemove click', idle.showAll );
1847            if ( self._options.idleMode == 'hover' ) {
1848                self.$('container').unbind( 'mouseleave', idle.hide );
1849            }
1850        },
1851
1852        addTimer : function() {
1853            if( self._options.idleMode == 'hover' ) {
1854                return;
1855            }
1856            self.addTimer( 'idle', function() {
1857                idle.hide();
1858            }, self._options.idleTime );
1859        },
1860
1861        hide : function() {
1862
1863            if ( !self._options.idleMode || self.getIndex() === false || self.getData().iframe ) {
1864                return;
1865            }
1866
1867            self.trigger( Galleria.IDLE_ENTER );
1868
1869            var len = idle.trunk.length;
1870
1871            $.each( idle.trunk, function(i, elem) {
1872
1873                var data = elem.data('idle');
1874
1875                if (! data) {
1876                    return;
1877                }
1878
1879                elem.data('idle').complete = false;
vendor: 9,137 bytes, lines 1880-2124
1880
1881                Utils.animate( elem, data.to, {
1882                    duration: self._options.idleSpeed,
1883                    complete: function() {
1884                        if ( i == len-1 ) {
1885                            idle.active = false;
1886                        }
1887                    }
1888                });
1889            });
1890        },
1891
1892        showAll : function() {
1893
1894            self.clearTimer( 'idle' );
1895
1896            $.each( idle.trunk, function( i, elem ) {
1897                idle.show( elem );
1898            });
1899        },
1900
1901        show: function(elem) {
1902
1903            var data = elem.data('idle');
1904
1905            if ( !idle.active || ( !data.busy && !data.complete ) ) {
1906
1907                data.busy = true;
1908
1909                self.trigger( Galleria.IDLE_EXIT );
1910
1911                self.clearTimer( 'idle' );
1912
1913                Utils.animate( elem, data.from, {
1914                    duration: self._options.idleSpeed/2,
1915                    complete: function() {
1916                        idle.active = true;
1917                        $(elem).data('idle').busy = false;
1918                        $(elem).data('idle').complete = true;
1919                    }
1920                });
1921
1922            }
1923            idle.addTimer();
1924        }
1925    };
1926
1927    // internal lightbox object
1928    // creates a predesigned lightbox for simple popups of images in galleria
1929    var lightbox = this._lightbox = {
1930
1931        width : 0,
1932
1933        height : 0,
1934
1935        initialized : false,
1936
1937        active : null,
1938
1939        image : null,
1940
1941        elems : {},
1942
1943        keymap: false,
1944
1945        init : function() {
1946
1947            // trigger the event
1948            self.trigger( Galleria.LIGHTBOX_OPEN );
1949
1950            if ( lightbox.initialized ) {
1951                return;
1952            }
1953            lightbox.initialized = true;
1954
1955            // create some elements to work with
1956            var elems = 'overlay box content shadow title info close prevholder prev nextholder next counter image',
1957                el = {},
1958                op = self._options,
1959                css = '',
1960                abs = 'position:absolute;',
1961                prefix = 'lightbox-',
1962                cssMap = {
1963                    overlay:    'position:fixed;display:none;opacity:'+op.overlayOpacity+';filter:alpha(opacity='+(op.overlayOpacity*100)+
1964                                ');top:0;left:0;width:100%;height:100%;background:'+op.overlayBackground+';z-index:99990',
1965                    box:        'position:fixed;display:none;width:400px;height:400px;top:50%;left:50%;margin-top:-200px;margin-left:-200px;z-index:99991',
1966                    shadow:     abs+'background:#000;width:100%;height:100%;',
1967                    content:    abs+'background-color:#fff;top:10px;left:10px;right:10px;bottom:10px;overflow:hidden',
1968                    info:       abs+'bottom:10px;left:10px;right:10px;color:#444;font:11px/13px arial,sans-serif;height:13px',
1969                    close:      abs+'top:10px;right:10px;height:20px;width:20px;background:#fff;text-align:center;cursor:pointer;color:#444;font:16px/22px arial,sans-serif;z-index:99999',
1970                    image:      abs+'top:10px;left:10px;right:10px;bottom:30px;overflow:hidden;display:block;',
1971                    prevholder: abs+'width:50%;top:0;bottom:40px;cursor:pointer;',
1972                    nextholder: abs+'width:50%;top:0;bottom:40px;right:-1px;cursor:pointer;',
1973                    prev:       abs+'top:50%;margin-top:-20px;height:40px;width:30px;background:#fff;left:20px;display:none;text-align:center;color:#000;font:bold 16px/36px arial,sans-serif',
1974                    next:       abs+'top:50%;margin-top:-20px;height:40px;width:30px;background:#fff;right:20px;left:auto;display:none;font:bold 16px/36px arial,sans-serif;text-align:center;color:#000',
1975                    title:      'float:left',
1976                    counter:    'float:right;margin-left:8px;'
1977                },
1978                hover = function(elem) {
1979                    return elem.hover(
1980                        function() { $(this).css( 'color', '#bbb' ); },
1981                        function() { $(this).css( 'color', '#444' ); }
1982                    );
1983                },
1984                appends = {};
1985
1986            // IE8 fix for IE's transparent background event "feature"
1987            if ( IE && IE > 7 ) {
1988                cssMap.nextholder += 'background:#000;filter:alpha(opacity=0);';
1989                cssMap.prevholder += 'background:#000;filter:alpha(opacity=0);';
1990            }
1991
1992            // create and insert CSS
1993            $.each(cssMap, function( key, value ) {
1994                css += '.galleria-'+prefix+key+'{'+value+'}';
1995            });
1996
1997            css += '.galleria-'+prefix+'box.iframe .galleria-'+prefix+'prevholder,'+
1998                   '.galleria-'+prefix+'box.iframe .galleria-'+prefix+'nextholder{'+
1999                   'width:100px;height:100px;top:50%;margin-top:-70px}';
2000
2001            Utils.insertStyleTag( css );
2002
2003            // create the elements
2004            $.each(elems.split(' '), function( i, elemId ) {
2005                self.addElement( 'lightbox-' + elemId );
2006                el[ elemId ] = lightbox.elems[ elemId ] = self.get( 'lightbox-' + elemId );
2007            });
2008
2009            // initiate the image
2010            lightbox.image = new Galleria.Picture();
2011
2012            // append the elements
2013            $.each({
2014                    box: 'shadow content close prevholder nextholder',
2015                    info: 'title counter',
2016                    content: 'info image',
2017                    prevholder: 'prev',
2018                    nextholder: 'next'
2019                }, function( key, val ) {
2020                    var arr = [];
2021                    $.each( val.split(' '), function( i, prop ) {
2022                        arr.push( prefix + prop );
2023                    });
2024                    appends[ prefix+key ] = arr;
2025            });
2026
2027            self.append( appends );
2028
2029            $( el.image ).append( lightbox.image.container );
2030
2031            $( DOM().body ).append( el.overlay, el.box );
2032
2033            Utils.optimizeTouch( el.box );
2034
2035            // add the prev/next nav and bind some controls
2036
2037            hover( $( el.close ).bind( 'click', lightbox.hide ).html('&#215;') );
2038
2039            $.each( ['Prev','Next'], function(i, dir) {
2040
2041                var $d = $( el[ dir.toLowerCase() ] ).html( /v/.test( dir ) ? '&#8249;&nbsp;' : '&nbsp;&#8250;' ),
2042                    $e = $( el[ dir.toLowerCase()+'holder'] );
2043
2044                $e.bind( 'click', function() {
2045                    lightbox[ 'show' + dir ]();
2046                });
2047
2048                // IE7 and touch devices will simply show the nav
2049                if ( IE < 8 || Galleria.TOUCH ) {
2050                    $d.show();
2051                    return;
2052                }
2053
2054                $e.hover( function() {
2055                    $d.show();
2056                }, function(e) {
2057                    $d.stop().fadeOut( 200 );
2058                });
2059
2060            });
2061            $( el.overlay ).bind( 'click', lightbox.hide );
2062
2063            // the lightbox animation is slow on ipad
2064            if ( Galleria.IPAD ) {
2065                self._options.lightboxTransitionSpeed = 0;
2066            }
2067
2068        },
2069
2070        rescale: function(event) {
2071
2072            // calculate
2073             var width = Math.min( $win.width()-40, lightbox.width ),
2074                height = Math.min( $win.height()-60, lightbox.height ),
2075                ratio = Math.min( width / lightbox.width, height / lightbox.height ),
2076                destWidth = Math.round( lightbox.width * ratio ) + 40,
2077                destHeight = Math.round( lightbox.height * ratio ) + 60,
2078                to = {
2079                    width: destWidth,
2080                    height: destHeight,
2081                    'margin-top': Math.ceil( destHeight / 2 ) *- 1,
2082                    'margin-left': Math.ceil( destWidth / 2 ) *- 1
2083                };
2084
2085            // if rescale event, don't animate
2086            if ( event ) {
2087                $( lightbox.elems.box ).css( to );
2088            } else {
2089                $( lightbox.elems.box ).animate( to, {
2090                    duration: self._options.lightboxTransitionSpeed,
2091                    easing: self._options.easing,
2092                    complete: function() {
2093                        var image = lightbox.image,
2094                            speed = self._options.lightboxFadeSpeed;
2095
2096                        self.trigger({
2097                            type: Galleria.LIGHTBOX_IMAGE,
2098                            imageTarget: image.image
2099                        });
2100
2101                        $( image.container ).show();
2102
2103                        $( image.image ).animate({ opacity: 1 }, speed);
2104                        Utils.show( lightbox.elems.info, speed );
2105                    }
2106                });
2107            }
2108        },
2109
2110        hide: function() {
2111
2112            // remove the image
2113            lightbox.image.image = null;
2114
2115            $win.unbind('resize', lightbox.rescale);
2116
2117            $( lightbox.elems.box ).hide();
2118
2119            Utils.hide( lightbox.elems.info );
2120
2121            self.detachKeyboard();
2122            self.attachKeyboard( lightbox.keymap );
2123
2124            lightbox.keymap = false;
2125
2126            Utils.hide( lightbox.elems.overlay, 200, function() {
2127                $( this ).hide().css( 'opacity', self._options.overlayOpacity );
2128                self.trigger( Galleria.LIGHTBOX_CLOSE );
2129            });
2130        },
2131
2132        showNext: function() {
2133            lightbox.show( self.getNext( lightbox.active ) );
2134        },
2135
2136        showPrev: function() {
2137            lightbox.show( self.getPrev( lightbox.active ) );
2138        },
2139
2140        show: function(index) {
2141
2142            lightbox.active = index = typeof index === 'number' ? index : self.getIndex() || 0;
2143
2144            if ( !lightbox.initialized ) {
2145                lightbox.init();
2146            }
2147
2148            // temporarily attach some keys
2149            // save the old ones first in a cloned object
2150            if ( !lightbox.keymap ) {
2151
2152                lightbox.keymap = $.extend({}, self._keyboard.map);
2153
2154                self.attachKeyboard({
2155                    escape: lightbox.hide,
2156                    right: lightbox.showNext,
2157                    left: lightbox.showPrev
2158                });
2159            }
2160
2161            $win.unbind('resize', lightbox.rescale );
2162
2163            var data = self.getData(index),
2164                total = self.getDataLength(),
2165                n = self.getNext( index ),
2166                ndata, p, i;
2167
2168            Utils.hide( lightbox.elems.info );
2169
2170            try {
2171                for ( i = self._options.preload; i > 0; i-- ) {
2172                    p = new Galleria.Picture();
2173                    ndata = self.getData( n );
2174                    p.preload( 'big' in ndata ? ndata.big : ndata.image );
2175                    n = self.getNext( n );
2176                }
2177            } catch(e) {}
2178
2179            lightbox.image.isIframe = !!data.iframe;
2180
2181            $(lightbox.elems.box).toggleClass( 'iframe', !!data.iframe );
2182
2183            lightbox.image.load( data.iframe || data.big || data.image, function( image ) {
2184
2185                lightbox.width = image.isIframe ? $(window).width() : image.original.width;
2186                lightbox.height = image.isIframe ? $(window).height() : image.original.height;
2187
2188                $( image.image ).css({
2189                    width: image.isIframe ? '100%' : '100.1%',
2190                    height: image.isIframe ? '100%' : '100.1%',
2191                    top: 0,
2192                    zIndex: 99998,
2193                    opacity: 0,
2194                    visibility: 'visible'
2195                });
2196
2197                lightbox.elems.title.innerHTML = data.title || '';
2198                lightbox.elems.counter.innerHTML = (index + 1) + ' / ' + total;
2199                $win.resize( lightbox.rescale );
2200                lightbox.rescale();
2201            });
2202
2203            $( lightbox.elems.overlay ).show().css( 'visibility', 'visible' );
2204            $( lightbox.elems.box ).show();
2205        }
2206    };
2207
2208    // the internal timeouts object
2209    // provides helper methods for controlling timeouts
2210
2211    var _timer = this._timer = {
2212
2213        trunk: {},
2214
2215        add: function( id, fn, delay, loop ) {
2216            id = id || new Date().getTime();
2217            loop = loop || false;
2218            this.clear( id );
2219            if ( loop ) {
2220                var old = fn;
2221                fn = function() {
2222                    old();
2223                    _timer.add( id, fn, delay );
2224                };
2225            }
2226            this.trunk[ id ] = window.setTimeout( fn, delay );
2227        },
2228
2229        clear: function( id ) {
2230
2231            var del = function( i ) {
2232                window.clearTimeout( this.trunk[ i ] );
2233                delete this.trunk[ i ];
2234            }, i;
2235
2236            if ( !!id && id in this.trunk ) {
2237                del.call( this, id );
2238
2239            } else if ( typeof id === 'undefined' ) {
2240                for ( i in this.trunk ) {
2241                    if ( this.trunk.hasOwnProperty( i ) ) {
2242                        del.call( this, i );
2243                    }
2244                }
2245            }
2246        }
2247    };
2248
2249    return this;
2250};
2251
2252// end Galleria constructor
2253
2254Galleria.prototype = {
2255
2256    // bring back the constructor reference
2257
2258    constructor: Galleria,
2259
2260    /**
2261        Use this function to initialize the gallery and start loading.
2262        Should only be called once per instance.
2263
2264        @param {HTMLElement} target The target element
2265        @param {Object} options The gallery options
2266
2267        @returns Instance
2268    */
2269
2270    init: function( target, options ) {
2271
2272        var self = this;
2273
2274        options = _legacyOptions( options );
2275
2276        // save the original ingredients
2277        this._original = {
2278            target: target,
2279            options: options,
2280            data: null
2281        };
2282
2283        // save the target here
2284        this._target = this._dom.target = target.nodeName ? target : $( target ).get(0);
2285
2286        // save the original content for destruction
2287        this._original.html = this._target.innerHTML;
2288
2289        // push the instance
2290        _instances.push( this );
2291
2292        // raise error if no target is detected
2293        if ( !this._target ) {
2294             Galleria.raise('Target not found', true);
2295             return;
2296        }
2297
2298        // apply options
2299        this._options = {
2300            autoplay: false,
2301            carousel: true,
2302            carouselFollow: true, // legacy, deprecate at 1.3
2303            carouselSpeed: 400,
2304            carouselSteps: 'auto',
2305            clicknext: false,
2306            dailymotion: {
2307                foreground: '%23EEEEEE',
2308                highlight: '%235BCEC5',
2309                background: '%23222222',
2310                logo: 0,
2311                hideInfos: 1
2312            },
2313            dataConfig : function( elem ) { return {}; },
2314            dataSelector: 'img',
2315            dataSort: false,
2316            dataSource: this._target,
2317            debug: undef,
2318            dummy: undef, // 1.2.5
2319            easing: 'galleria',
2320            extend: function(options) {},
2321            fullscreenCrop: undef, // 1.2.5
2322            fullscreenDoubleTap: true, // 1.2.4 toggles fullscreen on double-tap for touch devices
2323            fullscreenTransition: undef, // 1.2.6
2324            height: 0,
2325            idleMode: true, // 1.2.4 toggles idleMode
2326            idleTime: 3000,
2327            idleSpeed: 200,
2328            imageCrop: false,
2329            imageMargin: 0,
2330            imagePan: false,
2331            imagePanSmoothness: 12,
2332            imagePosition: '50%',
2333            imageTimeout: undef, // 1.2.5
2334            initialTransition: undef, // 1.2.4, replaces transitionInitial
2335            keepSource: false,
2336            layerFollow: true, // 1.2.5
2337            lightbox: false, // 1.2.3
2338            lightboxFadeSpeed: 200,
2339            lightboxTransitionSpeed: 200,
2340            linkSourceImages: true,
2341            maxScaleRatio: undef,
2342            minScaleRatio: undef,
2343            overlayOpacity: 0.85,
2344            overlayBackground: '#0b0b0b',
2345            pauseOnInteraction: true,
2346            popupLinks: false,
2347            preload: 2,
2348            queue: true,
2349            responsive: true,
2350            show: 0,
2351            showInfo: true,
2352            showCounter: true,
2353            showImagenav: true,
2354            swipe: true, // 1.2.4
2355            thumbCrop: true,
2356            thumbEventType: 'click',
2357            thumbFit: true, // legacy, deprecate at 1.3
2358            thumbMargin: 0,
2359            thumbQuality: 'auto',
2360            thumbDisplayOrder: true, // 1.2.8
2361            thumbnails: true,
2362            touchTransition: undef, // 1.2.6
2363            transition: 'fade',
2364            transitionInitial: undef, // legacy, deprecate in 1.3. Use initialTransition instead.
2365            transitionSpeed: 400,
2366            trueFullscreen: true, // 1.2.7
2367            useCanvas: false, // 1.2.4
2368            vimeo: {
2369                title: 0,
2370                byline: 0,
2371                portrait: 0,
2372                color: 'aaaaaa'
2373            },
2374            wait: true, // 1.2.7
2375            width: 'auto',
2376            youtube: {
2377                modestbranding: 1,
2378                autohide: 1,
2379                color: 'white',
2380                hd: 1,
2381                rel: 0,
2382                showinfo: 0
2383            }
2384        };
2385
2386        // legacy support for transitionInitial
2387        this._options.initialTransition = this._options.initialTransition || this._options.transitionInitial;
2388
2389        // turn off debug
2390        if ( options && options.debug === false ) {
2391            DEBUG = false;
2392        }
2393
2394        // set timeout
2395        if ( options && typeof options.imageTimeout === 'number' ) {
2396            TIMEOUT = options.imageTimeout;
2397        }
2398
2399        // set dummy
2400        if ( options && typeof options.dummy === 'string' ) {
2401            DUMMY = options.dummy;
2402        }
2403
2404        // hide all content
2405        $( this._target ).children().hide();
2406
2407        // now we just have to wait for the theme...
2408        if ( typeof Galleria.theme === 'object' ) {
2409            this._init();
2410        } else {
2411            // push the instance into the pool and run it when the theme is ready
2412            _pool.push( this );
2413        }
2414
2415        return this;
2416    },
2417
2418    // this method should only be called once per instance
2419    // for manipulation of data, use the .load method
2420
2421    _init: function() {
2422
2423        var self = this,
2424            options = this._options;
2425
2426        if ( this._initialized ) {
2427            Galleria.raise( 'Init failed: Gallery instance already initialized.' );
2428            return this;
2429        }
2430
2431        this._initialized = true;
2432
2433        if ( !Galleria.theme ) {
2434            Galleria.raise( 'Init failed: No theme found.', true );
2435            return this;
2436        }
2437
2438        // merge the theme & caller options
2439        $.extend( true, options, Galleria.theme.defaults, this._original.options, Galleria.configure.options );
2440
2441        // check for canvas support
2442        (function( can ) {
2443
2444            if ( !( 'getContext' in can ) ) {
2445                can = null;
2446                return;
2447            }
2448
2449            _canvas = _canvas || {
2450                elem: can,
2451                context: can.getContext( '2d' ),
2452                cache: {},
2453                length: 0
2454            };
2455
2456        }
vendor: 4,163 bytes, lines 2456-2568
2456( doc.createElement( 'canvas' ) ) );
2457
2458        // bind the gallery to run when data is ready
2459        this.bind( Galleria.DATA, function() {
2460
2461            // Warn for quirks mode
2462            if ( Galleria.QUIRK ) {
2463                Galleria.raise('Your page is in Quirks mode, Galleria may not render correctly. Please validate your HTML.');
2464            }
2465
2466            // save the new data
2467            this._original.data = this._data;
2468
2469            // lets show the counter here
2470            this.get('total').innerHTML = this.getDataLength();
2471
2472            // cache the container
2473            var $container = this.$( 'container' );
2474
2475            // set ratio if height is < 2
2476            if ( self._options.height < 2 ) {
2477                self._ratio = self._options.height;
2478            }
2479
2480            // the gallery is ready, let's just wait for the css
2481            var num = { width: 0, height: 0 };
2482            var testHeight = function() {
2483                return self.$( 'stage' ).height();
2484            };
2485
2486            // check container and thumbnail height
2487            Utils.wait({
2488                until: function() {
2489
2490                    // keep trying to get the value
2491                    num = self._getWH();
2492                    $container.width( num.width ).height( num.height );
2493                    return testHeight() && num.width && num.height > 50;
2494
2495                },
2496                success: function() {
2497
2498                    self._width = num.width;
2499                    self._height = num.height;
2500                    self._ratio = self._ratio || num.height/num.width;
2501
2502                    // for some strange reason, webkit needs a single setTimeout to play ball
2503                    if ( Galleria.WEBKIT ) {
2504                        window.setTimeout( function() {
2505                            self._run();
2506                        }, 1);
2507                    } else {
2508                        self._run();
2509                    }
2510                },
2511                error: function() {
2512
2513                    // Height was probably not set, raise hard errors
2514
2515                    if ( testHeight() ) {
2516                        Galleria.raise('Could not extract sufficient width/height of the gallery container. Traced measures: width:' + num.width + 'px, height: ' + num.height + 'px.', true);
2517                    } else {
2518                        Galleria.raise('Could not extract a stage height from the CSS. Traced height: ' + testHeight() + 'px.', true);
2519                    }
2520                },
2521                timeout: typeof this._options.wait == 'number' ? this._options.wait : false
2522            });
2523        });
2524
2525        // build the gallery frame
2526        this.append({
2527            'info-text' :
2528                ['info-title', 'info-description'],
2529            'info' :
2530                ['info-text'],
2531            'image-nav' :
2532                ['image-nav-right', 'image-nav-left'],
2533            'stage' :
2534                ['images', 'loader', 'counter', 'image-nav'],
2535            'thumbnails-list' :
2536                ['thumbnails'],
2537            'thumbnails-container' :
2538                ['thumb-nav-left', 'thumbnails-list', 'thumb-nav-right'],
2539            'container' :
2540                ['stage', 'thumbnails-container', 'info', 'tooltip']
2541        });
2542
2543        Utils.hide( this.$( 'counter' ).append(
2544            this.get( 'current' ),
2545            doc.createTextNode(' / '),
2546            this.get( 'total' )
2547        ) );
2548
2549        this.setCounter('&#8211;');
2550
2551        Utils.hide( self.get('tooltip') );
2552
2553        // add a notouch class on the container to prevent unwanted :hovers on touch devices
2554        this.$( 'container' ).addClass( Galleria.TOUCH ? 'touch' : 'notouch' );
2555
2556        // add images to the controls
2557        $.each( new Array(2), function( i ) {
2558
2559            // create a new Picture instance
2560            var image = new Galleria.Picture();
2561
2562            // apply some styles, create & prepend overlay
2563            $( image.container ).css({
2564                position: 'absolute',
2565                top: 0,
2566                left: 0
2567            }).prepend( self._layers[i] = $( Utils.create('galleria-layer') ).css({
2568                position: 'absolute',
2569                top:0, left:0, right:0, bottom:0,
2570                zIndex:2
2571            })[0] );
2572
2573            // append the image
2574            self.$( 'images' ).append( image.container );
2575
2576            // reload the controls
2577            self._controls[i] = image;
2578
2579        });
2580
2581        // some forced generic styling
2582        this.$( 'images' ).css({
2583            position: 'relative',
2584            top: 0,
2585            left: 0,
2586            width: '100%',
2587            height: '100%'
2588        });
2589
2590        this.$( 'thumbnails, thumbnails-list' ).css({
2591            overflow: 'hidden',
2592            position: 'relative'
2593        });
2594		//modified by JoomlaShine Team - BEGINNING - Align all images to center
2595		this.$( 'thumbnails' ).css({
2596            margin  : 'auto'
2597        });
2598		//modified by JoomlaShine Team  - END
2599        // bind image navigation arrows
2600        this.$( 'image-nav-right, image-nav-left' ).bind( 'click', function(e) {
2601
2602            // tune the clicknext option
2603            if ( options.clicknext ) {
2604                e.stopPropagation();
2605            }
2606
2607            // pause if options is set
2608            if ( options.pauseOnInteraction ) {
2609                self.pause();
2610            }
2611
2612            // navigate
2613            var fn = /right/.test( this.className ) ? 'next' : 'prev';
2614            self[ fn ]();
2615
2616        });
2617		/******************** Joomlashine dev BEGIN ********************/
2618        // bind information panel
2619        if (self._options.informationPanelClickAction)
2620        {
2621        	var jsntouch = Galleria.TOUCH,
2622            jsnclick = jsntouch ? 'touchstart' : 'click';
2623			this.$('info-text').css({
2624        		cursor: 'pointer'
2625        	}).bind(jsnclick, function(e) {
2626        	var index 	= self.getIndex();	
2627            var link 	= self._data[index].link;
2628         	if (link) 
2629        	{
2630        	  if (self._options.infoPanelPopupLinks) 
2631        	  {
2632        		  win = window.open(link, '_blank');
2633        	  } 
2634        	  else 
2635        	  {
2636        		  window.location.href = link;
2637        	  }
2638        	  return;
2639        	}});
2640        }
2641        /******************** Joomlashine dev END ********************/
2642        // hide controls if chosen to
2643        $.each( ['info','counter','image-nav'], function( i, el ) {
2644            if ( options[ 'show' + el.substr(0,1).toUpperCase() + el.substr(1).replace(/-/,'') ] === false ) {
2645                Utils.moveOut( self.get( el.toLowerCase() ) );
2646            }
2647        });
2648
2649        // load up target content
2650        this.load();
2651
2652        // now it's usually safe to remove the content
2653        // IE will never stop loading if we remove it, so let's keep it hidden for IE (it's usually fast enough anyway)
2654        if ( !options.keepSource && !IE ) {
2655            this._target.innerHTML = '';
2656        }
2657
2658        // re-append the errors, if they happened before clearing
2659        if ( this.get( 'errors' ) ) {
2660            this.appendChild( 'target', 'errors' );
2661        }
2662
2663        // append the gallery frame
2664        this.appendChild( 'target', 'container' );
2665
2666        // parse the carousel on each thumb load
2667        if ( options.carousel ) {
2668            var count = 0,
2669                show = options.show;
2670            this.bind( Galleria.THUMBNAIL, function() {
2671                this.updateCarousel();
2672                if ( ++count == this.getDataLength() && typeof show == 'number' && show > 0 ) {
2673                    this._carousel.follow( show );
2674                }
2675            });
2676        }
2677
2678        // bind window resize for responsiveness
2679        if ( options.responsive ) {
2680            $win.bind( 'resize', function() {
2681                if ( !self.isFullscreen() ) {
2682                    self.resize();
2683                }
2684            });
2685        }
2686
2687        // bind swipe gesture
2688        if ( options.swipe ) {
2689
2690            (function( images ) {
2691
2692                var swipeStart = [0,0],
2693                    swipeStop = [0,0],
2694                    limitX = 30,
2695                    limitY = 100,
2696                    multi = false,
2697                    tid = 0,
2698                    data,
2699                    ev = {
2700                        start: 'touchstart',
2701                        move: 'touchmove',
2702                        stop: 'touchend'
2703                    },
2704                    getData = function(e) {
2705                        return e.originalEvent.touches ? e.originalEvent.touches[0] : e;
2706                    },
2707                    moveHandler = function( e ) {
2708
2709                        if ( e.originalEvent.touches && e.originalEvent.touches.length > 1 ) {
2710                            return;
2711                        }
2712
2713                        data = getData( e );
2714                        swipeStop = [ data.pageX, data.pageY ];
2715
2716                        if ( !swipeStart[0] ) {
2717                            swipeStart = swipeStop;
2718                        }
2719
2720                        if ( Math.abs( swipeStart[0] - swipeStop[0] ) > 10 ) {
2721                            e.preventDefault();
2722                        }
2723                    },
2724                    upHandler = function( e ) {
2725
2726                        images.unbind( ev.move, moveHandler );
2727
2728                        // if multitouch (possibly zooming), abort
2729                        if ( ( e.originalEvent.touches && e.originalEvent.touches.length ) || multi ) {
2730                            multi = !multi;
2731                            return;
2732                        }
2733
2734                        if ( Utils.timestamp() - tid < 1000 &&
2735                             Math.abs( swipeStart[0] - swipeStop[0] ) > limitX &&
2736                             Math.abs( swipeStart[1] - swipeStop[1] ) < limitY ) {
2737
2738                            e.preventDefault();
2739                            self[ swipeStart[0] > swipeStop[0] ? 'next' : 'prev' ]();
2740                        }
2741
2742                        swipeStart = swipeStop = [0,0];
2743                    };
2744
2745                images.bind(ev.start, function(e) {
2746
2747                    if ( e.originalEvent.touches && e.originalEvent.touches.length > 1 ) {
2748                        return;
2749                    }
2750
2751                    data = getData(e);
2752                    tid = Utils.timestamp();
2753                    swipeStart = swipeStop = [ data.pageX, data.pageY ];
2754                    images.bind(ev.move, moveHandler ).one(ev.stop, upHandler);
2755
2756                });
2757
2758            }( self.$( 'images' ) ));
2759
2760            // double-tap/click fullscreen toggle
2761
2762            if ( options.fullscreenDoubleTap ) {
2763
2764                this.$( 'stage' ).bind( 'touchstart', (function() {
2765                    var last, cx, cy, lx, ly, now,
2766                        getData = function(e) {
2767                            return e.originalEvent.touches ? e.originalEvent.touches[0] : e;
2768                        };
2769                    return function(e) {
2770                        now = Galleria.utils.timestamp();
2771                        cx = getData(e).pageX;
2772                        cy = getData(e).pageY;
2773                        if ( ( now - last < 500 ) && ( cx - lx < 20) && ( cy - ly < 20) ) {
2774                            self.toggleFullscreen();
2775                            e.preventDefault();
2776                            self.$( 'stage' ).unbind( 'touchend', arguments.callee );
2777                            return;
2778                        }
2779                        last = now;
2780                        lx = cx;
2781                        ly = cy;
2782                    };
2783                }()));
2784            }
2785
2786        }
2787
2788        // optimize touch for container
2789        Utils.optimizeTouch( this.get( 'container' ) );
2790
2791        // bind the ons
2792        $.each( Galleria.on.binds, function(i, bind) {
2793            // check if already bound
2794            if ( $.inArray( bind.hash, self._binds ) == -1 ) {
2795                self.bind( bind.type, bind.callback );
2796            }
2797        });
2798
2799        return this;
2800    },
2801
2802    addTimer : function() {
2803        this._timer.add.apply( this._timer, Utils.array( arguments ) );
2804        return this;
2805    },
2806
2807    clearTimer : function() {
2808        this._timer.clear.apply( this._timer, Utils.array( arguments ) );
2809        return this;
2810    },
2811
2812    // parse width & height from CSS or options
2813
2814    _getWH : function() {
2815
2816        var $container = this.$( 'container' ),
2817            $target = this.$( 'target' ),
2818            self = this,
2819            num = {},
2820            arr;
2821
2822        $.each(['width', 'height'], function( i, m ) {
2823
2824            // first check if options is set
2825            if ( self._options[ m ] && typeof self._options[ m ] === 'number') {
2826                num[ m ] = self._options[ m ];
2827            } else {
2828
2829                arr = [
2830                    Utils.parseValue( $container.css( m ) ),         // the container css height
2831                    Utils.parseValue( $target.css( m ) ),            // the target css height
2832                    $container[ m ](),                               // the container jQuery method
2833                    $target[ m ]()                                   // the target jQuery method
2834                ];
2835
2836                // if first time, include the min-width & min-height
2837                if ( !self[ '_'+m ] ) {
2838                    arr.splice(arr.length,
2839                        Utils.parseValue( $container.css( 'min-'+m ) ),
2840                        Utils.parseValue( $target.css( 'min-'+m ) )
2841                    );
2842                }
2843
2844                // else extract the measures from different sources and grab the highest value
2845                num[ m ] = Math.max.apply( Math, arr );
2846            }
2847        });
2848
2849        // allow setting a height ratio instead of exact value
2850        // useful when doing responsive galleries
2851
2852		if ($win.width() < 1024)
2853		{
2854			if (num.height > num.width)
2855			{
2856				self._ratio = num.width/num.height;
2857			}
2858			else
2859			{
2860				self._ratio = num.height/num.width;
2861			}
2862		}
2863		else
2864		{
2865			self._ratio = num.height/num.width;
2866		}
2867			
2868        if ( self._ratio ) {
2869            num.height = num.width * self._ratio;
2870        }
2871
2872        return num;
2873    },
2874
2875    // Creates the thumbnails and carousel
2876    // can be used at any time, f.ex when the data object is manipulated
2877    // push is an optional argument with pushed images
2878
2879    _createThumbnails : function( push ) {
2880
2881        this.get( 'total' ).innerHTML = this.getDataLength();
2882
2883        var src,
2884            thumb,
2885            data,
2886            special,
2887
2888            $container,
2889
2890            self = this,
2891            o = this._options,
2892
2893            i = push ? this._data.length - push.length : 0,
2894            chunk = i,
2895
2896            thumbchunk = [],
2897            loadindex = 0,
2898
2899            gif = IE < 8 ? 'http://upload.wikimedia.org/wikipedia/commons/c/c0/Blank.gif' :
2900                           'data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw%3D%3D',
2901
2902            // get previously active thumbnail, if exists
2903            active = (function() {
2904                var a = self.$('thumbnails').find('.active');
2905                if ( !a.length ) {
2906                    return false;
2907                }
2908                return a.find('img').attr('src');
2909            }()),
2910
2911            // cache the thumbnail option
2912            optval = typeof o.thumbnails === 'string' ? o.thumbnails.toLowerCase() : null,
2913
2914            // move some data into the instance
2915            // for some reason, jQuery cant handle css(property) when zooming in FF, breaking the gallery
2916            // so we resort to getComputedStyle for browsers who support it
2917            getStyle = function( prop ) {
2918                return doc.defaultView && doc.defaultView.getComputedStyle ?
2919                    doc.defaultView.getComputedStyle( thumb.container, null )[ prop ] :
2920                    $container.css( prop );
2921            },
2922
2923            fake = function(image, index, container) {
2924                return function() {
2925                    $( container ).append( image );
2926                    self.trigger({
2927                        type: Galleria.THUMBNAIL,
2928                        thumbTarget: image,
2929                        index: index,
2930                        galleriaData: self.getData( index )
2931                    });
2932                };
2933            },
2934
2935            onThumbEvent = function( e ) {
2936
2937                // pause if option is set
2938                if ( o.pauseOnInteraction ) {
2939                    self.pause();
2940                }
2941
2942                // extract the index from the data
2943                var index = $( e.currentTarget ).data( 'index' );
2944                if ( self.getIndex() !== index ) {
2945                    self.show( index );
2946                }
2947
2948                e.preventDefault();
2949            },
2950
2951            thumbComplete = function( thumb, callback ) {
2952
2953                $( thumb.container ).css( 'visibility', 'visible' );
2954                self.trigger({
2955                    type: Galleria.THUMBNAIL,
2956                    thumbTarget: thumb.image,
2957                    index: thumb.data.order,
2958                    galleriaData: self.getData( thumb.data.order )
2959                });
2960
2961                if ( typeof callback == 'function' ) {
2962                    callback.call( self, thumb );
2963                }
2964            },
2965
2966            onThumbLoad = function( thumb, callback ) {
2967
2968                // scale when ready
2969                thumb.scale({
2970                    width:    thumb.data.width,
2971                    height:   thumb.data.height,
2972                    crop:     o.thumbCrop,
2973                    margin:   o.thumbMargin,
2974                    canvas:   o.useCanvas,
2975                    complete: function( thumb ) {
2976
2977                        // shrink thumbnails to fit
2978                        var top = ['left', 'top'],
2979                            arr = ['Width', 'Height'],
2980                            m,
2981                            css,
2982                            data = self.getData( thumb.index ),
2983                            special = data.thumb.split(':');
2984
2985                        // calculate shrinked positions
2986                        $.each(arr, function( i, measure ) {
2987                            m = measure.toLowerCase();
2988                            if ( (o.thumbCrop !== true || o.thumbCrop === m ) && o.thumbFit ) {
2989                                css = {};
2990                                css[ m ] = thumb[ m ];
2991                                $( thumb.container ).css( css );
2992                                css = {};
2993                                css[ top[ i ] ] = 0;
2994                                $( thumb.image ).css( css );
2995                            }
2996
2997                            // cache outer measures
2998                            thumb[ 'outer' + measure ] = $( thumb.container )[ 'outer' + measure ]( true );
2999                        });
3000
3001                        // set high quality if downscale is moderate
3002                        Utils.toggleQuality( thumb.image,
3003                            o.thumbQuality === true ||
3004                            ( o.thumbQuality === 'auto' && thumb.original.width < thumb.width * 3 )
3005                        );
3006
3007                        // get "special" thumbs from provider
3008                        if( data.iframe && special.length == 2 && special[0] in _video ) {
3009
3010                            _video[ special[0] ].getThumb( special[1], (function(img) {
3011                                return function(src) {
3012                                    img.src = src;
3013                                    thumbComplete( thumb, callback );
3014                                };
3015                            }( thumb.image ) ));
3016
3017                        } else if ( o.thumbDisplayOrder && !thumb.lazy ) {
3018
3019                            $.each( thumbchunk, function( i, th ) {
3020                                if ( i === loadindex && th.ready && !th.displayed ) {
3021
3022                                    loadindex++;
3023                                    th.displayed = true;
3024
3025                                    thumbComplete( th, callback );
3026
3027                                    return;
3028                                }
3029                            });
3030                        } else {
3031                            thumbComplete( thumb, callback );
3032                        }
3033                    }
3034                });
3035            };
3036
3037        if ( !push ) {
3038            this._thumbnails = [];
3039            this.$( 'thumbnails' ).empty();
3040        }
3041
3042        // loop through data and create thumbnails
3043        for( ; this._data[ i ]; i++ ) {
3044
3045            data = this._data[ i ];
3046
3047            // get source from thumb or image
3048            src = data.thumb || data.image;
3049
3050            if ( ( o.thumbnails === true || optval == 'lazy' ) && ( data.thumb || data.image ) ) {
3051
3052                // add a new Picture instance
3053                thumb = new Galleria.Picture(i);
3054
3055                // save the index
3056                thumb.index = i;
3057
3058                // flag displayed
3059                thumb.displayed = false;
3060
3061                // flag lazy
3062                thumb.lazy = false;
3063
3064                // flag video
3065                thumb.video = false;
3066
3067                // append the thumbnail
3068                this.$( 'thumbnails' ).append( thumb.container );
3069
3070                // cache the container
3071                $container = $( thumb.container );
3072
3073                // hide it
3074                $container.css( 'visibility', 'hidden' );
3075
3076                thumb.data = {
3077                    width  : Utils.parseValue( getStyle( 'width' ) ),
3078                    height : Utils.parseValue( getStyle( 'height' ) ),
3079                    order  : i,
3080                    src    : src
3081                };
3082
3083                // grab & reset size for smoother thumbnail loads
3084                if ( o.thumbFit && o.thumbCrop !== true ) {
3085                    $container.css( { width: 'auto', height: 'auto' } );
3086                } else {
3087                    $container.css( { width: thumb.data.width, height: thumb.data.height } );
3088                }
3089
3090                // load the thumbnail
3091                special = src.split(':');
3092
3093                if ( special.length == 2 && special[0] in _video ) {
3094
3095                    thumb.video = true;
3096                    thumb.ready = true;
3097
3098                    thumb.load( gif, {
3099                        height: thumb.data.height,
3100                        width: thumb.data.height*1.25
3101                    }, onThumbLoad);
3102
3103                } else if ( optval == 'lazy' ) {
3104
3105                    $container.addClass( 'lazy' );
3106
3107                    thumb.lazy = true;
3108
3109                    thumb.load( gif, {
3110                        height: thumb.data.height,
3111                        width: thumb.data.width
3112                    });
3113
3114                } else {
3115                    thumb.load( src, onThumbLoad );
3116                }
3117
3118                // preload all images here
3119                if ( o.preload === 'all' ) {
3120                    thumb.preload( data.image );
3121                }
3122
3123            // create empty spans if thumbnails is set to 'empty'
3124            } else if ( data.iframe || optval === 'empty' || optval === 'numbers' ) {
3125
3126                thumb = {
3127                    container:  Utils.create( 'galleria-image' ),
3128                    image: Utils.create( 'img', 'span' ),
3129                    ready: true
3130                };
3131
3132                // create numbered thumbnails
3133                if ( optval === 'numbers' ) {
3134                    $( thumb.image ).text( i + 1 );
3135                }
3136
3137                if ( data.iframe ) {
3138                    $( thumb.image ).addClass( 'iframe' );
3139                }
3140
3141                this.$( 'thumbnails' ).append( thumb.container );
3142
3143                // we need to "fake" a loading delay before we append and trigger
3144                // 50+ should be enough
3145
3146                window.setTimeout( ( fake )( thumb.image, i, thumb.container ), 50 + ( i*20 ) );
3147
3148            // create null object to silent errors
3149            } else {
3150                thumb = {
3151                    container: null,
3152                    image: null
3153                };
3154            }
3155
3156            // add events for thumbnails
3157            // you can control the event type using thumb_event_type
3158            // we'll add the same event to the source if it's kept
3159
3160            $( thumb.container ).add( o.keepSource && o.linkSourceImages ? data.original : null )
3161                .data('index', i).bind( o.thumbEventType, onThumbEvent )
3162                .data('thumbload', onThumbLoad);
3163
3164            if (active === src) {
3165                $( thumb.container ).addClass( 'active' );
3166            }
3167
3168            this._thumbnails.push( thumb );
3169        }
3170
3171        thumbchunk = this._thumbnails.slice( chunk );
3172
3173        return this;
3174    },
3175
3176    /**
3177        Lazy-loads thumbnails.
3178        You can call this method to load lazy thumbnails at run time
3179
3180        @param {Array|Number} index Index or array of indexes of thumbnails to be loaded
3181        @param {Function} complete Callback that is called when all lazy thumbnails have been loaded
3182
3183        @returns Instance
3184    */
3185
3186    lazyLoad: function( index, complete ) {
3187
3188        var arr = index.constructor == Array ? index : [ index ],
3189            self = this,
3190            thumbnails = this.$( 'thumbnails' ).children().filter(function() {
3191                return $(this).data('lazy-src');
3192            }),
3193            loaded = 0;
3194
3195        $.each( arr, function(i, ind) {
3196
3197            if ( ind > self._thumbnails.length - 1 ) {
3198                return;
3199            }
3200
3201            var thumb = self._thumbnails[ ind ],
3202                data = thumb.data,
3203                special = data.src.split(':'),
3204                callback = function() {
3205                    if ( ++loaded == arr.length && typeof complete == 'function' ) {
3206                        complete.call( self );
3207                    }
3208                },
3209                thumbload = $( thumb.container ).data( 'thumbload' );
3210            if ( thumb.video ) {
3211                thumbload.call( self, thumb, callback );
3212            } else {
3213                thumb.load( data.src , function( thumb ) {
3214                    thumbload.call( self, thumb, callback );
3215                });
3216            }
3217        });
3218
3219        return this;
3220
3221    },
3222
3223    /**
3224        Lazy-loads thumbnails in chunks.
3225        This method automatcally chops up the loading process of many thumbnails into chunks
3226
3227        @param {Number} size Size of each chunk to be loaded
3228        @param {Number} [delay] Delay between each loads
3229
3230        @returns Instance
3231    */
3232
3233    lazyLoadChunks: function( size, delay ) {
3234
3235        var len = this.getDataLength(),
3236            i = 0,
3237            n = 0,
3238            arr = [],
3239            temp = [],
3240            self = this;
3241
vendor: 6,369 bytes, lines 3242-3447
3242        delay = delay || 0;
3243
3244        for( ; i<len; i++ ) {
3245            temp.push(i);
3246            if ( ++n == size || i == len-1 ) {
3247                arr.push( temp );
3248                n = 0;
3249                temp = [];
3250            }
3251        }
3252
3253        var init = function( wait ) {
3254            var a = arr.shift();
3255            if ( a ) {
3256                window.setTimeout(function() {
3257                    self.lazyLoad(a, function() {
3258                        init( true );
3259                    });
3260                }, ( delay && wait ) ? delay : 0 );
3261            }
3262        };
3263
3264        init( false );
3265
3266        return this;
3267
3268    },
3269
3270    // the internal _run method should be called after loading data into galleria
3271    // makes sure the gallery has proper measurements before postrun & ready
3272    _run : function() {
3273
3274        var self = this;
3275
3276        self._createThumbnails();
3277
3278        // make sure we have a stageHeight && stageWidth
3279
3280        Utils.wait({
3281
3282            timeout: 10000,
3283
3284            until: function() {
3285
3286                // Opera crap
3287                if ( Galleria.OPERA ) {
3288                    self.$( 'stage' ).css( 'display', 'inline-block' );
3289                }
3290
3291                self._stageWidth  = self.$( 'stage' ).width();
3292                self._stageHeight = self.$( 'stage' ).height();
3293
3294                return( self._stageWidth &&
3295                        self._stageHeight > 50 ); // what is an acceptable height?
3296            },
3297
3298            success: function() {
3299
3300                // save the instance
3301                _galleries.push( self );
3302
3303                // postrun some stuff after the gallery is ready
3304
3305                // show counter
3306                Utils.show( self.get('counter') );
3307
3308                // bind carousel nav
3309                if ( self._options.carousel ) {
3310                    self._carousel.bindControls();
3311                }
3312
3313                // start autoplay
3314                if ( self._options.autoplay ) {
3315
3316                    self.pause();
3317
3318                    if ( typeof self._options.autoplay === 'number' ) {
3319                        self._playtime = self._options.autoplay;
3320                    }
3321
3322                    self._playing = true;
3323                }
3324                // if second load, just do the show and return
3325                if ( self._firstrun ) {
3326
3327                    if ( self._options.autoplay ) {
3328                        self.trigger( Galleria.PLAY );
3329                    }
3330
3331                    if ( typeof self._options.show === 'number' ) {
3332                        self.show( self._options.show );
3333                    }
3334                    return;
3335                }
3336
3337                self._firstrun = true;
3338
3339                // initialize the History plugin
3340                if ( Galleria.History ) {
3341
3342                    // bind the show method
3343                    Galleria.History.change(function( value ) {
3344
3345                        // if ID is NaN, the user pressed back from the first image
3346                        // return to previous address
3347                        if ( isNaN( value ) ) {
3348                            window.history.go(-1);
3349
3350                        // else show the image
3351                        } else {
3352                            self.show( value, undef, true );
3353                        }
3354                    });
3355                }
3356
3357                self.trigger( Galleria.READY );
3358
3359                // call the theme init method
3360                Galleria.theme.init.call( self, self._options );
3361
3362                // Trigger Galleria.ready
3363                $.each( Galleria.ready.callbacks, function(i ,fn) {
3364                    if ( typeof fn == 'function' ) {
3365                        fn.call( self, self._options );
3366                    }
3367                });
3368
3369                // call the extend option
3370                self._options.extend.call( self, self._options );
3371
3372                // show the initial image
3373                // first test for permalinks in history
3374                if ( /^[0-9]{1,4}$/.test( HASH ) && Galleria.History ) {
3375                    self.show( HASH, undef, true );
3376
3377                } else if( self._data[ self._options.show ] ) {
3378                    self.show( self._options.show );
3379                }
3380
3381                // play trigger
3382                if ( self._options.autoplay ) {
3383                    self.trigger( Galleria.PLAY );
3384                }
3385            },
3386
3387            error: function() {
3388                Galleria.raise('Stage width or height is too small to show the gallery. Traced measures: width:' + self._stageWidth + 'px, height: ' + self._stageHeight + 'px.', true);
3389            }
3390
3391        });
3392    },
3393
3394    /**
3395        Loads data into the gallery.
3396        You can call this method on an existing gallery to reload the gallery with new data.
3397
3398        @param {Array|string} [source] Optional JSON array of data or selector of where to find data in the document.
3399        Defaults to the Galleria target or dataSource option.
3400
3401        @param {string} [selector] Optional element selector of what elements to parse.
3402        Defaults to 'img'.
3403
3404        @param {Function} [config] Optional function to modify the data extraction proceedure from the selector.
3405        See the dataConfig option for more information.
3406
3407        @returns Instance
3408    */
3409
3410    load : function( source, selector, config ) {
3411
3412        var self = this,
3413            o = this._options;
3414
3415        // empty the data array
3416        this._data = [];
3417
3418        // empty the thumbnails
3419        this._thumbnails = [];
3420        this.$('thumbnails').empty();
3421
3422        // shorten the arguments
3423        if ( typeof selector === 'function' ) {
3424            config = selector;
3425            selector = null;
3426        }
3427
3428        // use the source set by target
3429        source = source || o.dataSource;
3430
3431        // use selector set by option
3432        selector = selector || o.dataSelector;
3433
3434        // use the dataConfig set by option
3435        config = config || o.dataConfig;
3436
3437        // if source is a true object, make it into an array
3438        if( /^function Object/.test( source.constructor ) ) {
3439            source = [source];
3440        }
3441
3442        // check if the data is an array already
3443        if ( source.constructor === Array ) {
3444            if ( this.validate( source ) ) {
3445                this._data = source;
3446            } else {
3447                Galleria.raise( 'Load failed: JSON Array not val
3447id.' );
3448            }
3449        } else {
3450
3451            // add .video and .iframe to the selector (1.2.7)
3452            selector += ',.video,.iframe';
3453
3454            // loop through images and set data
3455            $( source ).find( selector ).each( function( i, elem ) {
3456
3457                elem = $( elem );
3458                var data = {},
3459                    parent = elem.parent(),
3460                    href = parent.attr( 'href' ),
3461                    rel  = parent.attr( 'rel' );
3462
3463                if( href && ( elem[0].nodeName == 'IMG' || elem.hasClass('video') ) && _videoTest( href ) ) {
3464                    data.video = href;
3465                } else if( href && elem.hasClass('iframe') ) {
3466                    data.iframe = href;
3467                } else {
3468                    data.image = data.big = href;
3469                }
3470
3471                if ( rel ) {
3472                    data.big = rel;
3473                }
3474
3475                // alternative extraction from HTML5 data attribute, added in 1.2.7
3476                $.each( 'big title description link layer'.split(' '), function( i, val ) {
3477                    if ( elem.data(val) ) {
3478                        data[ val ] = elem.data(val);
3479                    }
3480                });
3481
3482                // mix default extractions with the hrefs and config
3483                // and push it into the data array
3484                self._data.push( $.extend({
3485
3486                    title:       elem.attr('title') || '',
3487                    thumb:       elem.attr('src'),
3488                    image:       elem.attr('src'),
3489                    big:         elem.attr('src'),
3490                    description: elem.attr('shortdesc') || '',
3491                    link:        elem.attr('longdesc'),
3492                    original:    elem.get(0) // saved as a reference
3493
3494                }, data, config( elem ) ) );
3495
3496            });
3497        }
3498
3499        if ( typeof o.dataSort == 'function' ) {
3500            protoArray.sort.call( this._data, o.dataSort );
3501        } else if ( o.dataSort == 'random' ) {
3502            this._data.sort( function() {
3503                return Math.round(Math.random())-0.5;
3504            });
3505        }
3506
3507
3508
3509        // trigger the DATA event and return
3510        if ( this.getDataLength() ) {
3511            this._parseData().trigger( Galleria.DATA );
3512        }
3513        return this;
3514
3515    },
3516
3517    // make sure the data works properly
3518    _parseData : function() {
3519
3520        var self = this,
3521            current;
3522
3523        $.each( this._data, function( i, data ) {
3524
3525            current = self._data[ i ];
3526
3527            // copy image as thumb if no thumb exists
3528            if ( 'thumb' in data === false ) {
3529                current.thumb = data.image;
3530            }
3531            // copy image as big image if no biggie exists
3532            if ( !'big' in data ) {
3533                current.big = data.image;
3534            }
3535            // parse video
3536            if ( 'video' in data ) {
3537                var result = _videoTest( data.video );
3538
3539                if ( result ) {
3540                    current.iframe = _video[ result.provider ].embed( result.id ) + (function() {
3541
3542                        // add options
3543                        if ( typeof self._options[ result.provider ] == 'object' ) {
3544                            var str = '?', arr = [];
3545                            $.each( self._options[ result.provider ], function( key, val ) {
3546                                arr.push( key + '=' + val );
3547                            });
3548
3549                            // small youtube specifics, perhaps move to _video later
3550                            if ( result.provider == 'youtube' ) {
3551                                arr = ['wmode=opaque'].concat(arr);
3552                            }
3553                            return str + arr.join('&');
3554                        }
3555                        return '';
3556                    }());
3557                    delete current.video;
3558                    if( !('thumb' in current) || !current.thumb ) {
3559                        current.thumb = result.provider+':'+result.id;
3560                    }
3561                }
3562            }
3563        });
3564
3565        return this;
3566    },
3567
3568    /**
3569        Destroy the Galleria instance and recover the original content
3570
3571        @example this.destroy();
3572
3573        @returns Instance
3574    */
3575
3576    destroy : function() {
3577        this.get('target').innerHTML = this._original.html;
3578        this.clearTimer();
3579        return this;
3580    },
3581
3582    /**
3583        Adds and/or removes images from the gallery
3584        Works just like Array.splice
3585        https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/splice
3586
3587        @example this.splice( 2, 4 ); // removes 4 images after the second image
3588
3589        @returns Instance
3590    */
3591
3592    splice : function() {
3593        var self = this,
3594            args = Utils.array( arguments );
3595        window.setTimeout(function() {
3596            protoArray.splice.apply( self._data, args );
3597            self._parseData()._createThumbnails();
3598        },2);
3599        return self;
3600    },
3601
3602    /**
3603        Append images to the gallery
3604        Works just like Array.push
3605        https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/push
3606
3607        @example this.push({ image: 'image1.jpg' }); // appends the image to the gallery
3608
3609        @returns Instance
3610    */
3611
3612    push : function() {
3613        var self = this,
3614            args = Utils.array( arguments );
3615
3616        if ( args.length == 1 && args[0].constructor == Array ) {
3617            args = args[0];
3618        }
3619
3620        window.setTimeout(function() {
3621            protoArray.push.apply( self._data, args );
3622            self._parseData()._createThumbnails( args );
3623        },2);
3624        return self;
3625    },
3626
3627    _getActive: function() {
3628        return this._controls.getActive();
3629    },
3630
3631    validate : function( data ) {
3632        // todo: validate a custom data array
3633        return true;
3634    },
3635
3636    /**
3637        Bind any event to Galleria
3638
3639        @param {string} type The Event type to listen for
3640        @param {Function} fn The function to execute when the event is triggered
3641
3642        @example this.bind( 'image', function() { Galleria.log('image shown') });
3643
3644        @returns Instance
3645    */
3646
3647    bind : function(type, fn) {
3648
3649        // allow 'image' instead of Galleria.IMAGE
3650        type = _patchEvent( type );
3651
3652        this.$( 'container' ).bind( type, this.proxy(fn) );
3653        return this;
3654    },
3655
3656    /**
3657        Unbind any event to Galleria
3658
3659        @param {string} type The Event type to forget
3660
3661        @returns Instance
3662    */
3663
3664    unbind : function(type) {
3665
3666        type = _patchEvent( type );
3667
3668        this.$( 'container' ).unbind( type );
3669        return this;
3670    },
3671
3672    /**
3673        Manually trigger a Galleria event
3674
3675        @param {string} type The Event to trigger
3676
3677        @returns Instance
3678    */
3679
3680    trigger : function( type ) {
3681
3682        type = typeof type === 'object' ?
3683            $.extend( type, { scope: this } ) :
3684            { type: _patchEvent( type ), scope: this };
3685
3686        this.$( 'container' ).trigger( type );
3687
3688        return this;
3689    },
3690
3691    /**
3692        Assign an "idle state" to any element.
3693        The idle state will be applied after a certain amount of idle time
3694        Useful to hide f.ex navigation when the gallery is inactive
3695
3696        @param {HTMLElement|string} elem The Dom node or selector to apply the idle state to
3697        @param {Object} styles the CSS styles to apply when in idle mode
3698        @param {Object} [from] the CSS styles to apply when in normal
3699        @param {Boolean} [hide] set to true if you want to hide it first
3700
3701        @example addIdleState( this.get('image-nav'), { opacity: 0 });
3702        @example addIdleState( '.galleria-image-nav', { top: -200 }, true);
3703
3704        @returns Instance
3705    */
3706
3707    addIdleState: function( elem, styles, from, hide ) {
3708        this._idle.add.apply( this._idle, Utils.array( arguments ) );
3709        return this;
3710    },
3711
3712    /**
3713        Removes any idle state previously set using addIdleState()
3714
3715        @param {HTMLElement|string} elem The Dom node or selector to remove the idle state from.
3716
3717        @returns Instance
3718    */
3719
3720    removeIdleState: function( elem ) {
3721        this._idle.remove.apply( this._idle, Utils.array( arguments ) );
3722        return this;
3723    },
3724
3725    /**
3726        Force Galleria to enter idle mode.
3727
3728        @returns Instance
3729    */
3730
3731    enterIdleMode: function() {
3732        this._idle.hide();
3733        return this;
3734    },
3735
3736    /**
3737        Force Galleria to exit idle mode.
3738
3739        @returns Instance
3740    */
3741
3742    exitIdleMode: function() {
3743        this._idle.showAll();
3744        return this;
3745    },
3746
3747    /**
3748        Enter FullScreen mode
3749
3750        @param {Function} callback the function to be executed when the fullscreen mode is fully applied.
3751
3752        @returns Instance
3753    */
3754
3755    enterFullscreen: function( callback ) {
3756        this._fullscreen.enter.apply( this, Utils.array( arguments ) );
3757        return this;
3758    },
3759
3760    /**
3761        Exits FullScreen mode
3762
3763        @param {Function} callback the function to be executed when the fullscreen mode is fully applied.
3764
3765        @returns Instance
3766    */
3767
3768    exitFullscreen: function( callback ) {
3769        this._fullscreen.exit.apply( this, Utils.array( arguments ) );
3770        return this;
3771    },
3772
3773    /**
3774        Toggle FullScreen mode
3775
3776        @param {Function} callback the function to be executed when the fullscreen mode is fully applied or removed.
3777
3778        @returns Instance
3779    */
3780
3781    toggleFullscreen: function( callback ) {
3782        this._fullscreen[ this.isFullscreen() ? 'exit' : 'enter'].apply( this, Utils.array( arguments ) );
3783        return this;
3784    },
3785
3786    /**
3787        Adds a tooltip to any element.
3788        You can also call this method with an object as argument with elemID:value pairs to apply tooltips to (see examples)
3789
3790        @param {HTMLElement} elem The DOM Node to attach the event to
3791        @param {string|Function} value The tooltip message. Can also be a function that returns a string.
3792
3793        @example this.bindTooltip( this.get('thumbnails'), 'My thumbnails');
3794        @example this.bindTooltip( this.get('thumbnails'), function() { return 'My thumbs' });
3795        @example this.bindTooltip( { image_nav: 'Navigation' });
3796
3797        @returns Instance
3798    */
3799
3800    bindTooltip: function( elem, value ) {
3801        this._tooltip.bind.apply( this._tooltip, Utils.array(arguments) );
3802        return this;
3803    },
3804
3805    /**
3806        Note: this method is deprecated. Use refreshTooltip() instead.
3807
3808        Redefine a tooltip.
3809        Use this if you want to re-apply a tooltip value to an already bound tooltip element.
3810
3811        @param {HTMLElement} elem The DOM Node to attach the event to
3812        @param {string|Function} value The tooltip message. Can also be a function that returns a string.
3813
3814        @returns Instance
3815    */
3816
3817    defineTooltip: function( elem, value ) {
3818        this._tooltip.define.apply( this._tooltip, Utils.array(arguments) );
3819        return this;
3820    },
3821
3822    /**
3823        Refresh a tooltip value.
3824        Use this if you want to change the tooltip value at runtime, f.ex if you have a play/pause toggle.
3825
3826        @param {HTMLElement} elem The DOM Node that has a tooltip that should be refreshed
3827
3828        @returns Instance
3829    */
3830
3831    refreshTooltip: function( elem ) {
3832        this._tooltip.show.apply( this._tooltip, Utils.array(arguments) );
3833        return this;
3834    },
3835
3836    /**
3837        Open a pre-designed lightbox with the currently active image.
3838        You can control some visuals using gallery options.
3839
3840        @returns Instance
3841    */
3842
3843    openLightbox: function() {
3844        this._lightbox.show.apply( this._lightbox, Utils.array( arguments ) );
3845        return this;
3846    },
3847
3848    /**
3849        Close the lightbox.
3850
3851        @returns Instance
3852    */
3853
3854    closeLightbox: function() {
3855        this._lightbox.hide.apply( this._lightbox, Utils.array( arguments ) );
3856        return this;
3857    },
3858
3859    /**
3860        Get the currently active image element.
3861
3862        @returns {HTMLElement} The image element
3863    */
3864
3865    getActiveImage: function() {
3866        return this._getActive().image || undef;
3867    },
3868
3869    /**
3870        Get the currently active thumbnail element.
3871
3872        @returns {HTMLElement} The thumbnail element
3873    */
3874
3875    getActiveThumb: function() {
3876        return this._thumbnails[ this._active ].image || undef;
3877    },
3878
3879    /**
3880        Get the mouse position relative to the gallery container
3881
3882        @param e The mouse event
3883
3884        @example
3885
3886var gallery = this;
3887$(document).mousemove(function(e) {
3888    console.log( gallery.getMousePosition(e).x );
3889});
3890
3891        @returns {Object} Object with x & y of the relative mouse postion
3892    */
3893
3894    getMousePosition : function(e) {
3895        return {
3896            x: e.pageX - this.$( 'container' ).offset().left,
3897            y: e.pageY - this.$( 'container' ).offset().top
3898        };
3899    },
3900
3901    /**
3902        Adds a panning effect to the image
3903
3904        @param [img] The optional image element. If not specified it takes the currently active image
3905
3906        @returns Instance
3907    */
3908
3909    addPan : function( img ) {
3910
3911        if ( this._options.imageCrop === false ) {
3912            return;
3913        }
3914
3915        img = $( img || this.getActiveImage() );
3916
3917        // define some variables and methods
3918        var self   = this,
3919            x      = img.width() / 2,
vendor: 8,795 bytes, lines 3920-4227
3920            y      = img.height() / 2,
3921            destX  = parseInt( img.css( 'left' ), 10 ),
3922            destY  = parseInt( img.css( 'top' ), 10 ),
3923            curX   = destX || 0,
3924            curY   = destY || 0,
3925            distX  = 0,
3926            distY  = 0,
3927            active = false,
3928            ts     = Utils.timestamp(),
3929            cache  = 0,
3930            move   = 0,
3931
3932            // positions the image
3933            position = function( dist, cur, pos ) {
3934                if ( dist > 0 ) {
3935                    move = Math.round( Math.max( dist * -1, Math.min( 0, cur ) ) );
3936                    if ( cache !== move ) {
3937
3938                        cache = move;
3939
3940                        if ( IE === 8 ) { // scroll is faster for IE
3941                            img.parent()[ 'scroll' + pos ]( move * -1 );
3942                        } else {
3943                            var css = {};
3944                            css[ pos.toLowerCase() ] = move;
3945                            img.css(css);
3946                        }
3947                    }
3948                }
3949            },
3950
3951            // calculates mouse position after 50ms
3952            calculate = function(e) {
3953                if (Utils.timestamp() - ts < 50) {
3954                    return;
3955                }
3956                active = true;
3957                x = self.getMousePosition(e).x;
3958                y = self.getMousePosition(e).y;
3959            },
3960
3961            // the main loop to check
3962            loop = function(e) {
3963
3964                if (!active) {
3965                    return;
3966                }
3967
3968                distX = img.width() - self._stageWidth;
3969                distY = img.height() - self._stageHeight;
3970                destX = x / self._stageWidth * distX * -1;
3971                destY = y / self._stageHeight * distY * -1;
3972                curX += ( destX - curX ) / self._options.imagePanSmoothness;
3973                curY += ( destY - curY ) / self._options.imagePanSmoothness;
3974
3975                position( distY, curY, 'Top' );
3976                position( distX, curX, 'Left' );
3977
3978            };
3979
3980        // we need to use scroll in IE8 to speed things up
3981        if ( IE === 8 ) {
3982
3983            img.parent().scrollTop( curY * -1 ).scrollLeft( curX * -1 );
3984            img.css({
3985                top: 0,
3986                left: 0
3987            });
3988
3989        }
3990
3991        // unbind and bind event
3992        this.$( 'stage' ).unbind( 'mousemove', calculate ).bind( 'mousemove', calculate );
3993
3994        // loop the loop
3995        this.addTimer( 'pan' + self._id, loop, 50, true);
3996
3997        return this;
3998    },
3999
4000    /**
4001        Brings the scope into any callback
4002
4003        @param fn The callback to bring the scope into
4004        @param [scope] Optional scope to bring
4005
4006        @example $('#fullscreen').click( this.proxy(function() { this.enterFullscreen(); }) )
4007
4008        @returns {Function} Return the callback with the gallery scope
4009    */
4010
4011    proxy : function( fn, scope ) {
4012        if ( typeof fn !== 'function' ) {
4013            return F;
4014        }
4015        scope = scope || this;
4016        return function() {
4017            return fn.apply( scope, Utils.array( arguments ) );
4018        };
4019    },
4020
4021    /**
4022        Removes the panning effect set by addPan()
4023
4024        @returns Instance
4025    */
4026
4027    removePan: function() {
4028
4029        // todo: doublecheck IE8
4030
4031        this.$( 'stage' ).unbind( 'mousemove' );
4032
4033        this.clearTimer( 'pan' + this._id );
4034
4035        return this;
4036    },
4037
4038    /**
4039        Adds an element to the Galleria DOM array.
4040        When you add an element here, you can access it using element ID in many API calls
4041
4042        @param {string} id The element ID you wish to use. You can add many elements by adding more arguments.
4043
4044        @example addElement('mybutton');
4045        @example addElement('mybutton','mylink');
4046
4047        @returns Instance
4048    */
4049
4050    addElement : function( id ) {
4051
4052        var dom = this._dom;
4053
4054        $.each( Utils.array(arguments), function( i, blueprint ) {
4055           dom[ blueprint ] = Utils.create( 'galleria-' + blueprint );
4056        });
4057
4058        return this;
4059    },
4060
4061    /**
4062        Attach keyboard events to Galleria
4063
4064        @param {Object} map The map object of events.
4065        Possible keys are 'UP', 'DOWN', 'LEFT', 'RIGHT', 'RETURN', 'ESCAPE', 'BACKSPACE', and 'SPACE'.
4066
4067        @example
4068
4069this.attachKeyboard({
4070    right: this.next,
4071    left: this.prev,
4072    up: function() {
4073        console.log( 'up key pressed' )
4074    }
4075});
4076
4077        @returns Instance
4078    */
4079
4080    attachKeyboard : function( map ) {
4081        this._keyboard.attach.apply( this._keyboard, Utils.array( arguments ) );
4082        return this;
4083    },
4084
4085    /**
4086        Detach all keyboard events to Galleria
4087
4088        @returns Instance
4089    */
4090
4091    detachKeyboard : function() {
4092        this._keyboard.detach.apply( this._keyboard, Utils.array( arguments ) );
4093        return this;
4094    },
4095
4096    /**
4097        Fast helper for appending galleria elements that you added using addElement()
4098
4099        @param {string} parentID The parent element ID where the element will be appended
4100        @param {string} childID the element ID that should be appended
4101
4102        @example this.addElement('myElement');
4103        this.appendChild( 'info', 'myElement' );
4104
4105        @returns Instance
4106    */
4107
4108    appendChild : function( parentID, childID ) {
4109        this.$( parentID ).append( this.get( childID ) || childID );
4110        return this;
4111    },
4112
4113    /**
4114        Fast helper for prepending galleria elements that you added using addElement()
4115
4116        @param {string} parentID The parent element ID where the element will be prepended
4117        @param {string} childID the element ID that should be prepended
4118
4119        @example
4120
4121this.addElement('myElement');
4122this.prependChild( 'info', 'myElement' );
4123
4124        @returns Instance
4125    */
4126
4127    prependChild : function( parentID, childID ) {
4128        this.$( parentID ).prepend( this.get( childID ) || childID );
4129        return this;
4130    },
4131
4132    /**
4133        Remove an element by blueprint
4134
4135        @param {string} elemID The element to be removed.
4136        You can remove multiple elements by adding arguments.
4137
4138        @returns Instance
4139    */
4140
4141    remove : function( elemID ) {
4142        this.$( Utils.array( arguments ).join(',') ).remove();
4143        return this;
4144    },
4145
4146    // a fast helper for building dom structures
4147    // leave this out of the API for now
4148
4149    append : function( data ) {
4150        var i, j;
4151        for( i in data ) {
4152            if ( data.hasOwnProperty( i ) ) {
4153                if ( data[i].constructor === Array ) {
4154                    for( j = 0; data[i][j]; j++ ) {
4155                        this.appendChild( i, data[i][j] );
4156                    }
4157                } else {
4158                    this.appendChild( i, data[i] );
4159                }
4160            }
4161        }
4162        return this;
4163    },
4164
4165    // an internal helper for scaling according to options
4166    _scaleImage : function( image, options ) {
4167
4168        image = image || this._controls.getActive();
4169
4170        // janpub (JH) fix:
4171        // image might be unselected yet
4172        // e.g. when external logics rescales the gallery on window resize events
4173        if( !image ) {
4174            return;
4175        }
4176
4177        var self = this,
4178
4179            complete,
4180
4181            scaleLayer = function( img ) {
4182                $( img.container ).children(':first').css({
4183                    top: Math.max(0, Utils.parseValue( img.image.style.top )),
4184                    left: Math.max(0, Utils.parseValue( img.image.style.left )),
4185                    width: Utils.parseValue( img.image.width ),
4186                    height: Utils.parseValue( img.image.height )
4187                });
4188            };
4189
4190        options = $.extend({
4191            width:    this._stageWidth,
4192            height:   this._stageHeight,
4193            crop:     this._options.imageCrop,
4194            max:      this._options.maxScaleRatio,
4195            min:      this._options.minScaleRatio,
4196            margin:   this._options.imageMargin,
4197            position: this._options.imagePosition
4198        }, options );
4199
4200        if ( this._options.layerFollow && this._options.imageCrop !== true ) {
4201
4202            if ( typeof options.complete == 'function' ) {
4203                complete = options.complete;
4204                options.complete = function() {
4205                    complete.call( image, image );
4206                    scaleLayer( image );
4207                };
4208            } else {
4209                options.complete = scaleLayer;
4210            }
4211
4212        } else {
4213            $( image.container ).children(':first').css({ top: 0, left: 0 });
4214        }
4215
4216        image.scale( options );
4217        return this;
4218    },
4219
4220    /**
4221        Updates the carousel,
4222        useful if you resize the gallery and want to re-check if the carousel nav is needed.
4223
4224        @returns Instance
4225    */
4226
4227    updateCarousel : function() {
4228        this._carousel.update();
4229        return this;
4230    },
4231
4232    /**
4233        Resize the entire gallery container
4234
4235        @param {Object} [measures] Optional object with width/height specified
4236        @param {Function} [complete] The callback to be called when the scaling is complete
4237
4238        @returns Instance
4239    */
4240
4241    resize : function( measures, complete ) {
4242
4243        if ( typeof measures == 'function' ) {
4244            complete = measures;
4245            measures = undef;
4246        }
4247
4248        measures = $.extend( { width:0, height:0 }, measures );
4249
4250        var self = this,
4251            $container = this.$( 'container' );
4252
4253        $.each( measures, function( m, val ) {
4254            if ( !val ) {
4255                $container[ m ]( 'auto' );
4256                measures[ m ] = self._getWH()[ m ];
4257            }
4258        });
4259
4260        $.each( measures, function( m, val ) {
4261            $container[ m ]( val );
4262        });
4263
4264        return this.rescale( complete );
4265
4266    },
4267
4268    /**
4269        Rescales the gallery
4270
4271        @param {number} width The target width
4272        @param {number} height The target height
4273        @param {Function} complete The callback to be called when the scaling is complete
4274
4275        @returns Instance
4276    */
4277
4278    rescale : function( width, height, complete ) {
4279
4280        var self = this;
4281
4282        // allow rescale(fn)
4283        if ( typeof width === 'function' ) {
4284            complete = width;
4285            width = undef;
4286        }
4287
4288        var scale = function() {
4289
4290            // set stagewidth
4291            self._stageWidth = width || self.$( 'stage' ).width();
4292            self._stageHeight = height || self.$( 'stage' ).height();
4293
4294            // scale the active image
4295            self._scaleImage();
4296
4297            if ( self._options.carousel ) {
4298                self.updateCarousel();
4299            }
4300
4301            self.trigger( Galleria.RESCALE );
4302
4303            if ( typeof complete === 'function' ) {
4304                complete.call( self );
4305            }
4306        };
4307
4308        scale.call( self );
4309
4310        return this;
4311    },
4312
4313    /**
4314        Refreshes the gallery.
4315        Useful if you change image options at runtime and want to apply the changes to the active image.
4316
4317        @returns Instance
4318    */
4319
4320    refreshImage : function() {
4321        this._scaleImage();
4322        if ( this._options.imagePan ) {
4323            this.addPan();
4324        }
4325        return this;
4326    },
4327
4328    /**
4329        Shows an image by index
4330
4331        @param {number|boolean} index The index to show
4332        @param {Boolean} rewind A boolean that should be true if you want the transition to go back
4333
4334        @returns Instance
4335    */
4336
4337    show : function( index, rewind, _history ) {
4338
4339        // do nothing if index is false or queue is false and transition is in progress
4340        if ( index === false || ( !this._options.queue && this._queue.stalled ) ) {
4341            return;
4342        }
4343
4344        index = Math.max( 0, Math.min( parseInt( index, 10 ), this.getDataLength() - 1 ) );
4345
4346        rewind = typeof rewind !== 'undefined' ? !!rewind : index < this.getIndex();
4347
4348        _history = _history || false;
4349
4350        // do the history thing and return
4351        if ( !_history && Galleria.History ) {
4352            Galleria.History.set( index.toString() );
4353            return;
4354        }
4355
4356        this._active = index;
4357
4358        protoArray.push.call( this._queue, {
4359            index : index,
4360            rewind : rewind
4361        });
4362        if ( !this._queue.stalled ) {
4363            this._show();
4364        }
4365
4366        return this;
4367    },
4368
4369    // the internal _show method does the actual showing
4370    _show : function() {
4371
4372        // shortcuts
4373        var self = this,
4374            queue = this._queue[ 0 ],
4375            data = this.getData( queue.index );
4376
4377        if ( !data ) {
4378            return;
4379        }
4380
4381        var src = data.iframe || ( this.isFullscreen() && 'big' in data ? data.big : data.image ), // use big image if fullscreen mode
4382            active = this._controls.getActive(),
4383            next = this._controls.getNext(),
4384            cached = next.isCached( src ),
4385            thumb = this._thumbnails[ queue.index ],
4386            mousetrigger = function() {
4387                $( next.image ).trigger( 'mouseup' );
4388            };
4389
4390        // to be fired when loading & transition is complete:
4391        var complete = (function( data, next, active, queue, thumb ) {
4392
4393            return function() {
4394
4395                var win;
4396
4397                _transitions.active = false;
4398
4399                // remove stalled
4400                self._queue.stalled = false;
4401
4402                // optimize quality
4403                Utils.toggleQuality( next.image, self._options.imageQuality );
4404
4405                // remove old layer
4406                self._layers[ self._controls.active ].innerHTML = '';
4407
4408                // swap
4409                $( active.container ).css({
4410                    zIndex: 0,
4411                    opacity: 0
4412                }).show();
4413
4414                if( active.isIframe ) {
4415                    $( active.container ).find( 'iframe' ).remove();
4416                }
4417
4418                self.$('container').toggleClass('iframe', !!data.iframe);
4419
4420                $( next.container ).css({
4421                    zIndex: 1,
4422                    left: 0,
4423                    top: 0
4424                }).show();
4425
4426                self._controls.swap();
4427
4428                // add pan according to option
4429                if ( self._options.imagePan ) {
4430                    self.addPan( next.image );
4431                }
4432
4433                // make the image link or add lightbox
4434                // link takes precedence over lightbox if both are detected
4435				if(self._options.imageClickAction)
4436                {
4437					if ( data.link || self._options.lightbox || self._options.clicknext ) {
4438
4439						$( next.image ).css({
4440							cursor: 'pointer'
4441						}).bind( 'mouseup', function() {
4442
4443							// clicknext
4444							if ( self._options.clicknext && !Galleria.TOUCH ) {
4445								if ( self._options.pauseOnInteraction ) {
4446									self.pause();
4447								}
4448								self.next();
4449								return;
4450							}
4451
4452							// popup link
4453							if ( data.link ) {
4454								if ( self._options.popupLinks ) {
4455									win = window.open( data.link, '_blank' );
4456								} else {
4457									window.location.href = data.link;
4458								}
4459								return;
4460							}
4461
4462							if ( self._options.lightbox ) {
4463								self.openLightbox();
4464							}
4465
4466						});
4467					}
4468				}
4469
4470                // remove the queued image
4471                protoArray.shift.call( self._queue );
4472
4473                // if we still have images in the queue, show it
4474                if ( self._queue.length ) {
4475                    self._show();
4476                }
4477
4478                // check if we are playing
4479                self._playCheck();
4480
4481                // trigger IMAGE event
4482                self.trigger({
4483                    type: Galleria.IMAGE,
4484                    index: queue.index,
4485                    imageTarget: next.image,
4486                    thumbTarget: thumb.image,
4487                    galleriaData: data
4488                });
4489            };
4490        }( data, next, active, queue, thumb ));
4491
4492        // let the carousel follow
4493        if ( this._options.carousel && this._options.carouselFollow ) {
4494            this._carousel.follow( queue.index );
4495        }
4496
4497        // preload images
4498        if ( this._options.preload ) {
4499
4500            var p, i,
4501                n = this.getNext(),
4502                ndata;
4503
4504            try {
4505                for ( i = this._options.preload; i > 0; i-- ) {
4506                    p = new Galleria.Picture();
4507                    ndata = self.getData( n );
4508                    p.preload( this.isFullscreen() && 'big' in ndata ? ndata.big : ndata.image );
4509                    n = self.getNext( n );
4510                }
4511            } catch(e) {}
4512        }
4513
4514        // show the next image, just in case
4515        Utils.show( next.container );
4516
4517        next.isIframe = !!data.iframe;
4518
4519        // add active classes
4520        $( self._thumbnails[ queue.index ].container )
4521            .addClass( 'active' )
4522            .siblings( '.active' )
4523            .removeClass( 'active' );
4524
4525        // trigger the LOADSTART event
4526        self.trigger( {
4527            type: Galleria.LOADSTART,
4528            cached: cached,
4529            index: queue.index,
4530            rewind: queue.rewind,
4531            imageTarget: next.image,
4532            thumbTarget: thumb.image,
4533            galleriaData: data
4534        });
4535
4536        // begin loading the next image
4537        next.load( src, function( next ) {
4538
4539            // add layer HTML
4540            var layer = $( self._layers[ 1-self._controls.active ] ).html( data.layer || '' ).hide();
4541
4542            self._scaleImage( next, {
4543
4544                complete: function( next ) {
4545
4546                    // toggle low quality for IE
4547                    if ( 'image' in active ) {
4548                        Utils.toggleQuality( active.image, false );
4549                    }
4550                    Utils.toggleQuality( next.image, false );
4551
4552                    // stall the queue
4553                    self._queue.stalled = true;
4554
4555                    // remove the image panning, if applied
4556                    // TODO: rethink if this is necessary
4557                    self.removePan();
4558
4559                    // set the captions and counter
vendor: 10,705 bytes, lines 4560-4975
4560                    self.setInfo( queue.index );
4561                    self.setCounter( queue.index );
4562
4563                    // show the layer now
4564                    if ( data.layer ) {
4565                        layer.show();
4566                        // inherit click events set on image
4567                        if ( data.link || self._options.lightbox || self._options.clicknext ) {
4568                            layer.css( 'cursor', 'pointer' ).unbind( 'mouseup' ).mouseup( mousetrigger );
4569                        }
4570                    }
4571
4572                    // trigger the LOADFINISH event
4573                    self.trigger({
4574                        type: Galleria.LOADFINISH,
4575                        cached: cached,
4576                        index: queue.index,
4577                        rewind: queue.rewind,
4578                        imageTarget: next.image,
4579                        thumbTarget: self._thumbnails[ queue.index ].image,
4580                        galleriaData: self.getData( queue.index )
4581                    });
4582
4583                    var transition = self._options.transition;
4584
4585                    // can JavaScript loop through objects in order? yes.
4586                    $.each({
4587                        initial: active.image === null,
4588                        touch: Galleria.TOUCH,
4589                        fullscreen: self.isFullscreen()
4590                    }, function( type, arg ) {
4591                        if ( arg && self._options[ type + 'Transition' ] !== undef ) {
4592                            transition = self._options[ type + 'Transition' ];
4593                            return false;
4594                        }
4595                    });
4596
4597                    // validate the transition
4598                    if ( transition in _transitions.effects === false ) {
4599                        complete();
4600                    } else {
4601                        var params = {
4602                            prev: active.container,
4603                            next: next.container,
4604                            rewind: queue.rewind,
4605                            speed: self._options.transitionSpeed || 400
4606                        };
4607
4608                        _transitions.active = true;
4609
4610                        // call the transition function and send some stuff
4611                        _transitions.init.call( self, transition, params, complete );
4612
4613                    }
4614                }
4615            });
4616        });
4617    },
4618
4619    /**
4620        Gets the next index
4621
4622        @param {number} [base] Optional starting point
4623
4624        @returns {number} the next index, or the first if you are at the first (looping)
4625    */
4626
4627    getNext : function( base ) {
4628        base = typeof base === 'number' ? base : this.getIndex();
4629        return base === this.getDataLength() - 1 ? 0 : base + 1;
4630    },
4631
4632    /**
4633        Gets the previous index
4634
4635        @param {number} [base] Optional starting point
4636
4637        @returns {number} the previous index, or the last if you are at the first (looping)
4638    */
4639
4640    getPrev : function( base ) {
4641        base = typeof base === 'number' ? base : this.getIndex();
4642        return base === 0 ? this.getDataLength() - 1 : base - 1;
4643    },
4644
4645    /**
4646        Shows the next image in line
4647
4648        @returns Instance
4649    */
4650
4651    next : function() {
4652        if ( this.getDataLength() > 1 ) {
4653            this.show( this.getNext(), false );
4654        }
4655        return this;
4656    },
4657
4658    /**
4659        Shows the previous image in line
4660
4661        @returns Instance
4662    */
4663
4664    prev : function() {
4665        if ( this.getDataLength() > 1 ) {
4666            this.show( this.getPrev(), true );
4667        }
4668        return this;
4669    },
4670
4671    /**
4672        Retrieve a DOM element by element ID
4673
4674        @param {string} elemId The delement ID to fetch
4675
4676        @returns {HTMLElement} The elements DOM node or null if not found.
4677    */
4678
4679    get : function( elemId ) {
4680        return elemId in this._dom ? this._dom[ elemId ] : null;
4681    },
4682
4683    /**
4684        Retrieve a data object
4685
4686        @param {number} index The data index to retrieve.
4687        If no index specified it will take the currently active image
4688
4689        @returns {Object} The data object
4690    */
4691
4692    getData : function( index ) {
4693        return index in this._data ?
4694            this._data[ index ] : this._data[ this._active ];
4695    },
4696
4697    /**
4698        Retrieve the number of data items
4699
4700        @returns {number} The data length
4701    */
4702    getDataLength : function() {
4703        return this._data.length;
4704    },
4705
4706    /**
4707        Retrieve the currently active index
4708
4709        @returns {number|boolean} The active index or false if none found
4710    */
4711
4712    getIndex : function() {
4713        return typeof this._active === 'number' ? this._active : false;
4714    },
4715
4716    /**
4717        Retrieve the stage height
4718
4719        @returns {number} The stage height
4720    */
4721
4722    getStageHeight : function() {
4723        return this._stageHeight;
4724    },
4725
4726    /**
4727        Retrieve the stage width
4728
4729        @returns {number} The stage width
4730    */
4731
4732    getStageWidth : function() {
4733        return this._stageWidth;
4734    },
4735
4736    /**
4737        Retrieve the option
4738
4739        @param {string} key The option key to retrieve. If no key specified it will return all options in an object.
4740
4741        @returns option or options
4742    */
4743
4744    getOptions : function( key ) {
4745        return typeof key === 'undefined' ? this._options : this._options[ key ];
4746    },
4747
4748    /**
4749        Set options to the instance.
4750        You can set options using a key & value argument or a single object argument (see examples)
4751
4752        @param {string} key The option key
4753        @param {string} value the the options value
4754
4755        @example setOptions( 'autoplay', true )
4756        @example setOptions({ autoplay: true });
4757
4758        @returns Instance
4759    */
4760
4761    setOptions : function( key, value ) {
4762        if ( typeof key === 'object' ) {
4763            $.extend( this._options, key );
4764        } else {
4765            this._options[ key ] = value;
4766        }
4767        return this;
4768    },
4769
4770    /**
4771        Starts playing the slideshow
4772
4773        @param {number} delay Sets the slideshow interval in milliseconds.
4774        If you set it once, you can just call play() and get the same interval the next time.
4775
4776        @returns Instance
4777    */
4778
4779    play : function( delay ) {
4780
4781        this._playing = true;
4782
4783        this._playtime = delay || this._playtime;
4784
4785        this._playCheck();
4786
4787        this.trigger( Galleria.PLAY );
4788
4789        return this;
4790    },
4791
4792    /**
4793        Stops the slideshow if currently playing
4794
4795        @returns Instance
4796    */
4797
4798    pause : function() {
4799
4800        this._playing = false;
4801
4802        this.trigger( Galleria.PAUSE );
4803
4804        return this;
4805    },
4806
4807    /**
4808        Toggle between play and pause events.
4809
4810        @param {number} delay Sets the slideshow interval in milliseconds.
4811
4812        @returns Instance
4813    */
4814
4815    playToggle : function( delay ) {
4816        return ( this._playing ) ? this.pause() : this.play( delay );
4817    },
4818
4819    /**
4820        Checks if the gallery is currently playing
4821
4822        @returns {Boolean}
4823    */
4824
4825    isPlaying : function() {
4826        return this._playing;
4827    },
4828
4829    /**
4830        Checks if the gallery is currently in fullscreen mode
4831
4832        @returns {Boolean}
4833    */
4834
4835    isFullscreen : function() {
4836        return this._fullscreen.active;
4837    },
4838
4839    _playCheck : function() {
4840        var self = this,
4841            played = 0,
4842            interval = 20,
4843            now = Utils.timestamp(),
4844            timer_id = 'play' + this._id;
4845
4846        if ( this._playing ) {
4847
4848            this.clearTimer( timer_id );
4849
4850            var fn = function() {
4851
4852                played = Utils.timestamp() - now;
4853                if ( played >= self._playtime && self._playing ) {
4854                    self.clearTimer( timer_id );
4855                    self.next();
4856                    return;
4857                }
4858                if ( self._playing ) {
4859
4860                    // trigger the PROGRESS event
4861                    self.trigger({
4862                        type:         Galleria.PROGRESS,
4863                        percent:      Math.ceil( played / self._playtime * 100 ),
4864                        seconds:      Math.floor( played / 1000 ),
4865                        milliseconds: played
4866                    });
4867
4868                    self.addTimer( timer_id, fn, interval );
4869                }
4870            };
4871            self.addTimer( timer_id, fn, interval );
4872        }
4873    },
4874
4875    /**
4876        Modify the slideshow delay
4877
4878        @param {number} delay the number of milliseconds between slides,
4879
4880        @returns Instance
4881    */
4882
4883    setPlaytime: function( delay ) {
4884        this._playtime = delay;
4885        return this;
4886    },
4887
4888    setIndex: function( val ) {
4889        this._active = val;
4890        return this;
4891    },
4892
4893    /**
4894        Manually modify the counter
4895
4896        @param {number} [index] Optional data index to fectch,
4897        if no index found it assumes the currently active index
4898
4899        @returns Instance
4900    */
4901
4902    setCounter: function( index ) {
4903
4904        if ( typeof index === 'number' ) {
4905            index++;
4906        } else if ( typeof index === 'undefined' ) {
4907            index = this.getIndex()+1;
4908        }
4909
4910        this.get( 'current' ).innerHTML = index;
4911
4912        if ( IE ) { // weird IE bug
4913
4914            var count = this.$( 'counter' ),
4915                opacity = count.css( 'opacity' );
4916
4917            if ( parseInt( opacity, 10 ) === 1) {
4918                Utils.removeAlpha( count[0] );
4919            } else {
4920                this.$( 'counter' ).css( 'opacity', opacity );
4921            }
4922
4923        }
4924
4925        return this;
4926    },
4927
4928    /**
4929        Manually set captions
4930
4931        @param {number} [index] Optional data index to fectch and apply as caption,
4932        if no index found it assumes the currently active index
4933
4934        @returns Instance
4935    */
4936
4937    setInfo : function( index ) {
4938
4939        var self = this,
4940            data = this.getData( index );
4941
4942        $.each( ['title','description'], function( i, type ) {
4943
4944            var elem = self.$( 'info-' + type );
4945
4946            if ( !!data[type] ) {
4947                elem[ data[ type ].length ? 'show' : 'hide' ]().html( data[ type ] );
4948            } else {
4949               elem.empty().hide();
4950            }
4951        });
4952
4953        return this;
4954    },
4955
4956    /**
4957        Checks if the data contains any captions
4958
4959        @param {number} [index] Optional data index to fectch,
4960        if no index found it assumes the currently active index.
4961
4962        @returns {boolean}
4963    */
4964
4965    hasInfo : function( index ) {
4966
4967        var check = 'title description'.split(' '),
4968            i;
4969
4970        for ( i = 0; check[i]; i++ ) {
4971            if ( !!this.getData( index )[ check[i] ] ) {
4972                return true;
4973            }
4974        }
4975        return false;
4976
4977    },
4978
4979    jQuery : function( str ) {
4980
4981        var self = this,
4982            ret = [];
4983
4984        $.each( str.split(','), function( i, elemId ) {
4985            elemId = $.trim( elemId );
4986
4987            if ( self.get( elemId ) ) {
4988                ret.push( elemId );
4989            }
4990        });
4991
4992        var jQ = $( self.get( ret.shift() ) );
4993
4994        $.each( ret, function( i, elemId ) {
4995            jQ = jQ.add( self.get( elemId ) );
4996        });
4997
4998        return jQ;
4999
5000    },
5001
5002    /**
5003        Converts element IDs into a jQuery collection
5004        You can call for multiple IDs separated with commas.
5005
5006        @param {string} str One or more element IDs (comma-separated)
5007
5008        @returns jQuery
5009
5010        @example this.$('info,container').hide();
5011    */
5012
5013    $ : function( str ) {
5014        return this.jQuery.apply( this, Utils.array( arguments ) );
5015    }
5016
5017};
5018
5019// End of Galleria prototype
5020
5021// Add events as static variables
5022$.each( _events, function( i, ev ) {
5023
5024    // legacy events
5025    var type = /_/.test( ev ) ? ev.replace( /_/g, '' ) : ev;
5026
5027    Galleria[ ev.toUpperCase() ] = 'galleria.'+type;
5028
5029} );
5030
5031$.extend( Galleria, {
5032
5033    // Browser helpers
5034    IE9:     IE === 9,
5035    IE8:     IE === 8,
5036    IE7:     IE === 7,
5037    IE6:     IE === 6,
5038    IE:      IE,
5039    WEBKIT:  /webkit/.test( NAV ),
5040    CHROME:  /chrome/.test( NAV ),
5041    SAFARI:  /safari/.test( NAV ) && !(/chrome/.test( NAV )),
5042    QUIRK:   ( IE && doc.compatMode && doc.compatMode === "BackCompat" ),
5043    MAC:     /mac/.test( navigator.platform.toLowerCase() ),
5044    OPERA:   !!window.opera,
5045    IPHONE:  /iphone/.test( NAV ),
5046    IPAD:    /ipad/.test( NAV ),
5047    ANDROID: /android/.test( NAV ),
5048    TOUCH:   ('ontouchstart' in doc)
5049
5050});
5051
5052// Galleria static methods
5053
5054/**
5055    Adds a theme that you can use for your Gallery
5056
5057    @param {Object} theme Object that should contain all your theme settings.
5058    <ul>
5059        <li>name - name of the theme</li>
5060        <li>author - name of the author</li>
5061        <li>css - css file name (not path)</li>
5062        <li>defaults - default options to apply, including theme-specific options</li>
5063        <li>init - the init function</li>
5064    </ul>
5065
5066    @returns {Object} theme
5067*/
5068
5069Galleria.addTheme = function( theme ) {
5070
5071    // make sure we have a name
5072    if ( !theme.name ) {
5073        Galleria.raise('No theme name specified');
5074    }
5075
5076    if ( typeof theme.defaults !== 'object' ) {
5077        theme.defaults = {};
5078    } else {
5079        theme.defaults = _legacyOptions( theme.defaults );
5080    }
5081
5082    var css = false,
5083        reg;
5084
5085    if ( typeof theme.css === 'string' ) {
5086
5087        // look for manually added CSS
5088        $('link').each(function( i, link ) {
5089            reg = new RegExp( theme.css );
5090            if ( reg.test( link.href ) ) {
5091
5092                // we found the css
5093                css = true;
5094
5095                // the themeload trigger
5096                _themeLoad( theme );
5097
5098                return false;
5099            }
5100        });
5101
5102        // else look for the absolute path and load the CSS dynamic
5103        if ( !css ) {
5104
5105            $('script').each(function( i, script ) {
5106
5107                // look for the theme script
5108                reg = new RegExp( 'galleria\\.' + theme.name.toLowerCase() + '\\.' );
5109                if( reg.test( script.src )) {
5110
5111                    // we have a match
5112                    css = script.src.replace(/[^\/]*$/, '') + theme.css;
5113
5114                    window.setTimeout(function() {
5115                        Utils.loadCSS( css, 'galleria-theme', function() {
5116
5117                            // the themeload trigger
5118                            _themeLoad( theme );
5119
5120                        });
5121                    }, 1);
5122
5123                }
5124            });
5125        }
5126
5127        if ( !css ) {
5128            Galleria.raise('No theme CSS loaded');
5129        }
5130    } else {
5131
5132        // pass
5133        _themeLoad( theme );
5134    }
5135    return theme;
5136};
5137
5138/**
5139    loadTheme loads a theme js file and attaches a load event to Galleria
5140
5141    @param {string} src The relative path to the theme source file
5142
5143    @param {Object} [options] Optional options you want to apply
5144
5145    @returns Galleria
5146*/
5147
5148Galleria.loadTheme = function( src, options ) {
5149
5150    var loaded = false,
5151        length = _galleries.length,
5152        err = window.setTimeout( function() {
5153            Galleria.raise( "Theme at " + src + " could not load, check theme path.", true );
5154        }, 5000 );
5155
5156    // first clear the current theme, if exists
5157    Galleria.theme = undef;
5158
5159    // load the theme
5160    Utils.loadScript( src, function() {
5161
5162        window.clearTimeout( err );
5163
5164        // check for existing galleries and reload them with the new theme
5165        if ( length ) {
5166
5167            // temporary save the new galleries
5168            var refreshed = [];
5169
5170            // refresh all instances
5171            // when adding a new theme to an existing gallery, all options will be resetted but the data will be kept
5172            // you can apply new options as a second argument
5173            $.each( Galleria.get(), function(i, instance) {
5174
5175                // mix the old data and options into the new instance
5176                var op = $.extend( instance._original.options, {
5177                    data_source: instance._data
5178                }, options);
5179
5180                // remove the old container
5181                instance.$('container').remove();
5182
5183                // create a new instance
5184                var g = new Galleria();
5185
5186                // move the id
5187                g._id = instance._id;
5188
5189                // initialize the new instance
5190                g.init( instance._original.target, op );
5191
5192                // push the new instance
5193                refreshed.push( g );
5194            });
5195
5196            // now overwrite the old holder with the new instances
5197            _galleries = refreshed;
5198        }
5199
5200    });
5201
5202    return Galleria;
5203};
5204
5205/**
5206    Retrieves a Galleria instance.
5207
5208    @param {number} [index] Optional index to retrieve.
5209    If no index is supplied, the method will return all instances in an array.
vendor: 5,523 bytes, lines 5210-5447
5210
5211    @returns Instance or Array of instances
5212*/
5213
5214Galleria.get = function( index ) {
5215    if ( !!_instances[ index ] ) {
5216        return _instances[ index ];
5217    } else if ( typeof index !== 'number' ) {
5218        return _instances;
5219    } else {
5220        Galleria.raise('Gallery index ' + index + ' not found');
5221    }
5222};
5223
5224/**
5225
5226    Configure Galleria options via a static function.
5227    The options will be applied to all instances
5228
5229    @param {string|object} key The options to apply or a key
5230
5231    @param [value] If key is a string, this is the value
5232
5233    @returns Galleria
5234
5235*/
5236
5237Galleria.configure = function( key, value ) {
5238
5239    var opts = {};
5240
5241    if( typeof key == 'string' && value ) {
5242        opts[key] = value;
5243        key = opts;
5244    } else {
5245        $.extend( opts, key );
5246    }
5247
5248    Galleria.configure.options = opts;
5249
5250    $.each( Galleria.get(), function(i, instance) {
5251        instance.setOptions( opts );
5252    });
5253
5254    return Galleria;
5255};
5256
5257Galleria.configure.options = {};
5258
5259/**
5260
5261    Bind a Galleria event to the gallery
5262
5263    @param {string} type A string representing the galleria event
5264
5265    @param {function} callback The function that should run when the event is triggered
5266
5267    @returns Galleria
5268
5269*/
5270
5271Galleria.on = function( type, callback ) {
5272    if ( !type ) {
5273        return;
5274    }
5275
5276    callback = callback || F;
5277
5278    // hash the bind
5279    var hash = type + callback.toString().replace(/\s/g,'') + Utils.timestamp();
5280
5281    // for existing instances
5282    $.each( Galleria.get(), function(i, instance) {
5283        instance._binds.push( hash );
5284        instance.bind( type, callback );
5285    });
5286
5287    // for future instances
5288    Galleria.on.binds.push({
5289        type: type,
5290        callback: callback,
5291        hash: hash
5292    });
5293
5294    return Galleria;
5295};
5296
5297Galleria.on.binds = [];
5298
5299/**
5300
5301    Run Galleria
5302    Alias for $(selector).galleria(options)
5303
5304    @param {string} selector A selector of element(s) to intialize galleria to
5305
5306    @param {object} options The options to apply
5307
5308    @returns Galleria
5309
5310*/
5311
5312Galleria.run = function( selector, options ) {
5313    $( selector || '#galleria' ).galleria( options );
5314    return Galleria;
5315};
5316
5317/**
5318    Creates a transition to be used in your gallery
5319
5320    @param {string} name The name of the transition that you will use as an option
5321
5322    @param {Function} fn The function to be executed in the transition.
5323    The function contains two arguments, params and complete.
5324    Use the params Object to integrate the transition, and then call complete when you are done.
5325
5326    @returns Galleria
5327
5328*/
5329
5330Galleria.addTransition = function( name, fn ) {
5331    _transitions.effects[name] = fn;
5332    return Galleria;
5333};
5334
5335/**
5336    The Galleria utilites
5337*/
5338
5339Galleria.utils = Utils;
5340
5341/**
5342    A helper metod for cross-browser logging.
5343    It uses the console log if available otherwise it falls back to alert
5344
5345    @example Galleria.log("hello", document.body, [1,2,3]);
5346*/
5347
5348Galleria.log = function() {
5349    var args = Utils.array( arguments );
5350    if( 'console' in window && 'log' in window.console ) {
5351        try {
5352            return window.console.log.apply( window.console, args );
5353        } catch( e ) {
5354            $.each( args, function() {
5355                window.console.log(this);
5356            });
5357        }
5358    } else {
5359        return window.alert( args.join('<br>') );
5360    }
5361};
5362
5363/**
5364    A ready method for adding callbacks when a gallery is ready
5365    Each method is call before the extend option for every instance
5366
5367    @param {function} callback The function to call
5368
5369    @returns Galleria
5370*/
5371
5372Galleria.ready = function( fn ) {
5373    if ( typeof fn != 'function' ) {
5374        return Galleria;
5375    }
5376    $.each( _galleries, function( i, gallery ) {
5377        fn.call( gallery, gallery._options );
5378    });
5379    Galleria.ready.callbacks.push( fn );
5380    return Galleria;
5381};
5382
5383Galleria.ready.callbacks = [];
5384
5385/**
5386    Method for raising errors
5387
5388    @param {string} msg The message to throw
5389
5390    @param {boolean} [fatal] Set this to true to override debug settings and display a fatal error
5391*/
5392
5393Galleria.raise = function( msg, fatal ) {
5394
5395    var type = fatal ? 'Fatal error' : 'Error',
5396
5397        self = this,
5398
5399        css = {
5400            color: '#fff',
5401            position: 'absolute',
5402            top: 0,
5403            left: 0,
5404            zIndex: 100000
5405        },
5406
5407        echo = function( msg ) {
5408
5409            var html = '<div style="padding:4px;margin:0 0 2px;background:#' +
5410                ( fatal ? '811' : '222' ) + '";>' +
5411                ( fatal ? '<strong>' + type + ': </strong>' : '' ) +
5412                msg + '</div>';
5413
5414            $.each( _instances, function() {
5415
5416                var cont = this.$( 'errors' ),
5417                    target = this.$( 'target' );
5418
5419                if ( !cont.length ) {
5420
5421                    target.css( 'position', 'relative' );
5422
5423                    cont = this.addElement( 'errors' ).appendChild( 'target', 'errors' ).$( 'errors' ).css(css);
5424                }
5425                cont.append( html );
5426
5427            });
5428
5429            if ( !_instances.length ) {
5430                $('<div>').css( $.extend( css, { position: 'fixed' } ) ).append( html ).appendTo( DOM().body );
5431            }
5432        };
5433
5434    // if debug is on, display errors and throw exception if fatal
5435    if ( DEBUG ) {
5436        echo( msg );
5437        if ( fatal ) {
5438            throw new Error(type + ': ' + msg);
5439        }
5440
5441    // else just echo a silent generic error if fatal
5442    } else if ( fatal ) {
5443        if ( _hasError ) {
5444            return;
5445        }
5446        _hasError = true;
5447        fatal = false;
5448        echo( 'Gallery could not load.' );
5449    }
5450};
5451
5452// Add the version
5453Galleria.version = VERSION;
5454
5455/**
5456    A method for checking what version of Galleria the user has installed and throws a readable error if the user needs to upgrade.
5457    Useful when building plugins that requires a certain version to function.
5458
5459    @param {number} version The minimum version required
5460
5461    @param {string} [msg] Optional message to display. If not specified, Galleria will throw a generic error.
5462
5463    @returns Galleria
5464*/
5465
5466Galleria.requires = function( version, msg ) {
5467    msg = msg || 'You need to upgrade Galleria to version ' + version + ' to use one or more components.';
5468    if ( Galleria.version < version ) {
5469        Galleria.raise(msg, true);
5470    }
5471    return Galleria;
5472};
5473
5474/**
5475    Adds preload, cache, scale and crop functionality
5476
5477    @constructor
5478
5479    @requires jQuery
5480
5481    @param {number} [id] Optional id to keep track of instances
5482*/
5483
5484Galleria.Picture = function( id ) {
5485
5486    // save the id
5487    this.id = id || null;
5488
5489    // the image should be null until loaded
5490    this.image = null;
5491
5492    // Create a new container
5493    this.container = Utils.create('galleria-image');
5494
5495    // add container styles
5496    $( this.container ).css({
5497        overflow: 'hidden',
5498        position: 'relative' // for IE Standards mode
5499    });
5500
5501    // saves the original measurements
5502    this.original = {
5503        width: 0,
5504        height: 0
5505    };
5506
5507    // flag when the image is ready
5508    this.ready = false;
5509
5510    // flag for iframe Picture
5511    this.isIframe = false;
5512
5513};
5514
5515Galleria.Picture.prototype = {
5516
5517    // the inherited cache object
5518    cache: {},
5519
5520    // show the image on stage
5521    show: function() {
5522        Utils.show( this.image );
5523    },
5524
5525    // hide the image
5526    hide: function() {
5527        Utils.moveOut( this.image );
5528    },
5529
5530    clear: function() {
5531        this.image = null;
5532    },
5533
5534    /**
5535        Checks if an image is in cache
5536
5537        @param {string} src The image source path, ex '/path/to/img.jpg'
5538
5539        @returns {boolean}
5540    */
5541
5542    isCached: function( src ) {
5543        return !!this.cache[src];
5544    },
5545
5546    /**
5547        Preloads an image into the cache
5548
5549        @param {string} src The image source path, ex '/path/to/img.jpg'
5550
5551        @returns Galleria.Picture
5552    */
5553
5554    preload: function( src ) {
5555        $( new Image() ).load((function(src, cache) {
5556            return function() {
5557                cache[ src ] = src;
5558            };
5559        }( src, this.cache ))).attr( 'src', src );
5560    },
5561
5562    /**
5563        Loads an image and call the callback when ready.
5564        Will also add the image to cache.
5565
5566        @param {string} src The image source path, ex '/path/to/img.jpg'
5567        @param {Object} [size] The forced size of the image, defined as an object { width: xx, height:xx }
5568        @param {Function} callback The function to be executed when the image is loaded & scaled
5569
5570        @returns The image container (jQuery object)
5571    */
5572
5573    load: function(src, size, callback) {
5574
5575        if ( typeof size == 'function' ) {
5576            callback = size;
5577            size = null;
5578        }
5579
5580        if( this.isIframe ) {
5581            var id = 'if'+new Date().getTime();
5582
5583            this.image = $('<iframe>', {
5584                src: src,
5585                frameborder: 0,
5586                id: id,
5587                allowfullscreen: true,
5588                css: { visibility: 'hidden' }
5589            })[0];
5590
5591            $( this.container ).find( 'iframe,img' ).remove();
5592
5593            this.container.appendChild( this.image );
5594
5595            $('#'+id).load( (function( self, callback ) {
5596                return function() {
5597                    window.setTimeout(function() {
5598                        $( self.image ).css( 'visibility', 'visible' );
5599                        if( typeof callback == 'function' ) {
5600                            callback.call( self, self );
5601                        }
5602                    }, 10);
5603                };
5604            }( this, callback )));
5605
5606            return this.container;
5607        }
5608
5609        this.image = new Image();
5610
5611        var i = 0,
5612            reload = false,
5613            resort = false,
5614
5615            // some jquery cache
5616            $container = $( this.container ),
5617            $image = $( this.image ),
5618
5619            onerror = function() {
5620                if ( !reload ) {
5621                    reload = true;
5622                    // reload the image with a timestamp
5623                    window.setTimeout((function(image, src) {
5624                        return function() {
5625                            image.attr('src', src + '?' + Utils.timestamp() );
5626                        };
5627                    }( $(this), src )), 50);
5628                } else {
5629                    // apply the dummy image if it exists
5630                    if ( DUMMY ) {
5631                        $( this ).attr( 'src', DUMMY );
5632                    } else {
5633                        Galleria.raise('Image not found: ' + src);
5634                    }
5635                }
5636            },
5637
5638            // the onload method
5639            onload = (function( self, callback, src ) {
5640
5641                return function() {
5642
5643                    var complete = function() {
5644
5645                        $( this ).unbind( 'load' );
5646
5647                        // save the original size
5648                        self.original = size || {
5649                            height: this.height,
5650                            width: this.width
5651                        };
5652
5653                        //self.container.appendChild( this );
5654
5655                        self.cache[ src ] = src; // will override old cache
5656
5657                        if (typeof callback == 'function' ) {
5658                            window.setTimeout(function() {
5659                                callback.call( self, self );
5660                            },1);
5661                        }
5662                    };
5663
5664                    // Delay the callback to "fix" the Adblock Bug
5665                    // http://code.google.com/p/adblockforchrome/issues/detail?id=3701
5666                    if ( ( !this.width || !this.height ) ) {
5667                        window.setTimeout( (function( img ) {
5668                            return function() {
5669                                if ( img.width && img.height ) {
5670                                    complete.call( img );
5671                                } else {
5672                                    // last resort, this should never happen but just in case it does...
5673                                    if ( !resort ) {
5674                                        $(new Image()).load( onload ).attr( 'src', img.src );
5675                                        resort = true;
5676                                    } else {
5677                                        Galleria.raise('Could not extract width/height from image: ' + img.src +
5678                                            '. Traced measures: width:' + img.width + 'px, height: ' + img.height + 'px.');
5679                                    }
5680                                }
5681                            };
5682                        }( this )), 2);
5683                    } else {
5684                        complete.call( this );
5685                    }
5686                };
5687            }( this, callback, src ));
5688
5689        // remove any previous images
5690        $container.find( 'iframe,img' ).remove();
5691
5692        // append the image
5693        $image.css( 'display', 'block');
5694
5695        // hide it for now
5696        Utils.hide( this.image );
5697
5698        // remove any max/min scaling
5699        $.each('minWidth minHeight maxWidth maxHeight'.split(' '), function(i, prop) {
5700            $image.css(prop, (/min/.test(prop) ? '0' : 'none'));
5701        });
5702
5703		$container.append( $image );
5704        // begin load and insert in cache when done
5705        $image.load( onload ).error( onerror ).attr( 'src', src );
5706
5707        // return the container
5708        return this.container;
5709    },
5710
5711    /**
5712        Scales and crops the image
5713
5714        @param {Object} options The method takes an object with a number of options:
5715
5716        <ul>
5717            <li>width - width of the container</li>
5718            <li>height - height of the container</li>
5719            <li>min - minimum scale ratio</li>
5720            <li>max - maximum scale ratio</li>
5721            <li>margin - distance in pixels from the image border to the container</li>
5722            <li>complete - a callback that fires when scaling is complete</li>
5723            <li>position - positions the image, works like the css background-image property.</li>
5724            <li>crop - defines how to crop. Can be true, false, 'width' or 'height'</li>
5725            <li>canvas - set to true to try a canvas-based rescale</li>
5726        </ul>
5727
5728        @returns The image container object (jQuery)
5729    */
5730
5731    scale: function( options ) {
5732
5733        var self = this;
5734
5735        // extend some defaults
5736        options = $.extend({
5737            width: 0,
5738            height: 0,
5739            min: undef,
5740            max: undef,
5741            margin: 0,
5742            complete: F,
5743            position: 'center',
5744            crop: false,
5745            canvas: false
5746        }, options);
5747
5748        if( this.isIframe ) {
5749            $( this.image ).width( options.width ).height( options.height ).removeAttr( 'width' ).removeAttr( 'height' );
5750            $( this.container ).width( options.width ).height( options.height) ;
5751            options.complete.call(self, self);
5752            try {
5753                if( this.image.contentWindow ) {
5754                    $( this.image.contentWindow ).trigger('resize');
5755                }
5756            } catch(e) {}
5757            return this.container;
5758        }
5759
5760        // return the element if no image found
5761        if (!this.image) {
5762            return this.container;
5763        }
5764
5765        // store locale variables
5766        var width,
5767            height,
5768            $container = $( self.container ),
5769            data;
5770
5771        // wait for the width/height
5772        Utils.wait({
5773            until: function() {
5774                width  = options.width ||
5775                         $container.width() ||
5776                         Utils.parseValue( $container.css('width') );
5777
5778                height = options.height ||
5779                         $container.height() ||
5780                         Utils.parseValue( $container.css('height') );
5781
5782                return width && height;
5783            },
5784            success: function() {
5785
5786                // calculate some cropping
5787                var newWidth = ( width - options.margin * 2 ) / self.original.width,
5788                    newHeight = ( height - options.margin * 2 ) / self.original.height,
5789                    min = Math.min( newWidth, newHeight ),
5790                    max = Math.max( newWidth, newHeight ),
5791                    cropMap = {
5792                        'true'  : max,
5793                        'width' : newWidth,
5794                        'height': newHeight,
5795                        'false' : min,
5796                        'landscape': self.original.width > self.original.height ? max : min,
5797                        'portrait': self.original.width < self.original.height ? max : min
5798                    },
5799                    ratio = cropMap[ options.crop.toString() ],
vendor: 5,436 bytes, lines 5800-5978
5800                    canvasKey = '';
5801
5802                // allow maxScaleRatio
5803                if ( options.max ) {
5804                    ratio = Math.min( options.max, ratio );
5805                }
5806
5807                // allow minScaleRatio
5808                if ( options.min ) {
5809                    ratio = Math.max( options.min, ratio );
5810                }
5811
5812                $.each( ['width','height'], function( i, m ) {
5813                    $( self.image )[ m ]( self[ m ] = self.image[ m ] = Math.round( self.original[ m ] * ratio ) );
5814                });
5815
5816                $( self.container ).width( width ).height( height );
5817
5818                if ( options.canvas && _canvas ) {
5819
5820                    _canvas.elem.width = self.width;
5821                    _canvas.elem.height = self.height;
5822
5823                    canvasKey = self.image.src + ':' + self.width + 'x' + self.height;
5824
5825                    self.image.src = _canvas.cache[ canvasKey ] || (function( key ) {
5826
5827                        _canvas.context.drawImage(self.image, 0, 0, self.original.width*ratio, self.original.height*ratio);
5828
5829                        try {
5830
5831                            data = _canvas.elem.toDataURL();
5832                            _canvas.length += data.length;
5833                            _canvas.cache[ key ] = data;
5834                            return data;
5835
5836                        } catch( e ) {
5837                            return self.image.src;
5838                        }
5839
5840                    }( canvasKey ) );
5841
5842                }
5843
5844                // calculate image_position
5845                var pos = {},
5846                    mix = {},
5847                    getPosition = function(value, measure, margin) {
5848                        var result = 0;
5849                        if (/\%/.test(value)) {
5850                            var flt = parseInt( value, 10 ) / 100,
5851                                m = self.image[ measure ] || $( self.image )[ measure ]();
5852
5853                            result = Math.ceil( m * -1 * flt + margin * flt );
5854                        } else {
5855                            result = Utils.parseValue( value );
5856                        }
5857                        return result;
5858                    },
5859                    positionMap = {
5860                        'top': { top: 0 },
5861                        'left': { left: 0 },
5862                        'right': { left: '100%' },
5863                        'bottom': { top: '100%' }
5864                    };
5865
5866                $.each( options.position.toLowerCase().split(' '), function( i, value ) {
5867                    if ( value === 'center' ) {
5868                        value = '50%';
5869                    }
5870                    pos[i ? 'top' : 'left'] = value;
5871                });
5872
5873                $.each( pos, function( i, value ) {
5874                    if ( positionMap.hasOwnProperty( value ) ) {
5875                        $.extend( mix, positionMap[ value ] );
5876                    }
5877                });
5878
5879                pos = pos.top ? $.extend( pos, mix ) : mix;
5880
5881                pos = $.extend({
5882                    top: '50%',
5883                    left: '50%'
5884                }, pos);
5885
5886                // apply position
5887                $( self.image ).css({
5888                    position : 'absolute',
5889                    top :  getPosition(pos.top, 'height', height),
5890                    left : getPosition(pos.left, 'width', width)
5891                });
5892
5893                // show the image
5894                self.show();
5895
5896                // flag ready and call the callback
5897                self.ready = true;
5898                options.complete.call( self, self );
5899
5900            },
5901            error: function() {
5902                Galleria.raise('Could not scale image: '+self.image.src);
5903            },
5904            timeout: 1000
5905        });
5906        return this;
5907    }
5908};
5909
5910// our own easings
5911$.extend( $.easing, {
5912
5913    galleria: function (_, t, b, c, d) {
5914        if ((t/=d/2) < 1) {
5915            return c/2*t*t*t + b;
5916        }
5917        return c/2*((t-=2)*t*t + 2) + b;
5918    },
5919
5920    galleriaIn: function (_, t, b, c, d) {
5921        return c*(t/=d)*t + b;
5922    },
5923
5924    galleriaOut: function (_, t, b, c, d) {
5925        return -c *(t/=d)*(t-2) + b;
5926    }
5927
5928});
5929
5930// the plugin initializer
5931$.fn.galleria = function( options ) {
5932
5933    var selector = this.selector;
5934
5935    // try domReady if element not found
5936    if ( !$(this).length ) {
5937
5938        $(function() {
5939            if ( $( selector ).length ) {
5940
5941                // if found on domReady, go ahead
5942                $( selector ).galleria( options );
5943
5944            } else {
5945
5946                // if not, try fetching the element for 5 secs, then raise a warning.
5947                Galleria.utils.wait({
5948                    until: function() {
5949                        return $( selector ).length;
5950                    },
5951                    success: function() {
5952                        $( selector ).galleria( options );
5953                    },
5954                    error: function() {
5955                        Galleria.raise('Init failed: Galleria could not find the element "'+selector+'".');
5956                    },
5957                    timeout: 5000
5958                });
5959
5960            }
5961        });
5962        return this;
5963    }
5964
5965    return this.each(function() {
5966
5967        // fail silent if already run
5968        if ( !$.data(this, 'galleria') ) {
5969            $.data( this, 'galleria', new Galleria().init( this, options ) );
5970        }
5971    });
5972
5973};
5974
5975// phew
5976
5977}( jsnThemeClassicjQuery ) );
5978;

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