a fix
[scribo] / www / galleria.js
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
11 var initializing = false,
12     fnTest = /xyz/.test(function(){xyz;}) ? /\b__super\b/ : /.*/,
13     Class = function(){},
14     window = this;
15
16 Class.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
48 var 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
328 var 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
486 var 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} );
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
1891 G.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
1903 var nav = navigator.userAgent.toLowerCase();
1904 var hash = window.location.hash.replace(/#\//,'');
1905
1906 G.DATA = 'data';
1907 G.READY = 'ready';
1908 G.THUMBNAIL = 'thumbnail';
1909 G.LOADSTART = 'loadstart';
1910 G.LOADFINISH = 'loadfinish';
1911 G.IMAGE = 'image';
1912 G.THEMELOAD = 'themeload';
1913 G.PLAY = 'play';
1914 G.PAUSE = 'pause';
1915 G.PROGRESS = 'progress';
1916 G.FULLSCREEN_ENTER = 'fullscreen_enter';
1917 G.FULLSCREEN_EXIT = 'fullscreen_exit';
1918 G.IDLE_ENTER = 'idle_enter';
1919 G.IDLE_EXIT = 'idle_exit';
1920 G.RESCALE = 'rescale';
1921 G.LIGHTBOX_OPEN = 'lightbox_open';
1922 G.LIGHTBOX_CLOSE = 'lightbox_cloe';
1923 G.LIGHTBOX_IMAGE = 'lightbox_image';
1924
1925 G.IE8 = (typeof(XDomainRequest) !== 'undefined')
1926 G.IE7 = !!(window.XMLHttpRequest && document.expando);
1927 G.IE6 = (!window.XMLHttpRequest);
1928 G.IE = !!(G.IE6 || G.IE7);
1929 G.WEBKIT = /webkit/.test( nav );
1930 G.SAFARI = /safari/.test( nav );
1931 G.CHROME = /chrome/.test( nav );
1932 G.QUIRK = (G.IE && document.compatMode && document.compatMode == "BackCompat");
1933 G.MAC = /mac/.test(navigator.platform.toLowerCase());
1934 G.OPERA = !!window.opera
1935
1936 G.Picture = Picture;
1937
1938 G.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
1971 G.raise = function(msg) {
1972     if ( G.debug ) {
1973         throw new Error( msg );
1974     }
1975 };
1976
1977 G.loadTheme = function(src) {
1978     G.prototype.loadScript(src);
1979 };
1980
1981 G.galleries = [];
1982 G.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
1992 jQuery.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
1999 G.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
2088 G.addTransition = function() {
2089     G.transitions.add.apply(this, arguments);
2090 }
2091
2092 jQuery.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 })();