PageSourceSearch

https://www.beavercreekreserve.org/js/galleria-1.2.9.js?v=1467223250

js beavercreekreserve.org collected 2026-10-04 00:26:15 UTC 186,859 bytes, 6,121 lines download raw bytes

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

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.