PageSourceSearch

https://www.socialmotus.com/scripts/galleria.js

js socialmotus.com collected 2026-10-04 21:15:13 UTC 72,300 bytes, 2,125 lines download raw bytes

1/*!
2 * Galleria v 1.1.95 2010-08-06
3 * http://galleria.aino.se
4 *
5 * Copyright (c) 2010, Aino
6 * Licensed under the MIT license.
7 */
8
9(function() {
10
11var initializing = false,
12    fnTest = /xyz/.test(function(){xyz;}) ? /\b__super\b/ : /.*/,
13    Class = function(){},
14    window = this;
15
16Class.extend = function(prop) {
17    var __super = this.prototype;
18    initializing = true;
19    var proto = new this();
20    initializing = false;
21    for (var name in prop) {
22        if (name) {
23            proto[name] = typeof prop[name] == "function" && 
24                typeof __super[name] == "function" && fnTest.test(prop[name]) ? 
25                (function(name, fn) { 
26                    return function() { 
27                        var tmp = this.__super; 
28                        this.__super = __super[name]; 
29                        var ret = fn.apply(this, arguments);       
30                        this.__super = tmp; 
31                        return ret; 
32                    }; 
33                })(name, prop[name]) : prop[name]; 
34        } 
35    }
36
37    function Class() {
38        if ( !initializing && this.__constructor ) {
39            this.__constructor.apply(this, arguments);
40        }
41    }
42    Class.prototype = proto;
43    Class.constructor = Class;
44    Class.extend = arguments.callee;
45    return Class;
46};
47
48var Base = Class.extend({
49    loop : function( elem, fn) {
50        var scope = this;
51        if (typeof elem == 'number') {
52            elem = new Array(elem);
53        }
54        jQuery.each(elem, function() {
55            fn.call(scope, arguments[1], arguments[0]);
56        });
57        return elem;
58    },
59    create : function( elem, className ) {
60        elem = elem || 'div';
61        var el = document.createElement(elem);
62        if (className) {
63            el.className = className;
64        }
65        return el;
66    },
67    getElements : function( selector ) {
68        var elems = {};
69        this.loop( jQuery(selector), this.proxy(function( elem ) {
70            this.push(elem, elems);
71        }));
72        return elems;
73    },
74    setStyle : function( elem, css ) {
75        jQuery(elem).css(css);
76        return this;
77    },
78    getStyle : function( elem, styleProp, parse ) {
79        var val = jQuery(elem).css(styleProp);
80        return parse ? this.parseValue( val ) : val;
81    },
82    cssText : function( string ) {
83        var style = document.createElement('style');
84        this.getElements('head')[0].appendChild(style);
85        if (style.styleSheet) { // IE
86            style.styleSheet.cssText = string;
87        } else {
88            var cssText = document.createTextNode(string);
89            style.appendChild(cssText);
90        }
91        return this;
92    },
93    touch : function(el) {
94        var sibling = el.nextSibling;
95        var parent = el.parentNode;
96        parent.removeChild(el);
97        if ( sibling ) {
98            parent.insertBefore(el, sibling);
99        } else {
100            parent.appendChild(el);
101        }
102        if (el.styleSheet && el.styleSheet.imports.length) {
103            this.loop(el.styleSheet.imports, function(i) {
104                el.styleSheet.addImport(i.href);
105            });
106        }
107    },
108    loadCSS : function(href, callback) {
109        var exists = this.getElements('link[href="'+href+'"]').length;
110        if (exists) {
111            callback.call(null);
112            return exists[0];
113        }
114        var link = this.create('link');
115        link.rel = 'stylesheet';
116        link.href = href;
117        
118        if (typeof callback == 'function') {
119            // a new css check method, still experimental...
120            this.wait(function() {
121                return !!document.body;
122            }, function() {
123                var testElem = this.create('div', 'galleria-container galleria-stage');
124                this.moveOut(testElem);
125                document.body.appendChild(testElem);
126                var getStyles = this.proxy(function() {
127                    var str = '';
128                    var props;
129                    if (document.defaultView && document.defaultView.getComputedStyle) {
130                        props = document.defaultView.getComputedStyle(testElem, "");
131                        this.loop(props, function(prop) {
132                            str += prop + props.getPropertyValue(prop);
133                        });
134                    } else if (testElem.currentStyle) { // IE
135                        props = testElem.currentStyle;
136                        this.loop(props, function(val, prop) {
137                            str += prop + val;
138                        });
139                    }
140                    return str;
141                });
142                var current = getStyles();
143                this.wait(function() {
144                    return getStyles() !== current;
145                }, function() {
146                    document.body.removeChild(testElem);
147                    callback.call(link);
148                }, function() {
149                    G.raise('Could not confirm theme CSS');
150                }, 2000);
151            });
152        }
153        window.setTimeout(this.proxy(function() {
154            var styles = this.getElements('link[rel="stylesheet"],style');
155            if (styles.length) {
156                styles[0].parentNode.insertBefore(link, styles[0]);
157            } else {
158                this.getElements('head')[0].appendChild(link);
159            }
160            // IE needs a manual touch to re-order the cascade
161            if (G.IE) {
162                this.loop(styles, function(el) {
163                    this.touch(el);
164                })
165            }
166        }), 2);
167        return link;
168    },
169    moveOut : function( elem ) {
170        return this.setStyle(elem, {
171            position: 'absolute',
172            left: '-10000px',
173            display: 'block'
174        });
175    },
176    moveIn : function( elem ) {
177        return this.setStyle(elem, {
178            left: '0'
179        }); 
180    },
181    reveal : function( elem ) {
182        return jQuery( elem ).show();
183    },
184    hide : function( elem ) {
185        return jQuery( elem ).hide();
186    },
187    mix : function() {
188        return jQuery.extend.apply(jQuery, arguments);
189    },
190    proxy : function( fn, scope ) {
191        if ( typeof fn !== 'function' ) {
192            return function() {};
193        }
194        scope = scope || this;
195        return function() {
196            return fn.apply( scope, Array.prototype.slice.call(arguments) );
197        };
198    },
199    listen : function( elem, type, fn ) {
200        jQuery(elem).bind( type, fn );
201    },
202    forget : function( elem, type, fn ) {
203        jQuery(elem).unbind(type, fn);
204    },
205    dispatch : function( elem, type ) {
206        jQuery(elem).trigger(type);
207    },
208    clone : function( elem, keepEvents ) {
209        keepEvents = keepEvents || false;
210        return jQuery(elem).clone(keepEvents)[0];
211    },
212    removeAttr : function( elem, attributes ) {
213        this.loop( attributes.split(' '), function(attr) {
214            jQuery(elem).removeAttr(attr);
215        });
216    },
217    push : function( elem, obj ) {
218        if (typeof obj.length == 'undefined') {
219            obj.length = 0;
220        }
221        Array.prototype.push.call( obj, elem );
222        return elem;
223    },
224    width : function( elem, outer ) {
225        return this.meassure(elem, outer, 'Width');
226    },
227    height : function( elem, outer ) {
228        return this.meassure(elem, outer, 'Height');
229    },
230    meassure : function(el, outer, meassure) {
231        var elem = jQuery( el );
232        var ret = outer ? elem['outer'+meassure](true) : elem[meassure.toLowerCase()]();
233        // fix quirks mode
234        if (G.QUIRK) {
235            var which = meassure == "Width" ? [ "left", "right" ] : [ "top", "bottom" ];
236            this.loop(which, function(s) {
237                ret += elem.css('border-' + s + '-width').replace(/[^\d]/g,'') * 1;
238                ret += elem.css('padding-' + s).replace(/[^\d]/g,'') * 1;
239            });
240        }
241        return ret;
242    },
243    toggleClass : function( elem, className, arg ) {
244        if (typeof arg !== 'undefined') {
245            var fn = arg ? 'addClass' : 'removeClass';
246            jQuery(elem)[fn](className);
247            return this;
248        }
249        jQuery(elem).toggleClass(className);
250        return this;
251    },
252    hideAll : function( el ) {
253        jQuery(el).find('*').hide();
254    },
255    animate : function( el, options ) {
256        options.complete = this.proxy(options.complete);
257        var elem = jQuery(el);
258        if (!elem.length) {
259            return;
260        }
261        if (options.from) {
262            elem.css(from);
263        }
264        elem.animate(options.to, {
265            duration: options.duration || 400,
266            complete: options.complete,
267            easing: options.easing || 'swing'
268        });
269    },
270    wait : function(fn, callback, err, max) {
271        fn = this.proxy(fn);
272        callback = this.proxy(callback);
273        err = this.proxy(err);
274        var ts = new Date().getTime() + (max || 3000);
275        window.setTimeout(function() {
276            if (fn()) {
277                callback();
278                return false;
279            }
280            if (new Date().getTime() >= ts) {
281                err();
282                callback();
283                return false;
284            }
285            window.setTimeout(arguments.callee, 2);
286        }, 2);
287        return this;
288    },
289    loadScript: function(url, callback) {
290       var script = document.createElement('script');
291       script.src = url;
292       script.async = true; // HTML5
293
294       var done = false;
295       var scope = this;
296
297       // Attach handlers for all browsers
298       script.onload = script.onreadystatechange = function() {
299           if ( !done && (!this.readyState ||
300               this.readyState == "loaded" || this.readyState == "complete") ) {
301               done = true;
302               
303               if (typeof callback == 'function') {
304                   callback.call(scope, this);
305               }
306
307               // Handle memory leak in IE
308               script.onload = script.onreadystatechange = null;
309           }
310       };
311       var s = document.getElementsByTagName('script')[0];
312       s.parentNode.insertBefore(script, s);
313       
314       return this;
315    },
316    parseValue: function(val) {
317        if (typeof val == 'number') {
318            return val;
319        } else if (typeof val == 'string') {
320            var arr = val.match(/\-?\d/g);
321            return arr && arr.constructor == Array ? arr.join('')*1 : 0;
322        } else {
323            return 0;
324        }
325    }
326});
327
328var Picture = Base.extend({
329    __constructor : function(order) {
330        this.image = null;
331        this.elem = this.create('div', 'galleria-image');
332        this.setStyle( this.elem, {
333            overflow: 'hidden',
334            position: 'relative' // for IE Standards mode
335        } );
336        this.order = order;
337        this.orig = { w:0, h:0, r:1 };
338    },
339    
340    cache: {},
341    ready: false,
342    
343    add: function(src) {
344        if (this.cache[src]) {
345            return this.cache[src];
346        }
347        var image = new Image();
348        image.src = src;
349        this.setStyle(image, {display: 'block'});
350        if (image.complete && image.width) {
351            this.cache[src] = image;
352            return image;
353        }
354        image.onload = (function(scope) {
355            return function() {
356                scope.cache[src] = image;
357            };
358        })(this);
359        return image;
360    },
361    
362    isCached: function(src) {
363        return this.cache[src] ? this.cache[src].complete : false;
364    },
365    
366    make: function(src) {
367        var i = this.cache[src] || this.add(src);
368        return this.clone(i);
369    },
370    
371    load: function(src, callback) {
372        callback = this.proxy( callback );
373        this.elem.innerHTML = '';
374        this.image = this.make( src );
375        this.moveOut( this.image );
376        this.elem.appendChild( this.image );
377        this.wait(function() {
378            return (this.image.complete && this.image.width);
379        }, function() {
380            this.orig = {
381                h: this.h || this.image.height,
382                w: this.w || this.image.width
383            };
384            callback( {target: this.image, scope: this} );
385        }, function() {
386            G.raise('image not loaded in 20 seconds: '+ src);
387        }, 20000);
388        return this;
389    },
390    
391    scale: function(options) {
392        var o = this.mix({
393            width: 0,
394            height: 0,
395            min: undefined,
396            max: undefined,
397            margin: 0,
398            complete: function(){},
399            position: 'center',
400            crop: false
401        }, options);
402        if (!this.image) {
403            return this;
404        }
405        var width,height;
406        this.wait(function() {
407            width  = o.width || this.width(this.elem);
408            height = o.height || this.height(this.elem);
409            return width && height;
410        }, function() {
411            var nw = (width - o.margin*2) / this.orig.w;
412            var nh = (height- o.margin*2) / this.orig.h;
413            var rmap = {
414                'true': Math.max(nw,nh),
415                'width': nw,
416                'height': nh,
417                'false': Math.min(nw,nh)
418            }
419            var ratio = rmap[o.crop.toString()];
420            if (o.max) {
421                ratio = Math.min(o.max, ratio);
422            }
423            if (o.min) {
424                ratio = Math.max(o.min, ratio);
425            }
426            this.setStyle(this.elem, {
427                width: width,
428                height: height
429            });
430            this.image.width = Math.ceil(this.orig.w * ratio);
431            this.image.height = Math.ceil(this.orig.h * ratio);
432            
433            var getPosition = this.proxy(function(value, img, m) {
434                var result = 0;
435                if (/\%/.test(value)) {
436                    var pos = parseInt(value) / 100;
437                    result = Math.ceil(this.image[img] * -1 * pos + m * pos);
438                } else {
439                    result = parseInt(value);
440                }
441                return result;
442            });
443            
444            var map = {
445                'top': { top: 0 },
446                'left': { left: 0 },
447                'right': { left: '100%' },
448                'bottom': { top: '100%' }
449            }
450            
451            var pos = {};
452            var mix = {};
453            
454            this.loop(o.position.toLowerCase().split(' '), function(p, i) {
455                if (p == 'center') {
456                    p = '50%';
457                }
458                pos[i ? 'top' : 'left'] = p;
459            });
460
461            this.loop(pos, function(val, key) {
462                if (map.hasOwnProperty(val)) {
463                    mix = this.mix(mix, map[val]);
464                }
465            });
466            
467            pos = pos.top ? this.mix(pos, mix) : mix;
468            
469            pos = this.mix({
470                top: '50%',
471                left: '50%'
472            }, pos);
473
474            this.setStyle(this.image, {
475                position : 'relative',
476                top :  getPosition(pos.top, 'height', height),
477                left : getPosition(pos.left, 'width', width)
478            });
479            this.ready = true;
480            o.complete.call(this);
481        });
482        return this;
483    }
484});
485
486var G = window.Galleria = Base.extend({
487    
488    __constructor : function(options) {
489        this.theme = undefined;
490        this.options = options;
491        this.playing = false;
492        this.playtime = 5000;
493        this.active = null;
494        this.queue = {};
495        this.data = {};
496        this.dom = {};
497        
498        var kb = this.keyboard = {
499            keys : {
500                UP: 38,
501                DOWN: 40,
502                LEFT: 37,
503                RIGHT: 39,
504                RETURN: 13,
505                ESCAPE: 27,
506                BACKSPACE: 8
507            },
508            map : {},
509            bound: false,
510            press: this.proxy(function(e) {
511                var key = e.keyCode || e.which;
512                if (kb.map[key] && typeof kb.map[key] == 'function') {
513                    kb.map[key].call(this, e);
514                }
515            }),
516            attach: this.proxy(function(map) {
517                for( var i in map ) {
518                    var k = i.toUpperCase();
519                    if ( kb.keys[k] ) {
520                        kb.map[kb.keys[k]] = map[i];
521                    }
522                }
523                if (!kb.bound) {
524                    kb.bound = true;
525                    this.listen(document, 'keydown', kb.press);
526                }
527            }),
528            detach: this.proxy(function() {
529                kb.bound = false;
530                this.forget(document, 'keydown', kb.press);
531            })
532        };
533        
534        this.timeouts = {
535            trunk: {},
536            add: function(id, fn, delay, loop) {
537                loop = loop || false;
538                this.clear(id);
539                if (loop) {
540                    var self = this;
541                    var old = fn;
542                    fn = function() {
543                        old();
544                        self.add(id,fn,delay);
545                    }
546                }
547                this.trunk[id] = window.setTimeout(fn,delay);
548            },
549            clear: function(id) {
550                if (id && this.trunk[id]) {
551                    window.clearTimeout(this.trunk[id]);
552                    delete this.trunk[id];
553                } else if (typeof id == 'undefined') {
554                    for (var i in this.trunk) {
555                        window.clearTimeout(this.trunk[i]);
556                        delete this.trunk[i];
557                    }
558                }
559            }
560        };
561        
562        this.controls = {
563            0 : null,
564            1 : null,
565            active : 0,
566            swap : function() {
567                this.active = this.active ? 0 : 1;
568            },
569            getActive : function() {
570                return this[this.active];
571            },
572            getNext : function() {
573                return this[Math.abs(this.active - 1)];
574            }
575        };
576        
577        var fs = this.fullscreen = {
578            scrolled: 0,
579            enter: this.proxy(function() {
580                this.toggleClass( this.get('container'), 'fullscreen');
581                fs.scrolled = jQuery(window).scrollTop();
582                this.loop(fs.getElements(), function(el, i) {
583                    fs.styles[i] = el.getAttribute('style');
584                    el.removeAttribute('style');
585                });
586                this.setStyle(fs.getElements(0), {
587                    position: 'fixed',
588                    top: 0,
589                    left: 0,
590                    width: '100%',
591                    height: '100%',
592                    zIndex: 10000
593                });
594                var bh = {
595                    height: '100%',
596                    overflow: 'hidden',
597                    margin:0,
598                    padding:0
599                };
600                this.setStyle( fs.getElements(1), bh );
601                this.setStyle( fs.getElements(2), bh );
602                this.attachKeyboard({
603                    escape: this.exitFullscreen,
604                    right: this.next,
605                    left: this.prev
606                });
607                this.rescale(this.proxy(function() {
608                    this.trigger(G.FULLSCREEN_ENTER);
609                }));
610                this.listen(window, 'resize', fs.scale);
611            }),
612            scale: this.proxy(function() {
613                this.rescale();
614            }),
615            exit: this.proxy(function() {
616                this.toggleClass( this.get('container'), 'fullscreen', false);
617                if (!fs.styles.length) {
618                    return;
619                }
620                this.loop(fs.getElements(), function(el, i) {
621                    el.removeAttribute('style');
622                    el.setAttribute('style', fs.styles[i]);
623                });
624                window.scrollTo(0, fs.scrolled);
625                this.detachKeyboard();
626                this.rescale(this.proxy(function() {
627                    this.trigger(G.FULLSCREEN_EXIT);
628                }));
629                this.forget(window, 'resize', fs.scale);
630            }),
631            styles: [],
632            getElements: this.proxy(function(i) {
633                var elems = [ this.get('container'), document.body, this.getElements('html')[0] ];
634                return i ? elems[i] : elems;
635            })
636        };
637        
638        var idle = this.idle = {
639            trunk: [],
640            bound: false,
641            add: this.proxy(function(elem, styles, fn) {
642                if (!elem) {
643                    return;
644                }
645                if (!idle.bound) {
646                    idle.addEvent();
647                }
648                elem = jQuery(elem);
649                var orig = {};
650                for (var style in styles) {
651                    orig[style] = elem.css(style);
652                }
653                elem.data('idle', {
654                    from: orig,
655                    to: styles,
656                    complete: true,
657                    busy: false,
658                    fn: this.proxy(fn)
659                });
660                idle.addTimer();
661                idle.trunk.push(elem);
662            }),
663            remove: this.proxy(function(elem) {
664                elem = jQuery(elem);
665                this.loop(idle.trunk, function(el, i) {
666                    if ( el && !el.not(elem).length ) {
667                        idle.show(elem);
668                        idle.trunk.splice(i,1);
669                    }
670                });
671                if (!idle.trunk.length) {
672                    idle.removeEvent();
673                    this.clearTimer('idle');
674                }
675            }),
676            addEvent: this.proxy(function() {
677                idle.bound = true;
678                this.listen( this.get('container'), 'mousemove click', idle.showAll );
679            }),
680            removeEvent: this.proxy(function() {
681                idle.bound = false;
682                this.forget( this.get('container'), 'mousemove click', idle.showAll );
683            }),
684            addTimer: this.proxy(function() {
685                this.addTimer('idle', this.proxy(function() {
686                    idle.hide();
687                }),this.options.idle_time);
688            }),
689            hide: this.proxy(function() {
690                this.trigger(G.IDLE_ENTER);
691                this.loop(idle.trunk, function(elem) {
692                    var data = elem.data('idle');
693                    data.complete = false;
694                    data.fn();
695                    elem.animate(data.to, {
696                        duration: 600,
697                        queue: false,
698                        easing: 'swing'
699                    });
700                });
701            }),
702            showAll: this.proxy(function() {
703                this.clearTimer('idle');
704                this.loop(idle.trunk, function(elem) {
705                    idle.show(elem);
706                });
707            }),
708            show: this.proxy(function(elem) {
709                var data = elem.data('idle');
710                if (!data.busy && !data.complete) {
711                    data.busy = true;
712                    this.trigger(G.IDLE_EXIT);
713                    elem.animate(data.from, {
714                        duration: 300,
715                        queue: false,
716                        easing: 'swing',
717                        complete: function() {
718                            $(this).data('idle').busy = false;
719                            $(this).data('idle').complete = true;
720                        }
721                    });
722                }
723                idle.addTimer();
724            })
725        };
726        
727        var lightbox = this.lightbox = {
728            w: 0,
729            h: 0,
730            initialized: false,
731            active: null,
732            init: this.proxy(function() {
733                if (lightbox.initialized) {
734                    return;
735                }
736                lightbox.initialized = true;
737                var elems = 'lightbox-overlay lightbox-box lightbox-content lightbox-shadow lightbox-title ' +
738                            'lightbox-info lightbox-close lightbox-prev lightbox-next lightbox-counter';
739                this.loop(elems.split(' '), function(el) {
740                    this.addElement(el);
741                    lightbox[el.split('-')[1]] = this.get(el);
742                });
743                
744                lightbox.image = new Galleria.Picture();
745                
746                this.append({
747                    'lightbox-box': ['lightbox-shadow','lightbox-content', 'lightbox-close'],
748                    'lightbox-info': ['lightbox-title','lightbox-counter','lightbox-next','lightbox-prev'],
749                    'lightbox-content': ['lightbox-info']
750                });
751                document.body.appendChild( lightbox.overlay );
752                document.body.appendChild( lightbox.box );
753                lightbox.content.appendChild( lightbox.image.elem );
754                
755                lightbox.close.innerHTML = '×';
756                lightbox.prev.innerHTML = '◄';
757                lightbox.next.innerHTML = '►';
758                
759                this.listen( lightbox.close, 'click', lightbox.hide );
760                this.listen( lightbox.overlay, 'click', lightbox.hide );
761                this.listen( lightbox.next, 'click', lightbox.showNext );
762                this.listen( lightbox.prev, 'click', lightbox.showPrev );
763                
764                if (this.options.lightbox_clicknext) {
765                    this.setStyle( lightbox.image.elem, {cursor:'pointer'} );
766                    this.listen( lightbox.image.elem, 'click', lightbox.showNext);
767                }
768                this.setStyle( lightbox.overlay, {
769                    position: 'fixed', display: 'none',
770                    opacity: this.options.overlay_opacity,
771                    top: 0, left: 0, width: '100%', height: '100%',
772                    background: this.options.overlay_background, zIndex: 99990
773                });
774                this.setStyle( lightbox.box, {
775                    position: 'fixed', display: 'none',
776                    width: 400, height: 400, top: '50%', left: '50%',
777                    marginTop: -200, marginLeft: -200, zIndex: 99991
778                });
779                this.setStyle( lightbox.shadow, {
780                    background:'#000', opacity:.4, width: '100%', height: '100%', position: 'absolute'
781                });
782                this.setStyle( lightbox.content, {
783                    backgroundColor:'#fff',position: 'absolute',
784                    top: 10, left: 10, right: 10, bottom: 10, overflow: 'hidden'
785                });
786                this.setStyle( lightbox.info, {
787                    color: '#444', fontSize: '11px', fontFamily: 'arial,sans-serif', height: 13, lineHeight: '13px',
788                    position: 'absolute', bottom: 10, left: 10, right: 10, opacity: 0
789                });
790                this.setStyle( lightbox.close, {
791                    background: '#fff', height: 20, width: 20, position: 'absolute', textAlign: 'center', cursor: 'pointer',
792                    top: 10, right: 10, lineHeight: '22px', fontSize: '16px', fontFamily:'arial,sans-serif',color:'#444', zIndex: 99999
793                });
794                this.setStyle( lightbox.image.elem, {
795                    top: 10, left: 10, right: 10, bottom: 30, position: 'absolute'
796                });
797                this.loop('title prev next counter'.split(' '), function(el) {
798                    var css = { display: 'inline', 'float':'left' };
799                    if (el != 'title') {
800                        this.mix(css, { 'float': 'right'});
801                        if (el != 'counter') {
802                            this.mix(css, { cursor: 'pointer'});
803                        } else {
804                            this.mix(css, { marginLeft: 8 });
805                        }
806                    }
807                    this.setStyle(lightbox[el], css);
808                });
809                this.loop('prev next close'.split(' '), function(el) {
810                    this.listen(lightbox[el], 'mouseover', this.proxy(function() {
811                        this.setStyle(lightbox[el], { color:'#000' });
812                    }));
813                    this.listen(lightbox[el], 'mouseout', this.proxy(function() {
814                        this.setStyle(lightbox[el], { color:'#444' });
815                    }));
816                });
817                this.trigger(G.LIGHTBOX_OPEN);
818            }),
819            rescale: this.proxy(function(e) {
820                var w = Math.min( this.width(window), lightbox.w );
821                var h = Math.min( this.height(window), lightbox.h );
822                var r = Math.min( (w-60) / lightbox.w, (h-80) / lightbox.h );
823                var destW = (lightbox.w * r) + 40;
824                var destH = (lightbox.h * r) + 60;
825                var dest = {
826                    width: destW,
827                    height: destH,
828                    marginTop: Math.ceil(destH/2)*-1,
829                    marginLeft: Math.ceil(destW)/2*-1
830                }
831                if (!e) {
832                    this.animate( lightbox.box, {
833                        to: dest,
834                        duration: this.options.lightbox_transition_speed,
835                        easing: 'galleria',
836                        complete: function() {
837                            this.trigger({
838                                type: G.LIGHTBOX_IMAGE,
839                                imageTarget: lightbox.image.image
840                            });
841                            this.moveIn( lightbox.image.image );
842                            this.animate( lightbox.image.image, { to: { opacity:1 }, duration: this.options.lightbox_fade_speed } );
843                            this.animate( lightbox.info, { to: { opacity:1 }, duration: this.options.lightbox_fade_speed } );
844                        }
845                    });
846                } else {
847                    this.setStyle( lightbox.box, dest );
848                }
849            }),
850            hide: this.proxy(function() {
851                lightbox.image.image = null;
852                this.forget(window, 'resize', lightbox.rescale);
853                this.hide( lightbox.box );
854                this.setStyle( lightbox.info, { opacity: 0 } );
855                this.animate( lightbox.overlay, {
856                    to: { opacity: 0 },
857                    duration: 200,
858                    complete: function() {
859                        this.hide( lightbox.overlay );
860                        this.setStyle( lightbox.overlay, { opacity: this.options.overlay_opacity});
861                        this.trigger(G.LIGHTBOX_CLOSE);
862                    }
863                });
864            }),
865            showNext: this.proxy(function() {
866                lightbox.show(this.getNext(lightbox.active));
867            }),
868            showPrev: this.proxy(function() {
869                lightbox.show(this.getPrev(lightbox.active));
870            }),
871            show: this.proxy(function(index) {
872                if (!lightbox.initialized) {
873                    lightbox.init();
874                }
875                this.forget( window, 'resize', lightbox.rescale );
876                index = typeof index == 'number' ? index : this.getIndex();
877                lightbox.active = index;
878                
879                var data = this.getData(index);
880                var total = this.data.length;
881                this.setStyle( lightbox.info, {opacity:0}
881 );
882
883                lightbox.image.load( data.image, function(o) {
884                    lightbox.w = o.scope.orig.w;
885                    lightbox.h = o.scope.orig.h;
886                    this.setStyle(o.target, {
887                        width: '100.5%',
888                        height: '100.5%',
889                        top:0,
890                        zIndex: 99998,
891                        opacity: 0
892                    });
893                    lightbox.title.innerHTML = data.title;
894                    lightbox.counter.innerHTML = (index+1) + ' / ' + total;
895                    this.listen( window, 'resize', lightbox.rescale );
896                    lightbox.rescale();
897                });
898                this.reveal( lightbox.overlay );
899                this.reveal( lightbox.box );
900            })
901        };
902        
903        this.thumbnails = { width: 0 };
904        this.stageWidth = 0;
905        this.stageHeight = 0;
906        
907        var elems = 'container stage images image-nav image-nav-left image-nav-right ' + 
908                    'info info-text info-title info-description info-author ' +
909                    'thumbnails thumbnails-list thumbnails-container thumb-nav-left thumb-nav-right ' +
910                    'loader counter';
911        elems = elems.split(' ');
912        
913        this.loop(elems, function(blueprint) {
914            this.dom[ blueprint ] = this.create('div', 'galleria-' + blueprint);
915        });
916        
917        this.target = this.dom.target = options.target.nodeName ? 
918            options.target : this.getElements(options.target)[0];
919
920        if (!this.target) {
921             G.raise('Target not found.');
922        }
923    },
924    
925    init: function() {
926        
927        this.options = this.mix(G.theme.defaults, this.options);
928        this.options = this.mix({
929            autoplay: false,
930            carousel: true,
931            carousel_follow: true,
932            carousel_speed: 400,
933            carousel_steps: 'auto',
934            clicknext: false,
935            data_config : function( elem ) { return {}; },
936            data_image_selector: 'img',
937            data_source: this.target,
938            data_type: 'auto',
939            debug: false,
940            extend: function(options) {},
941            height: 'auto',
942            idle_time: 3000,
943            image_crop: false,
944            image_margin: 0,
945            image_pan: false,
946            image_pan_smoothness: 12,
947            image_position: '50%',
948            keep_source: false,
949            lightbox_clicknext: true,
950            lightbox_fade_speed: 200,
951            lightbox_transition_speed: 300,
952            link_source_images: true,
953            max_scale_ratio: undefined,
954            min_scale_ratio: undefined,
955            on_image: function(img,thumb) {},
956            overlay_opacity: .85,
957            overlay_background: '#0b0b0b',
958            popup_links: false,
959            preload: 2,
960            queue: true,
961            show: 0,
962            show_info: true,
963            show_counter: true,
964            show_imagenav: true,
965            thumb_crop: true,
966            thumb_fit: true,
967            thumb_margin: 0,
968            thumb_quality: 'auto',
969            thumbnails: true,
970            transition: G.transitions.fade,
971            transition_speed: 400
972        }, this.options);
973        
974        var o = this.options;
975        
976        this.bind(G.DATA, function() {
977            this.run();
978        });
979        
980        if (o.clicknext) {
981            this.loop(this.data, function(data) {
982                delete data.link;
983            });
984            this.setStyle(this.get('stage'), { cursor: 'pointer'} );
985            this.listen(this.get('stage'), 'click', this.proxy(function() {
986                this.next();
987            }));
988        }
989        
990        this.bind(G.IMAGE, function(e) {
991            o.on_image.call(this, e.imageTarget, e.thumbTarget);
992        });
993        
994        this.bind(G.READY, function() {
995            if (G.History) {
996                G.History.change(this.proxy(function(e) {
997                    var val = parseInt(e.value.replace(/\//,''));
998                    if (isNaN(val)) {
999                        window.history.go(-1);
1000                    } else {
1001                        this.show(val, undefined, true);
1002                    }
1003                }));
1004            }
1005
1006            G.theme.init.call(this, o);
1007            o.extend.call(this, o);
1008            
1009            if (/^[0-9]{1,4}$/.test(hash) && G.History) {
1010                this.show(hash, undefined, true);
1011            } else if (typeof o.show == 'number') {
1012                this.show(o.show);
1013            }
1014            
1015            if (o.autoplay) {
1016                if (typeof o.autoplay == 'number') {
1017                    this.playtime = o.autoplay;
1018                }
1019                this.trigger( G.PLAY );
1020                this.playing = true;
1021            }
1022        });
1023        this.load();
1024        return this;
1025    },
1026    
1027    bind : function(type, fn) {
1028        this.listen( this.get('container'), type, this.proxy(fn) );
1029        return this;
1030    },
1031    
1032    unbind : function(type) {
1033        this.forget( this.get('container'), type );
1034    },
1035    
1036    trigger : function( type ) {
1037        type = typeof type == 'object' ? 
1038            this.mix( type, { scope: this } ) : 
1039            { type: type, scope: this };
1040        this.dispatch( this.get('container'), type );
1041        return this;
1042    },
1043    
1044    addIdleState: function() {
1045        this.idle.add.apply(this, arguments);
1046        return this;
1047    },
1048    
1049    removeIdleState: function() {
1050        this.idle.remove.apply(this, arguments);
1051        return this;
1052    },
1053    
1054    enterIdleMode: function() {
1055        this.idle.hide();
1056        return this;
1057    },
1058    
1059    exitIdleMode: function() {
1060        this.idle.show();
1061        return this;
1062    },
1063    
1064    addTimer: function() {
1065        this.timeouts.add.apply(this.timeouts, arguments);
1066        return this;
1067    },
1068    
1069    clearTimer: function() {
1070        this.timeouts.clear.apply(this.timeouts, arguments);
1071        return this;
1072    },
1073    
1074    enterFullscreen: function() {
1075        this.fullscreen.enter.apply(this, arguments);
1076        return this;
1077    },
1078    
1079    exitFullscreen: function() {
1080        this.fullscreen.exit.apply(this, arguments);
1081        return this;
1082    },
1083    
1084    openLightbox: function() {
1085        this.lightbox.show.apply(this, arguments);
1086    },
1087    
1088    closeLightbox: function() {
1089        this.lightbox.hide.apply(this, arguments);
1090    },
1091    
1092    getActive: function() {
1093        return this.controls.getActive();
1094    },
1095    
1096    getActiveImage: function() {
1097        return this.getActive().image || null;
1098    },
1099    
1100    run : function() {
1101        var o = this.options;
1102        if (!this.data.length) {
1103            G.raise('Data is empty.');
1104        }
1105        if (!o.keep_source && !Galleria.IE) {
1106            this.target.innerHTML = '';
1107        }
1108        this.loop(2, function() {
1109            var image = new Picture();
1110            this.setStyle( image.elem, {
1111                position: 'absolute',
1112                top: 0,
1113                left: 0
1114            });
1115            this.setStyle(this.get( 'images' ), {
1116                position: 'relative',
1117                top: 0,
1118                left: 0,
1119                width: '100%',
1120                height: '100%'
1121            });
1122            this.get( 'images' ).appendChild( image.elem );
1123            this.push(image, this.controls);
1124        }, this);
1125        
1126        if (o.carousel) {
1127            // try the carousel on each thumb load
1128            this.bind(G.THUMBNAIL, this.parseCarousel);
1129        }
1130        
1131        this.build();
1132        this.target.appendChild(this.get('container'));
1133        
1134        this.loop(['info','counter','image-nav'], function(el) {
1135            if ( o[ 'show_'+el.replace(/-/,'') ] === false ) {
1136                this.moveOut( this.get(el) );
1137            }
1138        });
1139        
1140        var w = 0;
1141        var h = 0;
1142        
1143        for( var i=0; this.data[i]; i++ ) {
1144            var thumb;
1145            if (o.thumbnails === true) {
1146                thumb = new Picture(i);
1147                var src = this.data[i].thumb || this.data[i].image;
1148                
1149                this.get( 'thumbnails' ).appendChild( thumb.elem );
1150                
1151                w = this.getStyle(thumb.elem, 'width', true);
1152                h = this.getStyle(thumb.elem, 'height', true);
1153                
1154                // grab & reset size for smoother thumbnail loads
1155                if (o.thumb_fit && o.thum_crop !== true) {
1156                    this.setStyle(thumb.elem, { width:0, height: 0});
1157                }
1158                
1159                thumb.load(src, this.proxy(function(e) {
1160                    var orig = e.target.width;
1161                    e.scope.scale({
1162                        width: w,
1163                        height: h,
1164                        crop: o.thumb_crop,
1165                        margin: o.thumb_margin,
1166                        complete: this.proxy(function() {
1167                            // shrink thumbnails to fit
1168                            var top = ['left', 'top'];
1169                            var arr = ['Height', 'Width'];
1170                            this.loop(arr, function(m,i) {
1171                                if ((!o.thumb_crop || o.thumb_crop == m.toLowerCase()) && o.thumb_fit) {
1172                                    var css = {};
1173                                    var opp = arr[Math.abs(i-1)].toLowerCase();
1174                                    css[opp] = e.target[opp];
1175                                    this.setStyle(e.target.parentNode, css);
1176                                    var css = {};
1177                                    css[top[i]] = 0;
1178                                    this.setStyle(e.target, css);
1179                                }
1180                                e.scope['outer'+m] = this[m.toLowerCase()](e.target.parentNode, true);
1181                            });
1182                            // set high quality if downscale is moderate
1183                            this.toggleQuality(e.target, o.thumb_quality === true || ( o.thumb_quality == 'auto' && orig < e.target.width * 3 ));
1184                            this.trigger({
1185                                type: G.THUMBNAIL,
1186                                thumbTarget: e.target,
1187                                thumbOrder: e.scope.order
1188                            });
1189                        })
1190                    });
1191                }));
1192                if (o.preload == 'all') {
1193                    thumb.add(this.data[i].image);
1194                }
1195            } else if (o.thumbnails == 'empty') {
1196                thumb = {
1197                    elem:  this.create('div','galleria-image'),
1198                    image: this.create('span','img')
1199                };
1200                thumb.elem.appendChild(thumb.image);
1201                this.get( 'thumbnails' ).appendChild( thumb.elem );
1202            } else {
1203                thumb = {
1204                    elem: false,
1205                    image: false
1206                }
1207            }
1208            var activate = this.proxy(function(e) {
1209                this.pause();
1210                e.preventDefault();
1211                var ind = e.currentTarget.rel;
1212                if (this.active !== ind) {
1213                    this.show( ind );
1214                }
1215            });
1216            if (o.thumbnails !== false) {
1217                thumb.elem.rel = i;
1218                this.listen(thumb.elem, 'click', activate);
1219            }
1220            if (o.link_source_images && o.keep_source && this.data[i].elem) {
1221                this.data[i].elem.rel = i;
1222                this.listen(this.data[i].elem, 'click', activate);
1223            }
1224            this.push(thumb, this.thumbnails );
1225        }
1226        this.setStyle( this.get('thumbnails'), { opacity: 0 } );
1227        
1228        if (o.height && o.height != 'auto') {
1229            this.setStyle( this.get('container'), { height: o.height })
1230        }
1231        
1232        this.wait(function() {
1233            // the most sensitive piece of code in Galleria, we need to have all the meassurements right to continue
1234            var cssHeight = this.getStyle( this.get( 'container' ), 'height', true );
1235            this.stageWidth = this.width(this.get( 'stage' ));
1236            this.stageHeight = this.height( this.get( 'stage' ));
1237            if (this.stageHeight < 50 && o.height == 'auto') {
1238                // no height detected for sure, set reasonable ratio (16/9)
1239                this.setStyle( this.get( 'container' ),  { 
1240                    height: Math.round( this.stageWidth*9/16 ) 
1241                } );
1242                this.stageHeight = this.height( this.get( 'stage' ));
1243            }
1244            return this.stageHeight && this.stageWidth;
1245        }, function() {
1246            this.listen(this.get('image-nav-right'), 'click', this.proxy(function(e) {
1247                if (o.clicknext) {
1248                    e.stopPropagation();
1249                }
1250                this.pause();
1251                this.next();
1252            }));
1253            this.listen(this.get('image-nav-left'), 'click', this.proxy(function(e) {
1254                if (o.clicknext) {
1255                    e.stopPropagation();
1256                }
1257                this.pause();
1258                this.prev();
1259            }));
1260            this.setStyle( this.get('thumbnails'), { opacity: 1 } );
1261            this.trigger( G.READY );
1262        }, function() {
1263            G.raise('Galleria could not load properly. Make sure stage has a height and width.');
1264        }, 5000);
1265    },
1266    
1267    mousePosition : function(e) {
1268        return {
1269            x: e.pageX - this.$('stage').offset().left + jQuery(document).scrollLeft(),
1270            y: e.pageY - this.$('stage').offset().top + jQuery(document).scrollTop()
1271        };
1272    },
1273    
1274    addPan : function(img) {
1275        var c = this.options.image_crop;
1276        if ( c === false ) {
1277            return;
1278        }
1279        if (this.options.image_crop === false) {
1280            return;
1281        }
1282        img = img || this.controls.getActive().image;
1283        if (img.tagName.toUpperCase() != 'IMG') {
1284            G.raise('Could not add pan');
1285        }
1286        
1287        var x = img.width/2;
1288        var y = img.height/2;
1289        var curX = destX = this.getStyle(img, 'left', true) || 0;
1290        var curY = destY = this.getStyle(img, 'top', true) || 0;
1291        var distX = 0;
1292        var distY = 0;
1293        var active = false;
1294        var ts = new Date().getTime();
1295        var calc = this.proxy(function(e) {
1296            if (new Date().getTime() - ts < 50) {
1297                return;
1298            }
1299            active = true;
1300            x = this.mousePosition(e).x;
1301            y = this.mousePosition(e).y;
1302        });
1303        var loop = this.proxy(function(e) {
1304            if (!active) {
1305                return;
1306            }
1307            distX = img.width - this.stageWidth;
1308            distY = img.height - this.stageHeight;
1309            destX = x / this.stageWidth * distX * -1;
1310            destY = y / this.stageHeight * distY * -1;
1311            curX += (destX - curX) / this.options.image_pan_smoothness;
1312            curY += (destY - curY) / this.options.image_pan_smoothness;
1313            if (distY > 0) {
1314                this.setStyle(img, { top: Math.max(distY*-1, Math.min(0, curY)) });
1315            }
1316            if (distX > 0) {
1317                this.setStyle(img, { left: Math.max(distX*-1, Math.min(0, curX)) });
1318            }
1319        });
1320        this.forget(this.get('stage'), 'mousemove');
1321        this.listen(this.get('stage'), 'mousemove', calc);
1322        this.addTimer('pan', loop, 30, true);
1323    },
1324    
1325    removePan: function() {
1326        this.forget(this.get('stage'), 'mousemove');
1327        this.clearTimer('pan');
1328    },
1329    
1330    parseCarousel : function(e) {
1331        var w = 0;
1332        var h = 0;
1333        var hooks = [0];
1334        this.loop(this.thumbnails, function(thumb,i) {
1335            if (thumb.ready) {
1336                w += thumb.outerWidth || this.width(thumb.elem, true);
1337                hooks[i+1] = w;
1338                h = Math.max(h, this.height(thumb.elem));
1339            }
1340        });
1341        this.toggleClass(this.get('thumbnails-container'), 'galleria-carousel', w > this.stageWidth);
1342        this.setStyle(this.get('thumbnails-list'), {
1343            overflow:'hidden',
1344            position: 'relative' // for IE Standards mode
1345        });
1346        this.setStyle(this.get('thumbnails'), {
1347            width: w,
1348            height: h,
1349            position: 'relative',
1350            overflow: 'hidden'
1351        });
1352        if (!this.carousel) {
1353            this.initCarousel();
1354        }
1355        this.carousel.max = w;
1356        this.carousel.hooks = hooks;
1357        this.carousel.width = this.width(this.get('thumbnails-list'));
1358        this.carousel.setClasses();
1359    },
1360    
1361    initCarousel : function() {
1362        var c = this.carousel = {
1363            right: this.get('thumb-nav-right'),
1364            left: this.get('thumb-nav-left'),
1365            update: this.proxy(function() {
1366                this.parseCarousel();
1367                // todo: fix so the carousel moves to the left
1368            }),
1369            width: 0,
1370            current: 0,
1371            set: function(i) {
1372                i = Math.max(i,0);
1373                while (c.hooks[i-1] + c.width > c.max && i >= 0) {
1374                    i--;
1375                }
1376                c.current = i;
1377                c.animate();
1378            },
1379            hooks: [],
1380            getLast: function(i) {
1381                i = i || c.current
1382                
1383                return i-1;
1384            },
1385            follow: function(i) {
1386                if (i == 0 || i == c.hooks.length-2) {
1387                    c.set(i);
1388                    return;
1389                }
1390                var last = c.current;
1391                while(c.hooks[last] - c.hooks[c.current] < c.width && last<= c.hooks.length) {
1392                    last++;
1393                }
1394                if (i-1 < c.current) {
1395                    c.set(i-1)
1396                } else if (i+2 > last) {
1397                    c.set(i - last + c.current + 2)
1398                }
1399            },
1400            max: 0,
1401            setClasses: this.proxy(function() {
1402                this.toggleClass( c.left, 'disabled', !c.current );
1403                this.toggleClass( c.right, 'disabled', c.hooks[c.current] + c.width > c.max );
1404            }),
1405            animate: this.proxy(function(to) {
1406                c.setClasses();
1407                this.animate( this.get('thumbnails'), {
1408                    to: { left: c.hooks[c.current] * -1 },
1409                    duration: this.options.carousel_speed,
1410                    easing: 'galleria',
1411                    queue: false
1412                });
1413            })
1414        };
1415        this.listen(c.right, 'click', this.proxy(function(e) {
1416            if (this.options.carousel_steps == 'auto') {
1417                for (var i = c.current; i<c.hooks.length; i++) {
1418                    if (c.hooks[i] - c.hooks[c.current] > c.width) {
1419                        c.set(i-2);
1420                        break;
1421                    }
1422                }
1423            } else {
1424                c.set(c.current + this.options.carousel_steps);
1425            }
1426        }));
1427        this.listen(c.left, 'click', this.proxy(function(e) {
1428            if (this.options.carousel_steps == 'auto') {
1429                for (var i = c.current; i>=0; i--) {
1430                    if (c.hooks[c.current] - c.hooks[i] > c.width) {
1431                        c.set(i+2);
1432                        break;
1433                    } else if (i == 0) {
1434                        c.set(0);
1435                        break;
1436                    }
1437                }
1438            } else {
1439                c.set(c.current - this.options.carousel_steps);
1440            }
1441        }));
1442    },
1443    addElement : function() {
1444        this.loop(arguments, function(b) {
1445            this.dom[b] = this.create('div', 'galleria-' + b );
1446        });
1447        return this;
1448    },
1449    getDimensions: function(i) {
1450        return {
1451            w: i.width,
1452            h: i.height,
1453            cw: this.stageWidth,
1454            ch: this.stageHeight,
1455            top: (this.stageHeight - i.height) / 2,
1456            left: (this.stageWidth - i.width) / 2
1457        };
1458    },
1459    attachKeyboard : function(map) {
1460        this.keyboard.attach(map);
1461        return this;
1462    },
1463    detachKeyboard : function() {
1464        this.keyboard.detach();
1465        return this;
1466    },
1467    build : function() {
1468        this.append({
1469            'info-text' :
1470                ['info-title', 'info-description', 'info-author'],
1471            'info' : 
1472                ['info-text'],
1473            'image-nav' : 
1474                ['image-nav-right', 'image-nav-left'],
1475            'stage' : 
1476                ['images', 'loader', 'counter', 'image-nav'],
1477            'thumbnails-list' :
1478                ['thumbnails'],
1479            'thumbnails-container' : 
1480                ['thumb-nav-left', 'thumbnails-list', 'thumb-nav-right'],
1481            'container' : 
1482                ['stage', 'thumbnails-container', 'info']
1483        });
1484        
1485        this.current = this.create('span', 'current');
1486        this.current.innerHTML = '-';
1487        this.get('counter').innerHTML = ' / <span class="total">' + this.data.length + '</span>';
1488        this.prependChild('counter', this.current);
1489    },
1490    
1491    appendChild : function(parent, child) {
1492        try {
1493            this.get(parent).appendChild(this.get(child));
1494        } catch(e) {}
1495    },
1496    
1497    prependChild : function(parent, child) {
1498        var child = this.get(child) || child;
1499        try {
1500            this.get(parent).insertBefore(child, this.get(parent).firstChild);
1501        } catch(e) {}
1502    },
1503    
1504    remove : function() {
1505        var a = Array.prototype.slice.call(arguments);
1506        this.jQuery(a.join(',')).remove();
1507    },
1508    
1509    append : function(data) {
1510        for( var i in data) {
1511            if (data[i].constructor == Array) {
1512                for(var j=0; data[i][j]; j++) {
1513                    this.appendChild(i, data[i][j]);
1514                }
1515            } else {
1516                this.appendChild(i, data[i]);
1517            }
1518        }
1519        return this;
1520    },
1521    
1522    rescale : function(width, height, callback) {
1523        
1524        var o = this.options;
1525        callback = this.proxy(callback);
1526        
1527        if (typeof width == 'function') {
1528            callback = this.proxy(width);
1529            width = undefined;
1530        }
1531        
1532        var scale = this.proxy(function() {
1533            this.stageWidth = width || this.width(this.get('stage'));
1534            this.stageHeight = height || this.height(this.get('stage'));
1535            this.controls.getActive().scale({
1536                width: this.stageWidth, 
1537                height: this.stageHeight, 
1538                crop: o.image_crop, 
1539                max: o.max_scale_ratio,
1540                min: o.min_scale_ratio,
1541                margin: o.image_margin,
1542                position: o.image_position
1543            });
1544            if (this.carousel) {
1545                this.carousel.update();
1546            }
1547            this.trigger(G.RESCALE)
1548            callback();
1549        });
1550        if ( G.WEBKIT && !width && !height ) {
1551            this.addTimer('scale', scale, 5);// webkit is too fast
1552        } else {
1553            scale.call(this); 
1554        }
1555    },
1556    
1557    show : function(index, rewind, history) {
1558        if (!this.options.queue && this.queue.stalled) {
1559            return;
1560        }
1561        rewind = typeof rewind != 'undefined' ? !!rewind : index < this.active;
1562        history = history || false;
1563        index = Math.max(0, Math.min(parseInt(index), this.data.length - 1));
1564        if (!history && G.History) {
1565            G.History.value(index.toString());
1566            return;
1567        }
1568        this.active = index;
1569        this.push([index,rewind], this.queue);
1570        if (!this.queue.stalled) {
1571            this.showImage();
1572        }
1573        return this;
1574    },
1575    
1576    showImage : function() {
1577        var o = this.options;
1578        var args = this.queue[0];
1579        var index = args[0];
1580        var rewind = !!args[1];
1581        if (o.carousel && this.carousel && o.carousel_follow) {
1582            this.carousel.follow(index);
1583        }
1584        
1585        var src = this.getData(index).image;
1586        var active = this.controls.getActive();
1587        var next = this.controls.getNext();
1588        var cached = next.isCached(src);
1589        var complete = this.proxy(function() {
1590            this.queue.stalled = false;
1591            this.toggleQuality(next.image, o.image_quality);
1592            this.setStyle( active.elem, { zIndex : 0 } );
1593            this.setStyle( next.elem, { zIndex : 1 } );
1594            this.trigger({
1595                type: G.IMAGE,
1596                index: index,
1597                imageTarget: next.image,
1598                thumbTarget: this.thumbnails[index].image
1599            });
1600            if (o.image_pan) {
1601                this.addPan(next.image);
1602            }
1603            this.controls.swap();
1604            this.moveOut( active.image );
1605            if (this.getData( index ).link) {
1606                this.setStyle( next.image, { cursor: 'pointer' } );
1607                this.listen( next.image, 'click', this.proxy(function() {
1608                    if (o.popup_links) {
1609                        var win = window.open(this.getData( index ).link, '_blank');
1610                    } else {
1611                        window.location.href = this.getData( index ).link;
1612                    }
1613                }));
1614            }
1615            Array.prototype.shift.call( this.queue );
1616            if (this.queue.length) {
1617                this.showImage();
1618            }
1619            this.playCheck();
1620        });
1621        if (typeof o.preload == 'number' && o.preload > 0) {
1622            var p,n = this.getNext();
1623            try {
1624                for (var i = o.preload; i>0; i--) {
1625                    p = new Picture();
1626                    p.add(this.getData(n).image);
1627                    n = this.getNext(n);
1628                }
1629            } catch(e) {}
1630        }
1631        this.trigger( {
1632            type: G.LOADSTART,
1633            cached: cached,
1634            index: index,
1635            imageTarget: next.image,
1636            thumbTarget: this.thumbnails[index].image
1637        } );
1638        
1639        jQuery(this.thumbnails[index].elem).addClass('active').siblings('.active').removeClass('active');
1640        
1641        next.load( src, this.proxy(function(e) {
1642            next.scale({
1643                width: this.stageWidth, 
1644                height: this.stageHeight, 
1645                crop: o.image_crop, 
1646                max: o.max_scale_ratio, 
1647                min: o.min_scale_ratio,
1648                margin: o.image_margin,
1649                position: o.image_position,
1650                complete: this.proxy(function() {
1651                    if (active.image) {
1652                        this.toggleQuality(active.image, false);
1653                    }
1654                    this.toggleQuality(next.image, false);
1655                    this.trigger({
1656                        type: G.LOADFINISH,
1657                        cached: cached,
1658                        index: index,
1659                        imageTarget: next.image,
1660                        thumbTarget: this.thumbnails[index].image
1661                    });
1662                    this.queue.stalled = true;
1663                    var transition = G.transitions[o.transition] || o.transition;
1664                    this.removePan();
1665                    this.setInfo(index);
1666                    this.setCounter(index);
1667                    if (typeof transition == 'function') {
1668                        transition.call(this, {
1669                            prev: active.image,
1670                            next: next.image,
1671                            rewind: rewind,
1672                            speed: o.transition_speed || 400
1673                        }, complete );
1674                    } else {
1675                        complete();
1676                    }
1677                })
1678            });
1679        }));
1680    },
1681    
1682    getNext : function(base) {
1683        base = typeof base == 'number' ? base : this.active;
1684        return base == this.data.length - 1 ? 0 : base + 1;
1685    },
1686    
1687    getPrev : function(base) {
1688        base = typeof base == 'number' ? base : this.active;
1689        return base === 0 ? this.data.length - 1 : base - 1;
1690    },
1691    
1692    next : function() {
1693        if (this.data.length > 1) {
1694            this.show(this.getNext(), false);
1695        }
1696        return this;
1697    },
1698    
1699    prev : function() {
1700        if (this.data.length > 1) {
1701            this.show(this.getPrev(), true);
1702        }
1703        return this;
1704    },
1705    
1706    get : function( elem ) {
1707        return elem in this.dom ? this.dom[ elem ] : null;
1708    },
1709    
1710    getData : function( index ) {
1711        return this.data[index] || this.data[this.active];
1712    },
1713    
1714    getIndex : function() {
1715        return typeof this.active === 'number' ? this.active : 0;
1716    },
1717    
1718    play : function(delay) {
1719        this.trigger( G.PLAY );
1720        this.playing = true;
1721        this.playtime = delay || this.playtime;
1722        this.playCheck();
1723        return this;
1724    },
1725    
1726    pause : function() {
1727        this.trigger( G.PAUSE );
1728        this.playing = false;
1729        return this;
1730    },
1731    
1732    playCheck : function() {
1733        var p = 0;
1734        var i = 20; // the interval
1735        var ts = function() {
1736            return new Date().getTime();
1737        }
1738        var now = ts();
1739        if (this.playing) {
1740            this.clearTimer('play');
1741            var fn = this.proxy(function() {
1742                p = ts() - now;
1743                if ( p >= this.playtime && this.playing ) {
1744                    this.clearTimer('play');
1745                    this.next();
1746                    return;
1747                }
1748                if ( this.playing ) {
1749                    this.trigger({
1750                        type: G.PROGRESS,
1751                        percent: Math.ceil(p / this.playtime * 100),
1752                        seconds: Math.floor(p/1000),
1753                        milliseconds: p
1754                    });
1755                    this.addTimer('play', fn, i);
1756                }
1757            });
1758            this.addTimer('play', fn, i);
1759        }
1760    },
1761    
1762    setActive: function(val) {
1763        this.active = val;
1764        return this;
1765    },
1766    
1767    setCounter: function(index) {
1768        index = index || this.active;
1769        this.current.innerHTML = index+1;
1770        return this;
1771    },
1772    
1773    setInfo : function(index) {
1774        var data = this.getData(index || this.active);
1775        this.loop(['title','description','author'], function(type) {
1776            var elem = this.get('info-'+type);
1777            var fn = data[type] && data[type].length ? 'reveal' : 'hide';
1778            this[fn](elem);
1779            if (data[type]) {
1780                elem.innerHTML = data[type];
1781            }
1782        });
1783        return this;
1784    },
1785    
1786    hasInfo : function(index) {
1787        var d = this.getData(index);
1788        var check = 'title description author'.split(' ');
1789        for ( var i=0; check[i]; i++ ) {
1790            if ( d[ check[i] ] && d[ check[i] ].length ) {
1791                return true;
1792            }
1793        }
1794        return false;
1795    },
1796    
1797    getDataObject : function(o) {
1798        var obj = {
1799            image: '',
1800            thumb: '',
1801            title: '',
1802            description: '',
1803            author: '',
1804            link: ''
1805        };
1806        return o ? this.mix(obj,o) : obj;
1807    },
1808    
1809    jQuery : function( str ) {
1810        var ret = [];
1811        this.loop(str.split(','), this.proxy(function(elem) {
1812            elem = elem.replace(/^\s\s*/, "").replace(/\s\s*$/, "");
1813            if (this.get(elem)) {
1814                ret.push(elem);
1815            }
1816        }));
1817        var jQ = jQuery(this.get(ret.shift()));
1818        this.loop(ret, this.proxy(function(elem) {
1819            jQ = jQ.add(this.get(elem));
1820        }));
1821        return jQ;
1822    },
1823    
1824    $ : function( str ) {
1825        return this.jQuery( str );
1826    },
1827    
1828    toggleQuality : function(img, force) {
1829        if (!G.IE7 || typeof img == 'undefined' || !img) {
1830            return this;
1831        }
1832        if (typeof force === 'undefined') {
1833            force = img.style.msInterpolationMode == 'nearest-neighbor';
1834        }
1835        img.style.msInterpolationMode = force ? 'bicubic' : 'nearest-neighbor';
1836
1837        return this;
1838    },
1839    
1840    unload : function() {
1841        //TODO
1842    },
1843    
1844    load : function() {
1845        var loaded = 0;
1846        var o = this.options;
1847        if (
1848            (o.data_type == 'auto' && 
1849                typeof o.data_source == 'object' && 
1850                !(o.data_source instanceof jQuery) && 
1851                !o.data_source.tagName
1852            ) || o.data_type == 'json' || o.data_source.constructor == Array ) {
1853            this.data = o.data_source;
1854            this.trigger( G.DATA );
1855            
1856        } else { // assume selector
1857            var images = jQuery(o.data_source).find(o.data_image_selector);
1858            var getData = this.proxy(function( elem ) {
1859                var i,j,anchor = elem.parentNode;
1860                if (anchor && anchor.nodeName == 'A') {
1861                    if (anchor.href.match(/\.(png|gif|jpg|jpeg)/i)) {
1862                        i = anchor.href;
1863                    } else {
1864                        j = anchor.href;
1865                    }
1866                }
1867                var obj = this.getDataObject({
1868                    title: elem.title,
1869                    thumb: elem.src,
1870                    image: i || elem.src,
1871                    description: elem.alt,
1872                    link: j || elem.getAttribute('longdesc'),
1873                    elem: elem
1874                });
1875                return this.mix(obj, o.data_config( elem ) );
1876            });
1877            this.loop(images, function( elem ) {
1878                loaded++;
1879                this.push( getData( elem ), this.data );
1880                if (!o.keep_source && !Galleria.IE) {
1881                    elem.parentNode.removeChild(elem);
1882                }
1883                if ( loaded == images.length ) {
1884                    this.trigger( G.DATA );
1885                }
1886            });
1887        }
1888    }
1889});
1890
1891G.log = function() {
1892    try { 
1893        console.log.apply( console, Array.prototype.slice.call(arguments) ); 
1894    } catch(e) {
1895        try {
1896            opera.postError.apply( opera, arguments ); 
1897        } catch(er) { 
1898              alert( Array.prototype.join.call( arguments, " " ) ); 
1899        } 
1900    }
1901};
1902
1903var nav = navigator.userAgent.toLowerCase();
1904var hash = window.location.hash.replace(/#\//,'');
1905
1906G.DATA = 'data';
1907G.READY = 'ready';
1908G.THUMBNAIL = 'thumbnail';
1909G.LOADSTART = 'loadstart';
1910G.LOADFINISH = 'loadfinish';
1911G.IMAGE = 'image';
1912G.THEMELOAD = 'themeload';
1913G.PLAY = 'play';
1914G.PAUSE = 'pause';
1915G.PROGRESS = 'progress';
1916G.FULLSCREEN_ENTER = 'fullscreen_enter';
1917G.FULLSCREEN_EXIT = 'fullscreen_exit';
1918G.IDLE_ENTER = 'idle_enter';
1919G.IDLE_EXIT = 'idle_exit';
1920G.RESCALE = 'rescale';
1921G.LIGHTBOX_OPEN = 'lightbox_open';
1922G.LIGHTBOX_CLOSE = 'lightbox_cloe';
1923G.LIGHTBOX_IMAGE = 'lightbox_image';
1924
1925G.IE8 = (typeof(XDomainRequest) !== 'undefined')
1926G.IE7 = !!(window.XMLHttpRequest && document.expando);
1927G.IE6 = (!window.XMLHttpRequest);
1928G.IE = !!(G.IE6 || G.IE7);
1929G.WEBKIT = /webkit/.test( nav );
1930G.SAFARI = /safari/.test( nav );
1931G.CHROME = /chrome/.test( nav );
1932G.QUIRK = (G.IE && document.compatMode && document.compatMode == "BackCompat");
1933G.MAC = /mac/.test(navigator.platform.toLowerCase());
1934G.OPERA = !!window.opera
1935
1936G.Picture = Picture;
1937
1938G.addTheme = function(obj) {
1939    var theme = {};
1940    var orig = ['name','author','version','defaults','init'];
1941    var proto = G.prototype;
1942    proto.loop(orig, function(val) {
1943        if (!obj[ val ]) {
1944            G.raise(val+' not specified in theme.');
1945        }
1946        if (val != 'name' && val != 'init') {
1947            theme[val] = obj[val];
1948        }
1949    });
1950    theme.init = obj.init;
1951    
1952    if (obj.css) {
1953        var css;
1954        proto.loop(proto.getElements('script'), function(el) {
1955            var reg = new RegExp('galleria.' + obj.name.toLowerCase() + '.js');
1956            if(reg.test(el.src)) {
1957                css = el.src.replace(/[^\/]*$/, "") + obj.css;
1958                proto.loadCSS(css, function() {
1959                    G.theme = theme;
1960                    jQuery(document).trigger( G.THEMELOAD );
1961                });
1962            }
1963        });
1964        if (!css) {
1965            G.raise('No theme CSS loaded');
1966        }
1967    }
1968    return theme;
1969};
1970
1971G.raise = function(msg) {
1972    if ( G.debug ) {
1973        throw new Error( msg );
1974    }
1975};
1976
1977G.loadTheme = function(src) {
1978    G.prototype.loadScript(src);
1979};
1980
1981G.galleries = [];
1982G.get = function(index) {
1983    if (G.galleries[index]) {
1984        return G.galleries[index];
1985    } else if (typeof index !== 'number') {
1986        return G.galleries;
1987    } else {
1988        G.raise('Gallery index not found');
1989    }
1990}
1991
1992jQuery.easing.galleria = function (x, t, b, c, d) {
1993    if ((t/=d/2) < 1) { 
1994        return c/2*t*t*t*t + b;
1995    }
1996    return -c/2 * ((t-=2)*t*t*t - 2) + b;
1997};
1998
1999G.transitions = {
2000    add: function(name, fn) {
2001        if (name != arguments.callee.name ) {
2002            this[name] = fn;
2003        }
2004    },
2005    fade: function(params, complete) {
2006        jQuery(params.next).show().css('opacity',0).animate({
2007            opacity: 1
2008        }, params.speed, complete);
2009        if (params.prev) {
2010            jQuery(params.prev).css('opacity',1).animate({
2011                opacity: 0
2012            }, params.speed);
2013        }
2014    },
2015    flash: function(params, complete) {
2016        jQuery(params.next).css('opacity',0);
2017        if (params.prev) {
2018            jQuery(params.prev).animate({
2019                opacity: 0
2020            }, (params.speed/2), function() {
2021                jQuery(params.next).animate({
2022                    opacity: 1
2023                }, params.speed, complete);
2024            });
2025        } else {
2026            jQuery(params.next).animate({
2027                opacity: 1
2028            }, params.speed, complete);
2029        }
2030    },
2031    pulse: function(params, complete) {
2032        if (params.prev) {
2033            jQuery(params.prev).css('opacity',0);
2034        }
2035        jQuery(params.next).css('opacity',0).animate({
2036            opacity:1
2037        }, params.speed, complete);
2038    },
2039    slide: function(params, complete) {
2040        var image = jQuery(params.next).parent();
2041        var images =  this.$('images');
2042        var width = this.stageWidth;
2043        image.css({
2044            left: width * ( params.rewind ? -1 : 1 )
2045        });
2046        images.animate({
2047            left: width * ( params.rewind ? 1 : -1 )
2048        }, {
2049            duration: params.speed,
2050            queue: false,
2051            easing: 'galleria',
2052            complete: function() {
2053                images.css('left',0);
2054                image.css('left',0);
2055                complete();
2056            }
2057        });
2058    },
2059    fadeslide: function(params, complete) {
2060        if (params.prev) {
2061            jQuery(params.prev).css({
2062                opacity: 1,
2063                left: 0
2064            }).animate({
2065                opacity: 0,
2066                left: 50 * ( params.rewind ? 1 : -1 )
2067            },{
2068                duration: params.speed,
2069                queue: false,
2070                easing: 'swing'
2071            });
2072        }
2073        jQuery(params.next).css({
2074            left: 50 * ( params.rewind ? -1 : 1 ), 
2075            opacity: 0
2076        }).animate({
2077            opacity: 1,
2078            left:0
2079        }, {
2080            duration: params.speed,
2081            complete: complete,
2082            queue: false,
2083            easing: 'swing'
2084        });
2085    }
2086};
2087
2088G.addTransition = function() {
2089    G.transitions.add.apply(this, arguments);
2090}
2091
2092jQuery.fn.galleria = function(options) {
2093    
2094    options = options || {};
2095    var selector = this.selector;
2096    
2097    return this.each(function() {
2098        if ( !options.keep_source ) {
2099            jQuery(this).children().hide();
2100        }
2101    
2102        options = G.prototype.mix(options, {target: this} );
2103        var height = G.prototype.height(this) || G.prototype.getStyle(this, 'height', true);
2104        if (!options.height && height) {
2105            options = G.prototype.mix( { height: height }, options );
2106        }
2107    
2108        G.debug = !!options.debug;
2109    
2110        var gallery = new G(options);
2111        
2112        Galleria.galleries.push(gallery);
2113    
2114        if (G.theme) {
2115            gallery.init();
2116        } else {
2117            jQuery(document).bind(G.THEMELOAD, function() {
2118                gallery.init();
2119            });
2120        }
2121    })
2122};
2123
2124
2125})();

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.