PageSourceSearch

https://www.fundacionlaboral.org/bower_components/shadowbox/build/shadowbox.js

js fundacionlaboral.org collected 2026-09-24 08:52:28 UTC 136,430 bytes, 5,212 lines download raw bytes

1/*!
2 * Shadowbox.js, version 3.0.3
3 * http://shadowbox-js.com/
4 *
5 * Copyright 2007-2010, Michael J. I. Jackson
6 * Date: 2015-06-23 17:44:16 +0200
7 */
8(function(window, undefined) {
9/**
10 * The Shadowbox object.
11 *
12 * @type    {Object}
13 * @public
14 */
15var S = {
16
17    /**
18     * The current version of Shadowbox.
19     *
20     * @type    {String}
21     * @public
22     */
23    version: "3.0.3"
24
25}
26
27var ua = navigator.userAgent.toLowerCase();
28
29// operating system detection
30if (ua.indexOf('windows') > -1 || ua.indexOf('win32') > -1) {
31    S.isWindows = true;
32} else if (ua.indexOf('macintosh') > -1 || ua.indexOf('mac os x') > -1) {
33    S.isMac = true;
34} else if (ua.indexOf('linux') > -1) {
35    S.isLinux = true;
36}
37
38// browser detection -- deprecated. the goal is to use object detection
39// instead of the user agent string
40S.isIE = ua.indexOf('msie') > -1;
41S.isIE6 = ua.indexOf('msie 6') > -1;
42S.isIE7 = ua.indexOf('msie 7') > -1;
43S.isGecko = ua.indexOf('gecko') > -1 && ua.indexOf('safari') == -1;
44S.isWebKit = ua.indexOf('applewebkit/') > -1;
45
46var inlineId = /#(.+)$/,
47    galleryName = /^(light|shadow)box\[(.*?)\]/i,
48    inlineParam = /\s*([a-z_]*?)\s*=\s*(.+)\s*/,
49    fileExtension = /[0-9a-z]+$/i,
50    scriptPath = /(.+\/)shadowbox\.js/i;
51
52/**
53 * True if Shadowbox is currently open, false otherwise.
54 *
55 * @type    {Boolean}
56 * @private
57 */
58var open = false,
59
60/**
61 * True if Shadowbox has been initialized, false otherwise.
62 *
63 * @type    {Boolean}
64 * @private
65 */
66initialized = false,
67
68/**
69 * The previous set of options that were used before Shadowbox.applyOptions was
70 * called.
71 *
72 * @type    {Object}
73 * @private
74 */
75lastOptions = {},
76
77/**
78 * The delay in milliseconds that the current gallery uses.
79 *
80 * @type    {Number}
81 * @private
82 */
83slideDelay = 0,
84
85/**
86 * The time at which the current slideshow frame appeared.
87 *
88 * @type    {Number}
89 * @private
90 */
91slideStart,
92
93/**
94 * The timeout id for the slideshow transition function.
95 *
96 * @type    {Number}
97 * @private
98 */
99slideTimer;
100
101/**
102 * The index of the current object in the gallery array.
103 *
104 * @type    {Number}
105 * @public
106 */
107S.current = -1;
108
109/**
110 * The current dimensions of Shadowbox.
111 *
112 * @type    {Object}
113 * @public
114 */
115S.dimensions = null;
116
117/**
118 * Easing function used for animations. Based on a cubic polynomial.
119 *
120 * @param   {Number}    state   The state of the animation (% complete)
121 * @return  {Number}            The adjusted easing value
122 * @public
123 */
124S.ease = function(state) {
125    return 1 + Math.pow(state - 1, 3);
126}
127
128/**
129 * An object containing names of plugins and links to their respective download pages.
130 *
131 * @type    {Object}
132 * @public
133 */
134S.errorInfo = {
135    fla: {
136        name: "Flash",
137        url:  "http://www.adobe.com/products/flashplayer/"
138    },
139    qt: {
140        name: "QuickTime",
141        url:  "http://www.apple.com/quicktime/download/"
142    },
143    wmp: {
144        name: "Windows Media Player",
145        url:  "http://www.microsoft.com/windows/windowsmedia/"
146    },
147    f4m: {
148        name: "Flip4Mac",
149        url:  "http://www.flip4mac.com/wmv_download.htm"
150    }
151};
152
153/**
154 * The content objects in the current set.
155 *
156 * @type    {Array}
157 * @public
158 */
159S.gallery = [];
160
161/**
162 * A function that will be called as soon as the DOM is ready.
163 *
164 * @type    {Function}
165 * @public
166 */
167S.onReady = noop;
168
169/**
170 * The URL path to the Shadowbox script.
171 *
172 * @type    {String}
173 * @public
174 */
175S.path = null;
176
177/**
178 * The current player object.
179 *
180 * @type    {Object}
181 * @public
182 */
183S.player = null;
184
185/**
186 * The id to use for the Shadowbox player element.
187 *
188 * @type    {String}
189 * @public
190 */
191S.playerId = "sb-player";
192
193/**
194 * Various options that control Shadowbox' behavior.
195 *
196 * @type    {Object}
197 * @public
198 */
199S.options = {
200
201    /**
202     * True to enable animations.
203     *
204     * @type    {Boolean}
205     */
206    animate: true,
207
208    /**
209     * True to enable opacity animations.
210     *
211     * @type    {Boolean}
212     */
213    animateFade: true,
214
215    /**
216     * True to automatically play movies when the load.
217     *
218     * @type    {Boolean}
219     */
220    autoplayMovies: true,
221
222    /**
223     * True to enable the user to skip to the first item in a gallery from the last using
224     * next.
225     *
226     * @type    {Boolean}
227     */
228    continuous: false,
229
230    /**
231     * True to enable keyboard navigation.
232     *
233     * @type    {Boolean}
234     */
235    enableKeys: true,
236
237    /**
238     * Parameters to pass to flash <object>'s.
239     *
240     * @type    {Object}
241     */
242    flashParams: {
243        bgcolor: "#000000",
244        allowfullscreen: true
245    },
246
247    /**
248     * Variables to pass to flash <object>'s.
249     *
250     * @type    {Object}
251     */
252    flashVars: {},
253
254    /**
255     * The minimum required Flash version.
256     *
257     * Note: The default is 9.0.115. This is the minimum version suggested by
258     * the JW FLV player.
259     *
260     * @type    {String}
261     */
262    flashVersion: "9.0.115",
263
264    /**
265     * Determines how oversized content is handled. If set to "resize" the
266     * content will be resized while preserving aspect ratio. If "drag" will display
267     * the image at its original resolution but it will be draggable. If "none" will
268     * display the content at its original resolution but it may be cropped.
269     *
270     * @type    {String}
271     */
272    handleOversize: "resize",
273
274    /**
275     * Determines how unsupported content is handled. If set to "remove" will
276     * remove the content from the gallery. If "link" will display a helpful
277     * link to a page where the necessary browser plugin can be installed.
278     *
279     * @type    {String}
280     */
281    handleUnsupported: "link",
282
283    /**
284     * A hook function to be fired when changing from one gallery item to the
285     * next. Is passed the item that is about to be displayed as its only argument.
286     *
287     * @type    {Function}
288     */
289    onChange: noop,
290
291    /**
292     * A hook function to be fired when closing. Is passed the most recent item
293     * as its only argument.
294     *
295     * @type    {Function}
296     */
297    onClose: noop,
298
299    /**
300     * A hook funciton to be fires when content is finished loading. Is passed the
301     * current gallery item as its only argument.
302     *
303     * @type    {Function}
304     */
305    onFinish: noop,
306
307    /**
308     * A hook function to be fired when opening. Is passed the current gallery item
309     * as its only argument.
310     *
311     * @type    {Function}
312     */
313    onOpen: noop,
314
315    /**
316     * True to enable movie controls on movie players.
317     *
318     * @type    {Boolean}
319     */
320    showMovieControls: true,
321
322    /**
323     * True to skip calling setup during init.
324     *
325     * @type    {Boolean}
326     */
327    skipSetup: false,
328
329    /**
330     * The delay (in seconds) to use when displaying a gallery in slideshow mode. Setting
331     * this option to any value other than 0 will trigger slideshow mode.
332     *
333     * @type    {Number}
334     */
335    slideshowDelay: 0,
336
337    /**
338     * The ammount of padding (in pixels) to maintain around the edge of the viewport at all
339     * times.
340     *
341     * @type    {Number}
342     */
343    viewportPadding: 20
344
345};
346
347/**
348 * Gets the object that is currently being displayed.
349 *
350 * @return  {Object}
351 * @public
352 */
353S.getCurrent = function() {
354    return S.current > -1 ? S.gallery[S.current] : null;
355}
356
357/**
358 * Returns true if there is another object to display after the current.
359 *
360 * @return  {Boolean}
361 * @public
362 */
363S.hasNext = function() {
364    return S.gallery.length > 1 && (S.current != S.gallery.length - 1 || S.options.continuous);
365}
366
367/**
368 * Returns true if Shadowbox is currently open.
369 *
370 * @return  {Boolean}
371 * @public
372 */
373S.isOpen = function() {
374    return open;
375}
376
377/**
378 * Returns true if Shadowbox is currently paused.
379 *
380 * @return  {Boolean}
381 * @public
382 */
383S.isPaused = function() {
384    return slideTimer == "pause";
385}
386
387/**
388 * Applies the given set of options to Shadowbox' options. May be undone with revertOptions().
389 *
390 * @param   {Object}    options
391 * @public
392 */
393S.applyOptions = function(options) {
394    lastOptions = apply({}, S.options);
395    apply(S.options, options);
396}
397
398/**
399 * Reverts to whatever the options were before applyOptions() was called.
400 *
401 * @public
402 */
403S.revertOptions = function() {
404    apply(S.options, lastOptions);
405}
406
407/**
408 * Initializes the Shadowbox environment. If options are given here, they
409 * will override the defaults. A callback may be provided that will be called
410 * when the document is ready. This function can be used for setting up links
411 * using Shadowbox.setup.
412 *
413 * @param   {Object}    options
414 * @param   {Function}  callback
415 * @public
416 */
417S.init = function(options, callback) {
418    if (initialized)
419        return;
420
421    initialized = true;
422
423    if (S.skin.options)
424        apply(S.options, S.skin.options);
425
426    if (options)
427        apply(S.options, options);
428
429    if (!S.path) {
430        // determine script path automatically
431        var path, scripts = document.getElementsByTagName("script");
432        for (var i = 0, len = scripts.length; i < len; ++i) {
433            path = scriptPath.exec(scripts[i].src);
434            if (path) {
435                S.path = path[1];
436                break;
437            }
438        }
439    }
440
441    if (callback)
442        S.onReady = callback;
443
444    bindLoad();
445}
446
447/**
448 * Opens the given object in Shadowbox. This object may be any of the following:
449 *
450 * - A URL specifying the location of some content to display
451 * - An HTML link object (A or AREA tag) that links to some content
452 * - A custom object similar to one produced by Shadowbox.makeObject
453 * - An array of any of the above
454 *
455 * Note: When a single link object is given, Shadowbox will automatically search
456 * for other cached link objects that have been set up in the same gallery and
457 * display them all together.
458 *
459 * @param   {mixed}     obj
460 * @public
461 */
462S.open = function(obj) {
463    if (open)
464        return;
465
466    var gc = S.makeGallery(obj);
467    S.gallery = gc[0];
468    S.current = gc[1];
469
470    obj = S.getCurrent();
471
472    if (obj == null)
473        return;
474
475    S.applyOptions(obj.options || {});
476
477    filterGallery();
478
479    // anything left to display?
480    if (S.gallery.length) {
481        obj = S.getCurrent();
482
483        if (S.options.onOpen(obj) === false)
484            return;
485
486        open = true;
487
488        S.skin.onOpen(obj, load);
489    }
490}
491
492/**
493 * Closes Shadowbox.
494 *
495 * @public
496 */
497S.close = function() {
498    if (!open)
499        return;
500
501    open = false;
502
503    if (S.player) {
504        S.player.remove();
505        S.player = null;
506    }
507
508    if (typeof slideTimer == "number") {
509        clearTimeout(slideTimer);
510        slideTimer = null;
511    }
512    slideDelay = 0;
513
514    listenKeys(false);
515
516    S.options.onClose(S.getCurrent());
517
518    S.skin.onClose();
519
520    S.revertOptions();
521}
522
523/**
524 * Starts a slideshow when a gallery is being displayed. Is called automatically
525 * when the slideshowDelay option is set to anything other than 0.
526 *
527 * @public
528 */
529S.play = function() {
530    if (!S.hasNext())
531        return;
532
533    if (!slideDelay)
534        slideDelay = S.options.slideshowDelay * 1000;
535
536    if (slideDelay) {
537        slideStart = now();
538        slideTimer = setTimeout(function(){
539            slideDelay = slideStart = 0; // reset slideshow
540            S.next();
541        }, slideDelay);
542
543        if(S.skin.onPlay)
544            S.skin.onPlay();
545    }
546}
547
548/**
549 * Pauses a slideshow on the current object.
550 *
551 * @public
552 */
553S.pause = function() {
554    if (typeof slideTimer != "number")
555        return;
556
557    slideDelay = Math.max(0, slideDelay - (now() - slideStart));
558
559    // if there's any time left on current slide, pause the timer
560    if (slideDelay) {
561        clearTimeout(slideTimer);
562        slideTimer = "pause";
563
564        if(S.skin.onPause)
565            S.skin.onPause();
566    }
567}
568
569/**
570 * Changes Shadowbox to display the item in the gallery specified by index.
571 *
572 * @param   {Number}    index
573 * @public
574 */
575S.change = function(index) {
576    if (!(index in S.gallery)) {
577        if (S.options.continuous) {
578            index = (index < 0 ? S.gallery.length + index : 0); // loop
579            if (!(index in S.gallery))
580                return;
581        } else {
582            return;
583        }
584    }
585
586    S.current = index;
587
588    if (typeof slideTimer == "number") {
589        clearTimeout(slideTimer);
590        slideTimer = null;
591        slideDelay = slideStart = 0;
592    }
593
594    S.options.onChange(S.getCurrent());
595
596    load(true);
597}
598
599/**
600 * Advances to the next item in the gallery.
601 *
602 * @public
603 */
604S.next = function() {
605    S.change(S.current + 1);
606}
607
608/**
609 * Rewinds to the previous gallery item.
610 *
611 * @public
612 */
613S.previous = function() {
614    S.change(S.current - 1);
615}
616
617/**
618 * Calculates the dimensions for Shadowbox.
619 *
620 * @param   {Number}    height          The height of the object
621 * @param   {Number}    width           The width of the object
622 * @param   {Number}    maxHeight       The maximum available height
623 * @param   {Number}    maxWidth        The maximum available width
624 * @param   {Number}    topBottom       The extra top/bottom required for borders/toolbars
625 * @param   {Number}    leftRight       The extra left/right required for borders/toolbars
626 * @param   {Number}    padding         The amount of padding (in pixels) to maintain around
627 *                                      the edge of the viewport
628 * @param   {Boolean}   preserveAspect  True to preserve the original aspect ratio when the
629 *                                      given dimensions are too large
630 * @return  {Object}                    The new dimensions object
631 * @public
632 */
633S.setDimensions = function(height, width, maxHeight, maxWidth, topBottom, leftRight, padding, preserveAspect) {
634    var originalHeight = height,
635        originalWidth = width;
636
637    // constrain height/width to max
638    var extraHeight = 2 * padding + topBottom;
639    if (height + extraHeight > maxHeight)
640        height = maxHeight - extraHeight;
641    var extraWidth = 2 * padding + leftRight;
642    if (width + extraWidth > maxWidth)
643        width = maxWidth - extraWidth;
644
645    // determine if object is oversized
646    var changeHeight = (originalHeight - height) / originalHeight,
647        changeWidth = (originalWidth - width) / originalWidth,
648        oversized = (changeHeight > 0 || changeWidth > 0);
649
650    // adjust height/width if too large
651    if (preserveAspect && oversized) {
652        // preserve aspect ratio according to greatest change
653        if (changeHeight > changeWidth) {
654            width = Math.round((originalWidth / originalHeight) * height);
655        } else if (changeWidth > changeHeight) {
656            height = Math.round((originalHeight / originalWidth) * width);
657        }
658    }
659
660    S.dimensions = {
661        height:         height + topBottom,
662        width:          width + leftRight,
663        innerHeight:    height,
664        innerWidth:     width,
665        top:            Math.floor((maxHeight - (height + extraHeight)) / 2 + padding),
666        left:           Math.floor((maxWidth - (width + extraWidth)) / 2 + padding),
667        oversized:      oversized
668    };
669
670    return S.dimensions;
671}
672
673/**
674 * Returns an array with two elements. The first is an array of objects that
675 * constitutes the gallery, and the second is the index of the given object in
676 * that array.
677 *
678 * @param   {mixed}     obj
679 * @return  {Array}     An array containing the gallery and current index
680 * @public
681 */
682S.makeGallery = function(obj) {
683    var gallery = [], current = -1;
684
685    if (typeof obj == "string")
686        obj = [obj];
687
688    if (typeof obj.length == "number") {
689        each(obj, function(i, o) {
690            if (o.content) {
691                gallery[i] = o;
692            } else {
693                gallery[i] = {content: o};
694            }
695        });
696        current = 0;
697    } else {
698        if (obj.tagName) {
699            // check the cache for this object before building one on the fly
700            var cacheObj = S.getCache(obj);
701            obj = cacheObj ? cacheObj : S.makeObject(obj);
702        }
703
704        if (obj.gallery) {
705            // gallery object, build gallery from cached gallery objects
706            gallery = [];
707
708            var o;
709            for (var key in S.cache) {
710                o = S.cache[key];
711                if (o.gallery && o.gallery == obj.gallery) {
712                    if (current == -1 && o.content == obj.content)
713                        current = gallery.length;
714                    gallery.push(o);
715                }
716            }
717
718            if (current == -1) {
719                gallery.unshift(obj);
720                current = 0;
721            }
722        } else {
723            // single object, no gallery
724            gallery = [obj];
725            current = 0;
726        }
727    }
728
729    // use apply to break references to each gallery object here because
730    // the code may modify certain properties of these objects from here
731    // on out and we want to preserve the original in case the same object
732    // is used again in a future call
733    each(gallery, function(i, o) {
734        gallery[i] = apply({}, o);
735    });
736
737    return [gallery, current];
738}
739
740/**
741 * Extracts parameters from a link element and returns an object c
741ontaining
742 * (most of) the following keys:
743 *
744 * - content:  The URL of the linked to content
745 * - player:   The abbreviated name of the player to use for the object (can automatically
746 *             be determined in most cases)
747 * - title:    The title to use for the object (optional)
748 * - gallery:  The name of the gallery the object belongs to (optional)
749 * - height:   The height of the object (in pixels, only required for movies and Flash)
750 * - width:    The width of the object (in pixels, only required for movies and Flash)
751 * - options:  A set of options to use for this object (optional)
752 * - link:     A reference to the original link element
753 *
754 * A custom set of options may be passed in here that will be applied when
755 * this object is displayed. However, any options that are specified in
756 * the link's HTML markup will trump options given here.
757 *
758 * @param   {HTMLElement}   link
759 * @param   {Object}        options
760 * @return  {Object}        An object representing the link
761 * @public
762 */
763S.makeObject = function(link, options) {
764    var obj = {
765        // accessing the href attribute directly here (instead of using
766        // getAttribute) should give a full URL instead of a relative one
767        content:    link.href,
768        title:      link.getAttribute("title") || "",
769        link:       link
770    };
771
772    // remove link-level options from top-level options
773    if (options) {
774        options = apply({}, options);
775        each(["player", "title", "height", "width", "gallery"], function(i, o) {
776            if (typeof options[o] != "undefined") {
777                obj[o] = options[o];
778                delete options[o];
779            }
780        });
781        obj.options = options;
782    } else {
783        obj.options = {};
784    }
785
786    if (!obj.player)
787        obj.player = S.getPlayer(obj.content);
788
789    // HTML options always trump JavaScript options, so do these last
790    var rel = link.getAttribute("rel");
791    if (rel) {
792        // extract gallery name from shadowbox[name] format
793        var match = rel.match(galleryName);
794        if (match)
795            obj.gallery = escape(match[2]);
796
797        // extract any other parameters
798        each(rel.split(';'), function(i, p) {
799            match = p.match(inlineParam);
800            if (match)
801                obj[match[1]] = match[2];
802        });
803    }
804
805    return obj;
806}
807
808/**
809 * Attempts to automatically determine the correct player to use for an object based
810 * on its content attribute. Defaults to "iframe" when the content type cannot
811 * automatically be determined.
812 *
813 * @param   {String}    content     The content attribute of the object
814 * @return  {String}                The name of the player to use
815 * @public
816 */
817S.getPlayer = function(content) {
818    if (content.indexOf("#") > -1 && content.indexOf(document.location.href) == 0)
819        return "inline";
820
821    // strip query string for player detection purposes
822    var q = content.indexOf("?");
823    if (q > -1)
824        content = content.substring(0, q);
825
826    // get file extension
827    var ext, m = content.match(fileExtension);
828    if (m)
829        ext = m[0].toLowerCase();
830
831    if (ext) {
832        if (S.img && S.img.ext.indexOf(ext) > -1)
833            return "img";
834        if (S.swf && S.swf.ext.indexOf(ext) > -1)
835            return "swf";
836        if (S.flv && S.flv.ext.indexOf(ext) > -1)
837            return "flv";
838        if (S.qt && S.qt.ext.indexOf(ext) > -1) {
839            if (S.wmp && S.wmp.ext.indexOf(ext) > -1) {
840                return "qtwmp"; // can be played by either QuickTime or Windows Media Player
841            } else {
842                return "qt";
843            }
844        }
845        if (S.wmp && S.wmp.ext.indexOf(ext) > -1)
846            return "wmp";
847    }
848
849    return "iframe";
850}
851
852/**
853 * Filters the current gallery for unsupported objects.
854 *
855 * @private
856 */
857function filterGallery() {
858    var err = S.errorInfo, plugins = S.plugins, obj, remove, needed,
859        m, format, replace, inlineEl, flashVersion;
860
861    for (var i = 0; i < S.gallery.length; ++i) {
862        obj = S.gallery[i]
863
864        remove = false; // remove the object?
865        needed = null; // what plugins are needed?
866
867        switch (obj.player) {
868        case "flv":
869        case "swf":
870            if (!plugins.fla)
871                needed = "fla";
872            break;
873        case "qt":
874            if (!plugins.qt)
875                needed = "qt";
876            break;
877        case "wmp":
878            if (S.isMac) {
879                if (plugins.qt && plugins.f4m) {
880                    obj.player = "qt";
881                } else {
882                    needed = "qtf4m";
883                }
884            } else if (!plugins.wmp) {
885                needed = "wmp";
886            }
887            break;
888        case "qtwmp":
889            if (plugins.qt) {
890                obj.player = "qt";
891            } else if (plugins.wmp) {
892                obj.player = "wmp";
893            } else {
894                needed = "qtwmp";
895            }
896            break;
897        }
898
899        // handle unsupported elements
900        if (needed) {
901            if (S.options.handleUnsupported == "link") {
902                // generate a link to the appropriate plugin download page(s)
903                switch (needed) {
904                case "qtf4m":
905                    format = "shared";
906                    replace = [err.qt.url, err.qt.name, err.f4m.url, err.f4m.name];
907                    break;
908                case "qtwmp":
909                    format = "either";
910                    replace = [err.qt.url, err.qt.name, err.wmp.url, err.wmp.name];
911                    break;
912                default:
913                    format = "single";
914                    replace = [err[needed].url, err[needed].name];
915                }
916
917                obj.player = "html";
918                obj.content = '<div class="sb-message">' + sprintf(S.lang.errors[format], replace) + '</div>';
919            } else {
920                remove = true;
921            }
922        } else if (obj.player == "inline") {
923            // inline element, retrieve innerHTML
924            m = inlineId.exec(obj.content);
925            if (m) {
926                inlineEl = get(m[1]);
927                if (inlineEl) {
928                    obj.content = inlineEl.innerHTML;
929                } else {
930                    // cannot find element with id
931                    remove = true;
932                }
933            } else {
934                // cannot determine element id from content string
935                remove = true;
936            }
937        } else if (obj.player == "swf" || obj.player == "flv") {
938            flashVersion = (obj.options && obj.options.flashVersion) || S.options.flashVersion;
939
940            if (S.flash && !S.flash.hasFlashPlayerVersion(flashVersion)) {
941                // express install will be triggered because the client does not meet the
942                // minimum required version of Flash. set height and width to those of expressInstall.swf
943                obj.width = 310;
944                // minimum height is 127, but +20 pixels on top and bottom looks better
945                obj.height = 177;
946            }
947        }
948
949        if (remove) {
950            S.gallery.splice(i, 1);
951
952            if (i < S.current) {
953                --S.current; // maintain integrity of S.current
954            } else if (i == S.current) {
955                S.current = i > 0 ? i - 1 : i; // look for supported neighbor
956            }
957
958            // decrement index for next loop
959            --i;
960        }
961    }
962}
963
964/**
965 * Sets up a listener on the document for keydown events.
966 *
967 * @param   {Boolean}   on      True to enable the listener, false to disable
968 * @private
969 */
970function listenKeys(on) {
971    if (!S.options.enableKeys)
972        return;
973
974    (on ? addEvent : removeEvent)(document, "keydown", handleKey);
975}
976
977/**
978 * A listener function that is fired when a key is pressed.
979 *
980 * @param   {Event}     e   The keydown event
981 * @private
982 */
983function handleKey(e) {
984    // don't handle events with modifier keys
985    if (e.metaKey || e.shiftKey || e.altKey || e.ctrlKey)
986        return;
987
988    var code = keyCode(e), handler;
989
990    switch (code) {
991    case 81: // q
992    case 88: // x
993    case 27: // esc
994        handler = S.close;
995        break;
996    case 37: // left
997        handler = S.previous;
998        break;
999    case 39: // right
1000        handler = S.next;
1001        break;
1002    case 32: // space
1003        handler = typeof slideTimer == "number" ? S.pause : S.play;
1004        break;
1005    }
1006
1007    if (handler) {
1008        preventDefault(e);
1009        handler();
1010    }
1011}
1012
1013/**
1014 * Loads the current object.
1015 *
1016 * @param   {Boolean}   True if changing from a previous object
1017 * @private
1018 */
1019function load(changing) {
1020    listenKeys(false);
1021
1022    var obj = S.getCurrent();
1023
1024    // determine player, inline is really just html
1025    var player = (obj.player == "inline" ? "html" : obj.player);
1026
1027    if (typeof S[player] != "function")
1028        throw "unknown player " + player;
1029
1030    if (changing) {
1031        S.player.remove();
1032        S.revertOptions();
1033        S.applyOptions(obj.options || {});
1034    }
1035
1036    S.player = new S[player](obj, S.playerId);
1037
1038    // preload neighboring gallery images
1039    if (S.gallery.length > 1) {
1040        var next = S.gallery[S.current + 1] || S.gallery[0];
1041        if (next.player == "img") {
1042            var a = new Image();
1043            a.src = next.content;
1044        }
1045        var prev = S.gallery[S.current - 1] || S.gallery[S.gallery.length - 1];
1046        if (prev.player == "img") {
1047            var b = new Image();
1048            b.src = prev.content;
1049        }
1050    }
1051
1052    S.skin.onLoad(changing, waitReady);
1053}
1054
1055/**
1056 * Waits until the current object is ready to be displayed.
1057 *
1058 * @private
1059 */
1060function waitReady() {
1061    if (!open)
1062        return;
1063
1064    if (typeof S.player.ready != "undefined") {
1065        // wait for content to be ready before loading
1066        var timer = setInterval(function() {
1067            if (open) {
1068                if (S.player.ready) {
1069                    clearInterval(timer);
1070                    timer = null;
1071                    S.skin.onReady(show);
1072                }
1073            } else {
1074                clearInterval(timer);
1075                timer = null;
1076            }
1077        }, 10);
1078    } else {
1079        S.skin.onReady(show);
1080    }
1081}
1082
1083/**
1084 * Displays the current object.
1085 *
1086 * @private
1087 */
1088function show() {
1089    if (!open)
1090        return;
1091
1092    S.player.append(S.skin.body, S.dimensions);
1093
1094    S.skin.onShow(finish);
1095}
1096
1097/**
1098 * Finishes up any remaining tasks after the object is displayed.
1099 *
1100 * @private
1101 */
1102function finish() {
1103    if (!open)
1104        return;
1105
1106    if (S.player.onLoad)
1107        S.player.onLoad();
1108
1109    S.options.onFinish(S.getCurrent());
1110
1111    if (!S.isPaused())
1112        S.play(); // kick off next slide
1113
1114    listenKeys(true);
1115}
1116if (!Array.prototype.indexOf) {
1117    Array.prototype.indexOf = function(obj, from) {
1118        var len = this.length >>> 0;
1119
1120        from = from || 0;
1121        if (from < 0)
1122            from += len;
1123
1124        for (; from < len; ++from) {
1125            if (from in this && this[from] === obj)
1126                return from;
1127        }
1128
1129        return -1;
1130    }
1131}
1132
1133/**
1134 * Gets the current time in milliseconds.
1135 *
1136 * @return  {Number}
1137 * @private
1138 */
1139function now() {
1140    return (new Date).getTime();
1141}
1142
1143/**
1144 * Applies all properties of extension to original.
1145 *
1146 * @param   {Object}    original
1147 * @param   {Object}    extension
1148 * @return  {Object}    The original object
1149 * @private
1150 */
1151function apply(original, extension) {
1152    for (var property in extension)
1153        original[property] = extension[property];
1154    return original;
1155}
1156
1157/**
1158 * Calls the given callback function for each element in obj. Note: obj must be an array-like
1159 * object.
1160 *
1161 * @param   {Array|mixed}   obj
1162 * @param   {Function}      callback
1163 * @private
1164 */
1165function each(obj, callback) {
1166    var i = 0, len = obj.length;
1167    for (var value = obj[0]; i < len && callback.call(value, i, value) !== false; value = obj[++i]) {}
1168}
1169
1170/**
1171 * Formats a string with the elements in the replacement array. The string should contain
1172 * tokens in the format {n} where n corresponds to the index of property name of the replacement
1173 * in the replace object.
1174 *
1175 * Example:
1176 *
1177 * format('Hello {0}', ['World']); // "Hello World"
1178 * format('Hello {world}', {world: "World"}); // "Hello World"
1179 *
1180 * @param   {String}        str         The format spec string
1181 * @param   {Array|Object}  replace     The array/object of replacement values
1182 * @return  {String}                    The formatted string
1183 * @private
1184 */
1185function sprintf(str, replace) {
1186    return str.replace(/\{(\w+?)\}/g, function(match, i) {
1187        return replace[i];
1188    });
1189}
1190
1191/**
1192 * A no-op function.
1193 *
1194 * @private
1195 */
1196function noop() {}
1197
1198/**
1199 * Gets the element with the given id.
1200 *
1201 * @param   {String}        id
1202 * @return  {HTMLElement}
1203 * @private
1204 */
1205function get(id) {
1206    return document.getElementById(id);
1207}
1208
1209/**
1210 * Removes an element from the DOM.
1211 *
1212 * @param   {HTMLElement}   el          The element to remove
1213 * @private
1214 */
1215function remove(el) {
1216    el.parentNode.removeChild(el);
1217}
1218
1219/**
1220 * True if this browser supports opacity.
1221 *
1222 * @type    {Boolean}
1223 * @private
1224 */
1225var supportsOpacity = true,
1226
1227/**
1228 * True if the browser supports fixed positioning.
1229 *
1230 * @type    {Boolean}
1231 * @private
1232 */
1233supportsFixed = true;
1234
1235/**
1236 * Checks the level of support the browser provides. Should be called when
1237 * the DOM is ready to be manipulated.
1238 *
1239 * @private
1240 */
1241function checkSupport() {
1242    var body = document.body,
1243        div = document.createElement("div");
1244
1245    // detect opacity support
1246    supportsOpacity = typeof div.style.opacity === "string";
1247
1248    // detect support for fixed positioning
1249    div.style.position = "fixed";
1250    div.style.margin = 0;
1251    div.style.top = "20px";
1252    body.appendChild(div, body.firstChild);
1253    supportsFixed = div.offsetTop == 20;
1254    body.removeChild(div);
1255}
1256
1257/**
1258 * Gets the computed value of the style on the given element.
1259 *
1260 * Note: This function is not safe for retrieving float values or non-pixel values
1261 * in IE.
1262 *
1263 * @param   {HTMLElement}   el          The element
1264 * @param   {String}        style       The camel-cased name of the style
1265 * @return  {mixed}                     The computed value of the given style
1266 * @public
1267 */
1268S.getStyle = (function() {
1269    var opacity = /opacity=([^)]*)/,
1270        getComputedStyle = document.defaultView && document.defaultView.getComputedStyle;
1271
1272    return function(el, style) {
1273        var ret;
1274
1275        if (!supportsOpacity && style == "opacity" && el.currentStyle) {
1276            ret = opacity.test(el.currentStyle.filter || "") ? (parseFloat(RegExp.$1) / 100) + "" : "";
1277            return ret === "" ? "1" : ret;
1278        }
1279
1280        if (getComputedStyle) {
1281            var computedStyle = getComputedStyle(el, null);
1282
1283            if (computedStyle)
1284                ret = computedStyle[style];
1285
1286            if (style == "opacity" && ret == "")
1287                ret = "1";
1288        } else {
1289            ret = el.currentStyle[style];
1290        }
1291
1292        return ret;
1293    }
1294})();
1295
1296/**
1297 * Appends an HTML fragment to the given element.
1298 *
1299 * @param   {HTMLElement}   el
1300 * @param   {String}        html    The HTML fragment to use
1301 * @public
1302 */
1303S.appendHTML = function(el, html) {
1304    if (el.insertAdjacentHTML) {
1305        el.insertAdjacentHTML("BeforeEnd", html);
1306    } else if (el.lastChild) {
1307        var range = el.ownerDocument.createRange();
1308        range.setStartAfter(el.lastChild);
1309        var frag = range.createContextualFragment(html);
1310        el.appendChild(frag);
1311    } else {
1312        el.innerHTML = html;
1313    }
1314}
1315
1316/**
1317 * Gets the window size. The dimension may be either "Height" or "Width".
1318 *
1319 * @param   {String}    dimension
1320 * @return  {Number}
1321 * @public
1322 */
1323S.getWindowSize = function(dimension) {
1324    if (document.compatMode === "CSS1Compat")
1325        return document.documentElement["client" + dimension];
1326
1327    return document.body["client" + dimension];
1328}
1329
1330/**
1331 * Sets an element's opacity.
1332 *
1333 * @param   {HTMLElement}   el
1334 * @param   {Number}        opacity
1335 * @public
1336 */
1337S.setOpacity = function(el, opacity) {
1338    var style = el.style;
1339    if (supportsOpacity) {
1340        style.opacity = (opacity == 1 ? "" : opacity);
1341    } else {
1342        style.zoom = 1; // trigger hasLayout
1343        if (opacity == 1) {
1344            if (typeof style.filter == "string" && (/alpha/i).test(style.filter))
1345                style.filter = style.filter.replace(/\s*[\w\.]*alpha\([^\)]*\);?/gi, "");
1346        } else {
1347            style.filter = (style.filter || "").replace(/\s*[\w\.]*alpha\([^\)]*\)/gi, "") +
1348                " alpha(opacity=" + (opacity * 100) + ")";
1349        }
1350    }
1351}
1352
1353/**
1354 * Clears the opacity setting on the given element. Needed for some cases in IE.
1355 *
1356 * @param   {HTMLElement}   el
1357 * @public
1358 */
1359S.clearOpacity = function(el) {
1360    S.setOpacity(el, 1);
1361}
1362/**
1363 * The base adapter for Shadowbox.
1364 */
1365
1366/**
1367 * Gets the target of the given event. The event object passed will be
1368 * the same object that is passed to listeners registered with
1369 * addEvent().
1370 *
1371 * @param   {Event}     e       The event object
1372 * @return  {HTMLElement}       The event's target element
1373 * @private
1374 */
1375function getTarget(e) {
1376    var target = e.target ? e.target : e.srcElement;
1377    return target.nodeType == 3 ? target.parentNode : target;
1378}
1379
1380/**
1381 * Gets the page X/Y coordinates of the mouse event in an [x, y] array.
1382 * The page coordinates should be relative to the document, and not the
1383 * viewport. The event object provided here will be the same object that
1384 * is passed to listeners registered with addEvent().
1385 *
1386 * @param   {Event}     e       The event object
1387 * @return  {Array}             The page X/Y coordinates
1388 * @private
1389 */
1390function getPageXY(e) {
1391    var x = e.pageX || (e.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft)),
1392        y = e.pageY || (e.clientY + (document.documentElement.scrollTop || document.body.scrollTop));
1393    return [x, y];
1394}
1395
1396/**
1397 * Prevents the event's default behavior. The event object passed will
1398 * be the same object that is passed to listeners registered with
1399 * addEvent().
1400 *
1401 * @param   {Event}     e       The event object
1402 * @private
1403 */
1404function preventDefault(e) {
1405    e.preventDefault();
1406}
1407
1408/**
1409 * Gets the key code of the given event object (keydown). The event
1410 * object here will be the same object that is passed to listeners
1411 * registered with addEvent().
1412 *
1413 * @param   {Event}     e       The event object
1414 * @return  {Number}            The key code of the event
1415 * @private
1416 */
1417function keyCode(e) {
1418    return e.which ? e.which : e.keyCode;
1419}
1420
1421// Event handling functions below modified from original by Dean Edwards
1422// http://dean.edwards.name/my/events.js
1423
1424/**
1425 * Adds an event handler to the given element. The handler should be called
1426 * in the scope of the element with the event object as its only argument.
1427 *
1428 * @param   {HTMLElement}   el          The element to listen to
1429 * @param   {String}        type        The type of the event to add
1430 * @param   {Function}      handler     The event handler function
1431 * @private
1432 */
1433function addEvent(el, type, handler) {
1434    if (el.addEventListener) {
1435        el.addEventListener(type, handler, false);
1436    } else {
1437        if (el.nodeType === 3 || el.nodeType === 8)
1438            return;
1439
1440        if (el.setInterval && (el !== window && !el.frameElement))
1441            el = window;
1442
1443        if (!handler.__guid)
1444            handler.__guid = addEvent.guid++;
1445
1446        if (!el.events)
1447            el.events = {};
1448
1449        var handlers = el.events[type];
1450        if (!handlers) {
1451            handlers = el.events[type] = {};
1452
1453            if (el["on" + type])
1454                handlers[0] = el["on" + type];
1455        }
1456
1457        handlers[handler.__guid] = handler;
1458
1459        el["on" + type] = addEvent.handleEvent;
1460    }
1461}
1462
1463addEvent.guid = 1;
1464
1465addEvent.handleEvent = function(event) {
1466    var result = true;
1467    event = event || addEvent.fixEvent(((this.ownerDocument || this.document || this).parentWindow || window).event);
1468    var handlers = this.events[event.type];
1469
1470    for (var i in handlers) {
1471        this.__handleEvent = handlers[i];
1472        if (this.__handleEvent(event) === false)
1473            result = false;
1474    }
1475
1476    return result;
1477}
1478
1479addEvent.preventDefault = function() {
1480    this.returnValue = false;
1481}
1482
1483addEvent.stopPropagation = function() {
1484    this.cancelBubble = true;
1485}
1486
1487addEvent.fixEvent = function(e) {
1488    e.preventDefault = addEvent.preventDefault;
1489    e.stopPropagation = addEvent.stopPropagation;
1490    return e;
1491}
1492
1493/**
1494 * Removes an event handler from the given element.
1495 *
1496 * @param   {HTMLElement}   el          The DOM element to stop listening to
1497 * @param   {String}        type        The type of the event to remove
1498 * @param   {Function}      handler     The event handler function
1499 * @private
1500 */
1501function removeEvent(el, type, handler) {
1502    if (el.removeEventListener) {
1503        el.removeEventListener(type, handler, false);
1504    } else {
1505        if (el.events && el.events[type])
1506            delete el.events[type][handler.__guid];
1507    }
1508}
1509// The code in this file is adapted for Shadowbox from the jQuery JavaScript library
1510
1511/**
1512 * True if Shadowbox has been loaded into the DOM, false otherwise.
1513 *
1514 * @type    {Boolean}
1515 * @private
1516 */
1517var loaded = false,
1518
1519/**
1520 * The callback function for the DOMContentLoaded browser event.
1521 *
1522 * @type    {Function}
1523 * @private
1524 */
1525DOMContentLoaded;
1526
1527if (document.addEventListener) {
1528    DOMContentLoaded = function() {
1529        document.removeEventListener("DOMContentLoaded", DOMContentLoaded, false);
1530        S.load();
1531    }
1532} else if (document.attachEvent) {
1533    DOMContentLoaded = function() {
1534        if (document.readyState === "complete") {
1535            document.detachEvent("onreadystatechange", DOMContentLoaded);
1536            S.load();
1537        }
1538    }
1539}
1540
1541/**
1542 * A DOM ready check for IE.
1543 *
1544 * @private
1545 */
1546function doScrollCheck() {
1547    if (loaded)
1548        return;
1549
1550    try {
1551        document.documentElement.doScroll("left");
1552    } catch (e) {
1553        setTimeout(doScrollCheck, 1);
1554        return;
1555    }
1556
1557    S.load();
1558}
1559
1560/**
1561 * Waits for the DOM to be ready before firing the given callback function.
1562 *
1563 * @param   {Function}  callback
1564 * @private
1565 */
1566function bindLoad() {
1567    if (document.readyState === "complete")
1568        return S.load();
1569
1570    if (document.addEventListener) {
1571        document.addEventListener("DOMContentLoaded", DOMContentLoaded, false);
1572        window.addEventListener("load", S.load, false);
1573    } else if (document.attachEvent) {
1574        document.attachEvent("onreadystatechange", DOMContentLoaded);
1575        window.attachEvent("onload", S.load);
1576
1577        var topLevel = false;
1578        try {
1579            topLevel = window.frameElement === null;
1580        } catch (e) {}
1581
1582        if (document.documentElement.doScroll && topLevel)
1583            doScrollCheck();
1584    }
1585}
1586
1587/**
1588 * Loads the Shadowbox code into the DOM. Is called automatically when the document
1589 * is ready.
1590 *
1591 * @public
1592 */
1593S.load = function() {
1594    if (loaded)
1595        return;
1596
1597    if (!document.body)
1598        return setTimeout(S.load, 13);
1599
1600    loaded = true;
1601
1602    checkSupport();
1603
1604    S.onReady();
1605
1606    if (!S.options.skipSetup)
1607        S.setup();
1608
1609    S.skin.init();
1610}
1611/**
1612 * Contains plugin support information. Each property of this object is a
1613 * boolean indicating whether that plugin is supported. Keys are:
1614 *
1615 * - fla: Flash player
1616 * - qt: QuickTime player
1617 * - wmp: Windows Media player
1618 * - f4m: Flip4Mac plugin
1619 *
1620 * @type    {Object}
1621 * @public
1622 */
1623S.plugins = {};
1624
1625if (navigator.plugins && navigator.plugins.length) {
1626    var names = [];
1627    each(navigator.plugins, function(i, p) {
1628        names.push(p.name);
1629    });
1630    names = names.join(',');
1631
1632    var f4m = names.indexOf('Flip4Mac') > -1;
1633
1634    S.plugins = {
1635        fla:    names.indexOf('Shockwave Flash') > -1,
1636        qt:     names.indexOf('QuickTime') > -1,
1637        wmp:    !f4m && names.indexOf('Windows Media') > -1, // if it's Flip4Mac, it's not really WMP
1638        f4m:    f4m
1639    };
1640} else {
1641    var detectPlugin = function(name) {
1642        var axo;
1643        try {
1644            axo = new ActiveXObject(name);
1645        } catch(e) {}
1646        return !!axo;
1647    }
1648
1649    S.plugins = {
1650        fla:    detectPlugin('ShockwaveFlash.ShockwaveFlash'),
1651        qt:     detectPlugin('QuickTime.QuickTime'),
1652        wmp:    detectPlugin('wmplayer.ocx'),
1653        f4m:    false
1654    };
1655}
1656// used to match the rel attribute of links
1657var relAttr = /^(light|shadow)box/i,
1658
1659/**
1660 * The name of the expando property that Shadowbox uses on HTML elements
1661 * to store the cache index of that element.
1662 *
1663 * @type    {String}
1664 * @private
1665 */
1666expando = "shadowboxCacheKey",
1667
1668/**
1669 * A unique id counter.
1670 *
1671 * @type    {Number}
1672 * @private
1673 */
1674cacheKey = 1;
1675
1676/**
1677 * Contains all link objects that have been cached.
1678 *
1679 * @type    {Object}
1680 * @public
1681 */
1682S.cache = {};
1683
1684/**
1685 * Resolves a link selector. The selector may be omitted to select all anchor elements
1686 * on the page with rel="shadowbox" or, if Shadowbox.find is used, it may be a single CSS
1687 * selector or an array of [selector, [context]].
1688 *
1689 * @param   {mixed}     selector
1690 * @return  {Array}     An array of matching link elements
1691 * @public
1692 */
1693S.select = function(selector) {
1694    var links = [];
1695
1696    if (!selector) {
1697        var rel;
1698        each(document.getElementsByTagName("a"), function(i, el) {
1699            rel = el.getAttribute("rel");
1700            if (rel && relAttr.test(rel))
1701                links.push(el);
1702        });
1703    } else {
1704        var length = selector.length;
1705        if (length) {
1706            if (typeof selector == "string") {
1707                if (S.find)
1708                    links = S.find(selector); // css selector
1709            } else if (length == 2 && typeof selector[0] == "string" && selector[1].nodeType) {
1710                if (S.find)
1711                    links = S.find(selector[0], selector[1]); // css selector + context
1712            } else {
1713                // array of links (or node list)
1714                for (var i = 0; i < length; ++i)
1715                    links[i] = selector[i];
1716            }
1717        } else {
1718            links.push(selector); // single link
1719        }
1720    }
1721
1722    return links;
1723}
1724
1725/**
1726 * Adds all links specified by the given selector to the cache. If no selector
1727 * is provided, will select every anchor element on the page with rel="shadowbox".
1728 *
1729 * Note: Options given here apply only to links selected by the given selector.
1730 * Also, because <area> elements do not support the rel attribute, they must be
1731 * explicitly passed to this method.
1732 *
1733 * @param   {mixed}     selector
1734 * @param   {Object}    options     Some options to use for the given links
1735 * @public
1736 */
1737S.setup = function(selector, options) {
1738    each(S.select(selector), function(i, link) {
1739        S.addCache(link, options);
1740    });
1741}
1742
1743/**
1744 * Removes all links specified by the given selector from the cache.
1745 *
1746 * @param   {mixed}     selector
1747 * @public
1748 */
1749S.teardown = function(selector) {
1750    each(S.select(selector), function(i, link) {
1751        S.removeCache(link);
1752    });
1753}
1754
1755/**
1756 * Adds the given link element to the cache with the given options.
1757 *
1758 * @param   {HTMLElement}   link
1759 * @param   {Object}        options
1760 * @public
1761 */
1762S.addCache = function(link, options) {
1763    var key = link[expando];
1764
1765    if (key == undefined) {
1766        key = cacheKey++;
1767        // assign cache key expando, use integer primitive to avoid memory leak in IE
1768        link[expando] = key;
1769        // add onclick listener
1770        addEvent(link, "click", handleClick);
1771    }
1772
1773    S.cache[key] = S.makeObject(link, options);
1774}
1775
1776/**
1777 * Removes the given link element from the cache.
1778 *
1779 * @param   {HTMLElement}   link
1780 * @public
1781 */
1782S.removeCache = function(link) {
1783    removeEvent(link, "click", handleClick);
1784    delete S.cache[link[expando]];
1785    link[expando] = null;
1786}
1787
1788/**
1789 * Gets the object from cache representative of the given link element (if there is one).
1790 *
1791 * @param   {HTMLElement}   link
1792 * @return  {Object}
1793 * @public
1794 */
1795S.getCache = function(link) {
1796    var key = link[expando];
1797    return (key in S.cache && S.cache[key]);
1798}
1799
1800/**
1801 * Removes all onclick listeners from elements that have previously been setup with
1802 * Shadowbox and clears all objects from cache.
1803 *
1804 * @public
1805 */
1806S.clearCache = function() {
1807    for (var key in S.cache)
1808        S.removeCache(S.cache[key].link);
1809
1810    S.cache = {};
1811}
1812
1813/**
1814 * Handles all clicks on links that have been set up to work with Shadowbox
1815 * and cancels the default event behavior when appropriate.
1816 *
1817 * @param   {Event}     e   The click event
1818 * @private
1819 */
1820function handleClick(e) {
1821    //preventDefault(e); // good for debugging
1822
1823    S.open(this);
1824
1825    if (S.gallery.length)
1826        preventDefault(e);
1827}
1828/*!
1829 * Sizzle CSS Selector Engine - v1.0
1830 *  Copyright 2009, The Dojo Foundation
1831 *  Released under the MIT, BSD, and GPL Licenses.
1832 *  More information: http://sizzlejs.com/
1833 *
1834 * Modified for inclusion in Shadowbox.js
1835 */
1836S.find = (function(){
1837
1838var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
1839	done = 0,
1840	toString = Object.prototype.toString,
1841	hasDuplicate = false,
1842	baseHasDuplicate = true;
1843
1844// Here we check if the JavaScript engine is using some sort of
1845// optimization where it does not always call our comparision
1846// function. If that is the case, discard the hasDuplicate value.
1847//   Thus far that includes Google Chrome.
1848[0, 0].sort(function(){
1849	baseHasDuplicate = false;
1850	return 0;
1851});
1852
1853var Sizzle = function(selector, context, results, seed) {
1854	results = results || [];
1855	var origContext = context = context || document;
1856
1857	if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
1858		return [];
1859	}
1860
1861	if ( !selector || typeof selector !== "string" ) {
1862		return results;
1863	}
1864
1865	var parts = [], m, set, checkSet, extra, prune = true, contextXML = isXML(context),
1866		soFar = selector;
1867
1868	// Reset the position of the chunker regexp (start from head)
1869	while ( (chunker.exec(""), m = chunker.exec(soFar)) !== null ) {
1870		soFar = m[3];
1871
1872		parts.push( m[1] );
1873
1874		if ( m[2] ) {
1875			extra = m[3];
1876			break;
1877		}
1878	}
1879
1880	if ( parts.length > 1 && origPOS.exec( selector ) ) {
1881		if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
1882			set = posProcess( parts[0] + parts[1], context );
1883		} else {
1884			set = Expr.relative[ parts[0] ] ?
1885				[ context ] :
1886				Sizzle( parts.shift(), context );
1887
1888			while ( parts.length ) {
1889				selector = parts.shift();
1890
1891				if ( Expr.relative[ selector ] ) {
1892					selector += parts.shift();
1893				}
1894
1895				set = posProcess( selector, set );
1896			}
1897		}
1898	} else {
1899		// Take a shortcut and set the context if the root selector is an ID
1900		// (but not if it'll be faster if the inner selector is an ID)
1901		if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
1902				Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
1903			var ret = Sizzle.find( parts.shift(), context, contextXML );
1904			context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0];
1905		}
1906
1907		if ( context ) {
1908			var ret = seed ?
1909				{ expr: parts.pop(), set: makeArray(seed) } :
1910				Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
1911			set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set;
1912
1913			if ( parts.length > 0 ) {
1914				checkSet = makeArray(set);
1915			} else {
1916				prune = false;
1917			}
1918
1919			while ( parts.length ) {
1920				var cur = parts.pop(), pop = cur;
1921
1922				if ( !Expr.relative[ cur ] ) {
1923					cur = "";
1924				} else {
1925					pop = parts.pop();
1926				}
1927
1928				if ( pop == null ) {
1929					pop = context;
1930				}
1931
1932				Expr.relative[ cur ]( checkSet, pop, contextXML );
1933			}
1934		} else {
1935			checkSet = parts = [];
1936		}
1937	}
1938
1939	if ( !checkSet ) {
1940		checkSet = set;
1941	}
1942
1943	if ( !checkSet ) {
1944		throw "Syntax error, unrecognized expression: " + (cur || selector);
1945	}
1946
1947	if ( toString.call(checkSet) === "[object Array]" ) {
1948		if ( !prune ) {
1949			results.push.apply( results, checkSet );
1950		} else if ( context && context.nodeType === 1 ) {
1951			for ( var i = 0; checkSet[i] != null; i++ ) {
1952				if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) {
1953					results.push( set[i] );
1954				}
1955			}
1956		} else {
1957			for ( var i = 0; checkSet[i] != null; i++ ) {
1958				if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
1959					results.push( set[i] );
1960				}
1961			}
1962		}
1963	} else {
1964		makeArray( checkSet, results );
1965	}
1966
1967	if ( extra ) {
1968		Sizzle( extra, origContext, results, seed );
1969		Sizzle.uniqueSort( results );
1970	}
1971
1972	return results;
1973};
1974
1975Sizzle.uniqueSort = function(results){
1976	if ( sortOrder ) {
1977		hasDuplicate = baseHasDuplicate;
1978		results.sort(sortOrder);
1979
1980		if ( hasDuplicate ) {
1981			for ( var i = 1; i < results.length; i++ ) {
1982				if ( results[i] === results[i-1] ) {
1983					results.splice(i--, 1);
1984				}
1985			}
1986		}
1987	}
1988
1989	return results;
1990};
1991
1992Sizzle.matches = function(expr, set){
1993	return Sizzle(expr, null, null, set);
1994};
1995
1996Sizzle.find = function(expr, context, isXML){
1997	var set, match;
1998
1999	if ( !expr ) {
2000		return [];
2001	}
2002
2003	for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
2004		var type = Expr.order[i], match;
2005
2006		if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
2007			var left = match[1];
2008			match.splice(1,1);
2009
2010			if ( left.substr( left.length - 1 ) !== "\\" ) {
2011				match[1] = (match[1] || "").replace(/\\/g, "");
2012				set = Expr.find[ type ]( match, context, isXML );
2013				if ( set != null ) {
2014					expr = expr.replace( Expr.match[ type ], "" );
2015					break;
2016				}
2017			}
2018		}
2019	}
2020
2021	if ( !set ) {
2022		set = context.getElementsByTagName("*");
2023	}
2024
2025	return {set: set, expr: expr};
2026};
2027
2028Sizzle.filter = function(expr, set, inplace, not){
2029	var old = expr, result = [], curLoop = set, match, anyFound,
2030		isXMLFilter = set && set[0] && isXML(set[0]);
2031
2032	while ( expr && set.length ) {
2033		for ( var type in Expr.filter ) {
2034			if ( (match = Expr.match[ type ].exec( expr )) != null ) {
2035				var filter = Expr.filter[ type ], found, item;
2036				anyFound = false;
2037
2038				if ( curLoop === result ) {
2039					result = [];
2040				}
2041
2042				if ( Expr.preFilter[ type ] ) {
2043					match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
2044
2045					if ( !match ) {
2046						anyFound = found = true;
2047					} else if ( match === true ) {
2048						continue;
2049					}
2050				}
2051
2052				if ( match ) {
2053					for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
2054						if ( item ) {
2055							found = filter( item, match, i, curLoop );
2056							var pass = not ^ !!found;
2057
2058							if ( inplace && found != null ) {
2059								if ( pass ) {
2060									anyFound = true;
2061								} else {
2062									curLoop[i] = false;
2063								}
2064							} else if ( pass ) {
2065								result.push( item );
2066								anyFound = true;
2067							}
2068						}
2069					}
2070				}
2071
2072				if ( found !== undefined ) {
2073					if ( !inplace ) {
2074						curLoop = result;
2075					}
2076
2077					expr = expr.replace( Expr.match[ type ], "" );
2078
2079					if ( !anyFound ) {
2080						return [];
2081					}
2082
2083					break;
2084				}
2085			}
2086		}
2087
2088		// Improper expression
2089		if ( expr === old ) {
2090			if ( anyFound == null ) {
2091				throw "Syntax error, unrecognized expression: " + expr;
2092			} else {
2093				break;
2094			}
2095		}
2096
2097		old = expr;
2098	}
2099
2100	return curLoop;
2101};
2102
2103var Expr = Sizzle.selectors = {
2104	order: [ "ID", "NAME", "TAG" ],
2105	match: {
2106		ID: /#((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
2107		CLASS: /\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
2108		NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/,
2109		ATTR: /\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
2110		TAG: /^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/,
2111		CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,
2112		POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,
2113		PSEUDO: /:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/
2114	},
2115	leftMatch: {},
2116	attrMap: {
2117		"class": "className",
2118		"for": "htmlFor"
2119	},
2120	attrHandle: {
2121		href: function(elem){
2122			return elem.getAttribute("href");
2123		}
2124	},
2125	relative: {
2126		"+": function(checkSet, part){
2127			var isPartStr = typeof part === "string",
2128				isTag = isPartStr && !/\W/.test(part),
2129				isPartStrNotTag = isPartStr && !isTag;
2130
2131			if ( isTag ) {
2132				part = part.toLowerCase();
2133			}
2134
2135			for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
2136				if ( (elem = checkSet[i]) ) {
2137					while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
2138
2139					checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
2140						elem || false :
2141						elem === part;
2142				}
2143			}
2144
2145			if ( isPartStrNotTag ) {
2146				Sizzle.filter( part, checkSet, true );
2147			}
2148		},
2149		">": function(checkSet, part){
2150			var isPartStr = typeof part === "string";
2151
2152			if ( isPartStr && !/\W/.test(part) ) {
2153				part = part.toLowerCase();
2154
2155				for ( var i = 0, l = checkSet.length; i < l; i++ ) {
2156					var elem = checkSet[i];
2157					if ( elem ) {
2158						var parent = elem.parentNode;
2159						checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
2160					}
2161				}
2162			} else {
2163				for ( var i = 0, l = checkSet.length; i < l; i++ ) {
2164					var elem = checkSet[i];
2165					if ( elem ) {
2166						checkSet[i] = isPartStr ?
2167							elem.parentNode :
2168							elem.parentNode === part;
2169					}
2170				}
2171
2172				if ( isPartStr ) {
2173					Sizzle.filter( part, checkSet, true );
2174				}
2175			}
2176		},
2177		"": function(checkSet, part, isXML){
2178			var doneName = done++, checkFn = dirCheck;
2179
2180			if ( typeof part === "string" && !/\W/.test(part) ) {
2181				var nodeCheck = part = part.toLowerCase();
2182				checkFn = dirNodeCheck;
2183			}
2184
2185			checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
2186		},
2187		"~": function(checkSet, part, isXML){
2188			var doneName = done++, checkFn = dirCheck;
2189
2190			if ( typeof part === "string" && !/\W/.test(part) ) {
2191				var nodeCheck = part = part.toLowerCase();
2192				checkFn = dirNodeCheck;
2193			}
2194
2195			checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
2196		}
2197	},
2198	find: {
2199		ID: function(match, context, isXML){
2200			if ( typeof context.getElementById !== "undefined" && !isXML ) {
2201				var m = context.getElementById(match[1]);
2202				return m ? [m] : [];
2203			}
2204		},
2205		NAME: function(match, context){
2206			if ( typeof context.getElementsByName !== "undefined" ) {
2207				var ret = [], results = context.getElementsByName(match[1]);
2208
2209				for ( var i = 0, l = results.length; i < l; i++ ) {
2210					if ( results[i].getAttribute("name") === match[1] ) {
2211						ret.push( results[i] );
2212					}
2213				}
2214
2215				return ret.length === 0 ? null : ret;
2216			}
2217		},
2218		TAG: function(match, context){
2219			return context.getElementsByTagName(match[1]);
2220		}
2221	},
2222	preFilter: {
2223		CLASS: function(match, curLoop, inplace, result, not, isXML){
2224			match = " " + match[1].replace(/\\/g, "") + " ";
2225
2226			if ( isXML ) {
2227				return match;
2228			}
2229
2230			for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
2231				if ( elem ) {
2232					if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) {
2233						if ( !inplace ) {
2234							result.push( elem );
2235						}
2236					} else if ( inplace ) {
2237						curLoop[i] = false;
2238					}
2239				}
2240			}
2241
2242			return false;
2243		},
2244		ID: function(match){
2245			return match[1].replace(/\\/g, "");
2246		},
2247		TAG: function(match, curLoop){
2248			return match[1].toLowerCase();
2249		},
2250		CHILD: function(match){
2251			if ( match[1] === "nth" ) {
2252				// parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
2253				var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
2254					match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
2255					!/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
2256
2257				// calculate the numbers (first)n+(last) including if they are negative
2258				match[2] = (test[1] + (test[2] || 1)) - 0;
2259				match[3] = test[3] - 0;
2260			}
2261
2262			// TODO: Move to normal caching system
2263			match[0] = done++;
2264
2265			return match;
2266		},
2267		ATTR: function(match, curLoop, inplace, result, not, isXML){
2268			var name = match[1].replace(/\\/g, "");
2269
2270			if ( !isXML && Expr.attrMap[name] ) {
2271				match[1] = Expr.attrMap[name];
2272			}
2273
2274			if ( match[2] === "~=" ) {
2275				match[4] = " " + match[4] + " ";
2276			}
2277
2278			return match;
2279		},
2280		PSEUDO: function(match, curLoop, inplace, result, not){
2281			if ( match[1] === "not" ) {
2282				// If we're dealing with a complex expression, or a simple one
2283				if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
2284					match[3] = Sizzle(match[3], null, null, curLoop);
2285				} else {
2286					var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
2287					if ( !inplace ) {
2288						result.push.apply( result, ret );
2289					}
2290					return false;
2291				}
2292			} else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
2293				return true;
2294			}
2295
2296			return match;
2297		},
2298		POS: function(match){
2299			match.unshift( true );
2300			return match;
2301		}
2302	},
2303	filters: {
2304		enabled: function(elem){
2305			return elem.disabled === false && elem.type !== "hidden";
2306		},
2307		disabled: function(elem){
2308			return elem.disabled === true;
2309		},
2310		checked: function(elem){
2311			return elem.checked === true;
2312		},
2313		selected: function(elem){
2314			// Accessing this property makes selected-by-default
2315			// options in Safari work properly
2316			elem.parentNode.selectedIndex;
2317			return elem.selected === true;
2318		},
2319		parent: function(elem){
2320			return !!elem.firstChild;
2321		},
2322		empty: function(elem){
2323			return !elem.firstChild;
2324		},
2325		has: function(elem, i, match){
2326			return !!Sizzle( match[3], elem ).length;
2327		},
2328		header: function(elem){
2329			return /h\d/i.test( elem.nodeName );
2330		},
2331		text: function(elem){
2332			return "text" === elem.type;
2333		},
2334		radio: function(elem){
2335			return "radio" === elem.type;
2336		},
2337		checkbox: function(elem){
2338			return "checkbox" === elem.type;
2339		},
2340		file: function(elem){
2341			return "file" === elem.type;
2342		},
2343		password: function(elem){
2344			return "password" === elem.type;
2345		},
2346		submit: function(elem){
2347			return "submit" === elem.type;
2348		},
2349		image: function(elem){
2350			return "image" === elem.type;
2351		},
2352		reset: function(elem){
2353			return "reset" === elem.type;
2354		},
2355		button: function(elem){
2356			return "button" === elem.type || elem.nodeName.toLowerCase() === "button";
2357		},
2358		input: function(elem){
2359			return /input|select|textarea|button/i.test(elem.nodeName);
2360		}
2361	},
2362	setFilters: {
2363		first: function(elem, i){
2364			return i === 0;
2365		},
2366		last: function(elem, i, match, array){
2367			return i === array.length - 1;
2368		},
2369		even: function(elem, i){
2370			return i % 2 === 0;
2371		},
2372		odd: function(elem, i){
2373			return i % 2 === 1;
2374		},
2375		lt: function(elem, i, match){
2376			return i < match[3] - 0;
2377		},
2378		gt: function(elem, i, match){
2379			return i > match[3] - 0;
2380		},
2381		nth: function(elem, i, match){
2382			return match[3] - 0 === i;
2383		},
2384		eq: function(elem, i, match){
2385			return match[3] - 0 === i;
2386		}
2387	},
2388	filter: {
2389		PSEUDO: function(elem, match, i, array){
2390			var name = match[1], filter = Expr.filters[ name ];
2391
2392			if ( filter ) {
2393				return filter( elem, i, match, array );
2394			} else if ( name === "contains" ) {
2395				return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0;
2396			} else if ( name === "not" ) {
2397				var not = match[3];
2398
2399				for ( var i = 0, l = not.length; i < l; i++ ) {
2400					if ( not[i] === elem ) {
2401						return false;
2402					}
2403				}
2404
2405				return true;
2406			} else {
2407				throw "Syntax error, unrecognized expression: " + name;
2408			}
2409		},
2410		CHILD: function(elem, match){
2411			var type = match[1], node = elem;
2412			switch (type) {
2413				case 'only':
2414				case 'first':
2415					while ( (node = node.previousSibling) )	 {
2416						if ( node.nodeType === 1 ) {
2417							return false;
2418						}
2419					}
2420					if ( type === "first" ) {
2421						return true;
2422					}
2423					node = elem;
2424				case 'last':
2425					while ( (node = node.nextSibling) )	 {
2426						if ( node.nodeType === 1 ) {
2427							return false;
2428						}
2429					}
2430					return true;
2431				case 'nth':
2432					var first = match[2], last = match[3];
2433
2434					if ( first === 1 && last === 0 ) {
2435						return true;
2436					}
2437
2438					var doneName = match[0],
2439						parent = elem.parentNode;
2440
2441					if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
2442						var count = 0;
2443						for ( node = parent.firstChild; node; node = node.nextSibling ) {
2444							if ( node.nodeType === 1 ) {
2445								node.nodeIndex = ++count;
2446							}
2447						}
2448						parent.sizcache = doneName;
2449					}
2450
2451					var diff = elem.nodeIndex - last;
2452					if ( first === 0 ) {
2453						return diff === 0;
2454					} else {
2455						return ( diff % first === 0 && diff / first >= 0 );
2456					}
2457			}
2458		},
2459		ID: function(elem, match){
2460			return elem.nodeType === 1 && elem.getAttribute("id") === match;
2461		},
2462		TAG: function(elem, match){
2463			return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match;
2464		},
2465		CLASS: function(elem, match){
2466			return (" " + (elem.className || elem.getAttribute("class")) + " ")
2467				.indexOf( match ) > -1;
2468		},
2469		ATTR: function(elem, match){
2470			var name = match[1],
2471				result = Expr.attrHandle[ name ] ?
2472					Expr.attrHandle[ name ]( elem ) :
2473					elem[ name ] != null ?
2474						elem[ name ] :
2475						elem.getAttribute( name ),
2476				value = result + "",
2477				type = match[2],
2478				check = match[4];
2479
2480			return result == null ?
2481				type === "!=" :
2482				type === "=" ?
2483				value === check :
2484				type === "*=" ?
2485				value.indexOf(check) >= 0 :
2486				type === "~=" ?
2487				(" " + value + " ").indexOf(check) >= 0 :
2488				!check ?
2489				value && result !== false :
2490				type === "!=" ?
2491				value !== check :
2492				type === "^=" ?
2493				value.indexOf(check) === 0 :
2494				type === "$=" ?
2495				value.substr(value.length - check.length) === check :
2496				type === "|=" ?
2497				value === check || value.substr(0, check.length + 1) === check + "-" :
2498				false;
2499		},
2500		POS: function(elem, match, i, array){
2501			var name = match[2], filter = Expr.setFilters[ name ];
2502
2503			if ( filter ) {
2504				return filter( elem, i, match, array );
2505			}
2506		}
2507	}
2508};
2509
2510var origPOS = Expr.match.POS;
2511
2512for ( var type in Expr.match ) {
2513	Expr.match[ type ] = new RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source );
2514	Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source );
2515}
2516
2517var makeArray = function(array, results) {
2518	array = Array.prototype.slice.call( array, 0 );
2519
2520	if ( results ) {
2521		results.push.apply( results, array );
2522		return results;
2523	}
2524
2525	return array;
2526};
2527
2528// Perform a simple check to determine if the browser is capable of
2529// converting a NodeList to an array using builtin methods.
2530try {
2531	Array.prototype.slice.call( document.documentElement.childNodes, 0 );
2532
2533// Provide a fallback method if it does not work
2534} catch(e){
2535	makeArray = function(array, results) {
2536		var ret = results || [];
2537
2538		if ( toString.call(array) === "[object Array]" ) {
2539			Array.prototype.push.apply( ret, array );
2540		} else {
2541			if ( typeof array.length === "number" ) {
2542				for ( var i = 0, l = array.length; i < l; i++ ) {
2543					ret.push( array[i] );
2544				}
2545			} else {
2546				for ( var i = 0; array[i]; i++ ) {
2547					ret.push( array[i] );
2548				}
2549			}
2550		}
2551
2552		return ret;
2553	};
2554}
2555
2556var sortOrder;
2557
2558if ( document.documentElement.compareDocumentPosition ) {
2559	sortOrder = function( a, b ) {
2560		if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
2561			if ( a == b ) {
2562				hasDuplicate = true;
2563			}
2564			return a.compareDocumentPosition ? -1 : 1;
2565		}
2566
2567		var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
2568		if ( ret === 0 ) {
2569			hasDuplicate = true;
2570		}
2571		return ret;
2572	};
2573} else if ( "sourceIndex" in document.documentElement ) {
2574	sortOrder = function( a, b ) {
2575		if ( !a.sourceIndex || !b.sourceIndex ) {
2576			if ( a == b ) {
2577				hasDuplicate = true;
2578			}
2579			return a.sourceIndex ? -1 : 1;
2580		}
2581
2582		var ret = a.sourceIndex - b.sourceIndex;
2583		if ( ret === 0 ) {
2584			hasDuplicate = true;
2585		}
2586		return ret;
2587	};
2588} else if ( document.createRange ) {
2589	sortOrder = function( a, b ) {
2590		if ( !a.ownerDocument || !b.ownerDocument ) {
2591			if ( a == b ) {
2592				hasDuplicate = true;
2593			}
2594			return a.ownerDocument ? -1 : 1;
2595		}
2596
2597		var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
2598		aRange.setStart(a, 0);
2599		aRange.setEnd(a, 0);
2600		bRange.setStart(b, 0);
2601		bRange.setEnd(b, 0);
2602		var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
2603		if ( ret === 0 ) {
2604			hasDuplicate = true;
2605		}
2606		return ret;
2607	};
2608}
2609
2610// Utility function for retreiving the text value of an array of DOM nodes
2611function getText( elems ) {
2612	var ret = "", elem;
2613
2614	for ( var i = 0; elems[i]; i++ ) {
2615		elem = elems[i];
2616
2617		// Get the text from text nodes and CDATA nodes
2618		if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
2619			ret += elem.nodeValue;
2620
2621		// Traverse everything else, except comment nodes
2622		} else if ( elem.nodeType !== 8 ) {
2623			ret += getText( elem.childNodes );
2624		}
2625	}
2626
2627	return ret;
2628}
2629
2630// Check to see if the browser returns elements by name when
2631// querying by getElementById (and provide a workaround)
2632(function(){
2633	// We're going to inject a fake input element with a specified name
2634	var form = document.createElement("div"),
2635		id = "script" + (new Date).getTime();
2636	form.innerHTML = "<a name='" + id + "'/>";
2637
2638	// Inject it into the root element, check its status, and remove it quickly
2639	var root = document.documentElement;
2640	root.insertBefore( form, root.firstChild );
2641
2642	// The workaround has to do additional checks after a getElementById
2643	// Which slows things down for other browsers (hence the branching)
2644	if ( document.getElementById( id ) ) {
2645		Expr.find.ID = function(match, context, isXML){
2646			if ( typeof context.getElementById !== "undefined" && !isXML ) {
2647				var m = context.getElementById(match[1]);
2648				return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
2649			}
2650		};
2651
2652		Expr.filter.ID = function(elem, match){
2653			var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
2654			return elem.nodeType === 1 && node && node.nodeValue === match;
2655		};
2656	}
2657
2658	root.removeChild( form );
2659	root = form = null; // release memory in IE
2660})();
2661
2662(function(){
2663	// Check to see if the browser returns only elements
2664	// when doing getElementsByTagName("*")
2665
2666	// Create a fake element
2667	var div = document.createElement("div");
2668	div.appendChild( document.createComment("") );
2669
2670	// Make sure no comments are found
2671	if ( div.getElementsByTagName("*").length > 0 ) {
2672		Expr.find.TAG = function(match, context){
2673			var results = context.getElementsByTagName(match[1]);
2674
2675			// Filter out possible comments
2676			if ( match[1] === "*" ) {
2677				var tmp = [];
2678
2679				for ( var i = 0; results[i]; i++ ) {
2680					if ( results[i].nodeType === 1 ) {
2681						tmp.push( results[i] );
2682					}
2683				}
2684
2685				results = tmp;
2686			}
2687
2688			return results;
2689		};
2690	}
2691
2692	// Check to see if an attribute returns normalized href attributes
2693	div.innerHTML = "<a href='#'></a>";
2694	if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
2695			div.firstChild.getAttribute("href") !== "#" ) {
2696		Expr.attrHandle.href = function(elem){
2697			return elem.getAttribute("href", 2);
2698		};
2699	}
2700
2701	div = null; // release memory in IE
2702})();
2703
2704if ( document.querySelectorAll ) {
2705	(function(){
2706		var oldSizzle = Sizzle, div = document.createElement("div");
2707		div.innerHTML = "<p class='TEST'></p>";
2708
2709		// Safari can't handle uppercase or unicode characters when
2710		// in quirks mode.
2711		if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
2712			return;
2713		}
2714
2715		Sizzle = function(query, context, extra, seed){
2716			context = context || document;
2717
2718			// Only use querySelectorAll on non-XML documents
2719			// (ID selectors don't work in non-HTML documents)
2720			if ( !seed && context.nodeType === 9 && !isXML(context) ) {
2721				try {
2722					return makeArray( context.querySelectorAll(query), extra );
2723				} catch(e){}
2724			}
2725
2726			return oldSizzle(query, context, extra, seed);
2727		};
2728
2729		for ( var prop in oldSizzle ) {
2730			Sizzle[ prop ] = oldSizzle[ prop ];
2731		}
2732
2733		div = null; // release memory in IE
2734	})();
2735}
2736
2737(function(){
2738	var div = document.createElement("div");
2739
2740	div.innerHTML = "<div class='test e'></div><div class='test'></div>";
2741
2742	// Opera can't find a second classname (in 9.6)
2743	// Also, make sure that getElementsByClassName actually exists
2744	if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
2745		return;
2746	}
2747
2748	// Safari caches class attributes, doesn't catch changes (in 3.2)
2749	div.lastChild.className = "e";
2750
2751	if ( div.getElementsByClassName("e").length === 1 ) {
2752		return;
2753	}
2754
2755	Expr.order.splice(1, 0, "CLASS");
2756	Expr.find.CLASS = function(match, context, isXML) {
2757		if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
2758			return context.getElementsByClassName(match[1]);
2759		}
2760	};
2761
2762	div = null; // release memory in IE
2763})();
2764
2765function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
2766	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
2767		var elem = checkSet[i];
2768		if ( elem ) {
2769			elem = elem[dir];
2770			var match = false;
2771
2772			while ( elem ) {
2773				if ( elem.sizcache === doneName ) {
2774					match = checkSet[elem.sizset];
2775					break;
2776				}
2777
2778				if ( elem.nodeType === 1 && !isXML ){
2779					elem.sizcache = doneName;
2780					elem.sizset = i;
2781				}
2782
2783				if ( elem.nodeName.toLowerCase() === cur ) {
2784					match = elem;
2785					break;
2786				}
2787
2788				elem = elem[dir];
2789			}
2790
2791			checkSet[i] = match;
2792		}
2793	}
2794}
2795
2796function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
2797	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
2798		var elem = checkSet[i];
2799		if ( elem ) {
2800			elem = elem[dir];
2801			var match = false;
2802
2803			while ( elem ) {
2804				if ( elem.sizcache === doneName ) {
2805					match = checkSet[elem.sizset];
2806					break;
2807				}
2808
2809				if ( elem.nodeType === 1 ) {
2810					if ( !isXML ) {
2811						elem.sizcache = doneName;
2812						elem.sizset = i;
2813					}
2814					if ( typeof cur !== "string" ) {
2815						if ( elem === cur ) {
2816							match = true;
2817							break;
2818						}
2819
2820					} else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
2821						match = elem;
2822						break;
2823					}
2824				}
2825
2826				elem = elem[dir];
2827			}
2828
2829			checkSet[i] = match;
2830		}
2831	}
2832}
2833
2834var contains = document.compareDocumentPosition ? function(a, b){
2835	return a.compareDocumentPosition(b) & 16;
2836} : function(a, b){
2837	return a !== b && (a.contains ? a.contains(b) : true);
2838};
2839
2840var isXML = function(elem){
2841	// documentElement is verified for cases where it doesn't yet exist
2842	// (such as loading iframes in IE - #4833)
2843	var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
2844	return documentElement ? documentElement.nodeName !== "HTML" : false;
2845};
2846
2847var posProcess = function(selector, context){
2848	var tmpSet = [], later = "", match,
2849		root = context.nodeType ? [context] : context;
2850
2851	// Position selectors must be done after the filter
2852	// And so must :not(positional) so we move all PSEUDOs to the end
2853	while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
2854		later += match[0];
2855		selector = selector.replace( Expr.match.PSEUDO, "" );
2856	}
2857
2858	selector = Expr.relative[selector] ? selector + "*" : selector;
2859
2860	for ( var i = 0, l = root.length; i < l; i++ ) {
2861		Sizzle( selector, root[i], tmpSet );
2862	}
2863
2864	return Sizzle.filter( later, tmpSet );
2865};
2866
2867// EXPOSE
2868
2869//window.Sizzle = Sizzle;
2870return Sizzle;
2871
2872})();
2873/*!
2874 * SWFObject v2.1 <http://code.google.com/p/swfobject/>
2875 * Copyright (c) 2007-2008 Geoff Stearns, Michael Williams, and Bobby van der Sluis
2876 * This software is released under the MIT License <http://www.opensource.org/licenses/mit-license.php>
2877 *
2878 * Modified for inclusion in Shadowbox.js
2879 */
2880S.flash = (function(){
2881
2882var swfobject = function() {
2883
2884	var UNDEF = "undefined",
2885		OBJECT = "object",
2886		SHOCKWAVE_FLASH = "Shockwave Flash",
2887		SHOCKWAVE_FLASH_AX = "ShockwaveFlash.ShockwaveFlash",
2888		FLASH_MIME_TYPE = "application/x-shockwave-flash",
2889		EXPRESS_INSTALL_ID = "SWFObjectExprInst",
2890
2891		win = window,
2892		doc = document,
2893		nav = navigator,
2894
2895		domLoadFnArr = [],
2896		regObjArr = [],
2897		objIdArr = [],
2898		listenersArr = [],
2899		script,
2900		timer = null,
2901		storedAltContent = null,
2902		storedAltContentId = null,
2903		isDomLoaded = false,
2904		isExpressInstallActive = false;
2905
2906	/* Centralized function for browser feature detection
2907		- Proprietary feature detection (conditional compiling) is used to detect Internet Explorer's features
2908		- User agent string detection is only used when no alternative is possible
2909		- Is executed directly for optimal performance
2910	*/
2911	var ua = function() {
2912		var w3cdom = typeof doc.getElementById != UNDEF && typeof doc.getElementsByTagName != UNDEF && typeof doc.createElement != UNDEF,
2913			playerVersion = [0,0,0],
2914			d = null;
2915		if (typeof nav.plugins != UNDEF && typeof nav.plugins[SHOCKWAVE_FLASH] == OBJECT) {
2916			d = nav.plugins[SHOCKWAVE_FLASH].description;
2917			if (d && !(typeof nav.mimeTypes != UNDEF && nav.mimeTypes[FLASH_MIME_TYPE] && !nav.mimeTypes[FLASH_MIME_TYPE].enabledPlugin)) { // navigator.mimeTypes["application/x-shockwave-flash"].enabledPlugin indicates whether plug-ins are enabled or disabled in Safari 3+
2918				d = d.replace(/^.*\s+(\S+\s+\S+$)/, "$1");
2919				playerVersion[0] = parseInt(d.replace(/^(.*)\..*$/, "$1"), 10);
2920				playerVersion[1] = parseInt(d.replace(/^.*\.(.*)\s.*$/, "$1"), 10);
2921				playerVersion[2] = /r/.test(d) ? parseInt(d.replace(/^.*r(.*)$/, "$1"), 10) : 0;
2922			}
2923		}
2924		else if (typeof win.ActiveXObject != UNDEF) {
2925			var a = null, fp6Crash = false;
2926			try {
2927				a = new ActiveXObject(SHOCKWAVE_FLASH_AX + ".7");
2928			}
2929			catch(e) {
2930				try {
2931					a = new ActiveXObject(SHOCKWAVE_FLASH_AX + ".6");
2932					playerVersion = [6,0,21];
2933					a.AllowScriptAccess = "always";	 // Introduced in fp6.0.47
2934				}
2935				catch(e) {
2936					if (playerVersion[0] == 6) {
2937						fp6Crash = true;
2938					}
2939				}
2940				if (!fp6Crash) {
2941					try {
2942						a = new ActiveXObject(SHOCKWAVE_FLASH_AX);
2943					}
2944					catch(e) {}
2945				}
2946			}
2947			if (!fp6Crash && a) { // a will return null when ActiveX is disabled
2948				try {
2949					d = a.GetVariable("$version");	// Will crash fp6.0.21/23/29
2950					if (d) {
2951						d = d.split(" ")[1].split(",");
2952						playerVersion = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)];
2953					}
2954				}
2955				catch(e) {}
2956			}
2957		}
2958		var u = nav.userAgent.toLowerCase(),
2959			p = nav.platform.toLowerCase(),
2960			webkit = /webkit/.test(u) ? parseFloat(u.replace(/^.*webkit\/(\d+(\.\d+)?).*$/, "$1")) : false, // returns either the webkit version or false if not webkit
2961			ie = false,
2962			windows = p ? /win/.test(p) : /win/.test(u),
2963			mac = p ? /mac/.test(p) : /mac/.test(u);
2964		/*@cc_on
2965			ie = true;
2966			@if (@_win32)
2967				windows = true;
2968			@elif (@_mac)
2969				mac = true;
2970			@end
2971		@*/
2972		return { w3cdom:w3cdom, pv:playerVersion, webkit:webkit, ie:ie, win:windows, mac:mac };
2973	}();
2974
2975	/* Cross-browser onDomLoad
2976		- Based on Dean Edwards' solution: http://dean.edwards.name/weblog/2006/06/again/
2977		- Will fire an event as soon as the DOM of a page is loaded (supported by Gecko based browsers - like Firefox -, IE, Opera9+, Safari)
2978	*/
2979	var onDomLoad = function() {
2980		if (!ua.w3cdom) {
2981			return;
2982		}
2983		addDomLoadEvent(main);
2984		if (ua.ie && ua.win) {
2985			try {	 // Avoid a possible Operation Aborted error
2986				doc.write("<scr" + "ipt id=__ie_ondomload defer=true src=//:></scr" + "ipt>"); // String is split into pieces to avoid Norton AV to add code that can cause errors
2987				script = getElementById("__ie_ondomload");
2988				if (script) {
2989					addListener(script, "onreadystatechange", checkReadyState);
2990				}
2991			}
2992			catch(e) {}
2993		}
2994		if (ua.webkit && typeof doc.readyState != UNDEF) {
2995			timer = setInterval(function() { if (/loaded|complete/.test(doc.readyState)) { callDomLoadFunctions(); }}, 10);
2996		}
2997		if (typeof doc.addEventListener != UNDEF) {
2998			doc.addEventListener("DOMContentLoaded", callDomLoadFunctions, null);
2999		}
3000		addLoadEvent(callDomLoadFunctions);
3001	}();
3002
3003	function checkReadyState() {
3004		if (script.readyState == "complete") {
3005			script.parentNode.removeChild(script);
3006			callDomLoadFunctions();
3007		}
3008	}
3009
3010	function callDomLoadFunctions() {
3011		if (isDomLoaded) {
3012			return;
3013		}
3014		if (ua.ie && ua.win) { // Test if we can really add elements to the DOM; we don't want to fire it too early
3015			var s = createElement("span");
3016			try { // Avoid a possible Operation Aborted error
3017				var t = doc.getElementsByTagName("body")[0].appendChild(s);
3018				t.parentNode.removeChild(t);
3019			}
3020			catch (e) {
3021				return;
3022			}
3023		}
3024		isDomLoaded = true;
3025		if (timer) {
3026			clearInterval(timer);
3027			timer = null;
3028		}
3029		var dl = domLoadFnArr.length;
3030		for (var i = 0; i < dl; i++) {
3031			domLoadFnArr[i]();
3032		}
3033	}
3034
3035	function addDomLoadEvent(fn) {
3036		if (isDomLoaded) {
3037			fn();
3038		}
3039		else {
3040			domLoadFnArr[domLoadFnArr.length] = fn; // Array.push() is only available in IE5.5+
3041		}
3042	}
3043
3044	/* Cross-browser onload
3045		- Based on James Edwards' solution: http://brothercake.com/site/resources/scripts/onload/
3046		- Will fire an event as soon as a web page including all of its assets are loaded
3047	 */
3048	function addLoadEvent(fn) {
3049		if (typeof win.addEventListener != UNDEF) {
3050			win.addEventListener("load", fn, false);
3051		}
3052		else if (typeof doc.addEventListener != UNDEF) {
3053			doc.addEventListener("load", fn, false);
3054		}
3055		else if (typeof win.attachEvent != UNDEF) {
3056			addListener(win, "onload", fn);
3057		}
3058		else if (typeof win.onload == "function") {
3059			var fnOld = win.onload;
3060			win.onload = function() {
3061				fnOld();
3062				fn();
3063			};
3064		}
3065		else {
3066			win.onload = fn;
3067		}
3068	}
3069
3070	/* Main function
3071		- Will preferably execute onDomLoad, otherwise onload (as a fallback)
3072	*/
3073	function main() { // Static publishing only
3074		var rl = regObjArr.length;
3075		for (var i = 0; i < rl; i++) { // For each registered object element
3076			var id = regObjArr[i].id;
3077			if (ua.pv[0] > 0) {
3078				var obj = getElementById(id);
3079				if (obj) {
3080					regObjArr[i].width = obj.getAttribute("width") ? obj.getAttribute("width") : "0";
3081					regObjArr[i].height = obj.getAttribute("height") ? obj.getAttribute("height") : "0";
3082					if (hasPlayerVersion(regObjArr[i].swfVersion)) { // Flash plug-in version >= Flash content version: Houston, we have a match!
3083						if (ua.webkit && ua.webkit < 312) { // Older webkit engines ignore the object element's nested param elements
3084							fixParams(obj);
3085						}
3086						setVisibility(id, true);
3087					}
3088					else if (regObjArr[i].expressInstall && !isExpressInstallActive && hasPlayerVersion("6.0.65") && (ua.win || ua.mac)) { // Show the Adobe Express Install dialog if set by the web page author and if supported (fp6.0.65+ on Win/Mac OS only)
3089						showExpressInstall(regObjArr[i]);
3090					}
3091					else { // Flash plug-in and Flash content version mismatch: display alternative content instead of Flash content
3092						displayAltContent(obj);
3093					}
3094				}
3095			}
3096			else {	// If no fp is installed, we let the object element do its job (show alternative content)
3097				setVisibility(id, true);
3098			}
3099		}
3100	}
3101
3102	/* Fix nested param elements, which are ignored by older webkit engines
3103		- This includes Safari up to and including version 1.2.2 on Mac OS 10.3
3104		- Fall back to the proprietary embed element
3105	*/
3106	function fixParams(obj) {
3107		var nestedObj = obj.getElementsByTagName(OBJECT)[0];
3108		if (nestedObj) {
3109			var e = createElement("embed"), a = nestedObj.attributes;
3110			if (a) {
3111				var al = a.length;
3112				for (var i = 0; i < al; i++) {
3113					if (a[i].nodeName == "DATA") {
3114						e.setAttribute("src", a[i].nodeValue);
3115					}
3116					else {
3117						e.setAttribute(a[i].nodeName, a[i].nodeValue);
3118					}
3119				}
3120			}
3121			var c = nestedObj.childNodes;
3122			if (c) {
3123				var cl = c.length;
3124				for (var j = 0; j < cl; j++) {
3125					if (c[j].nodeType == 1 && c[j].nodeName == "PARAM") {
3126						e.setAttribute(c[j].getAttribute("name"), c[j].getAttribute("value"));
3127					}
3128				}
3129			}
3130			obj.parentNode.replaceChild(e, obj);
3131		}
3132	}
3133
3134	/* Show the Adobe Express Install dialog
3135		- Reference: http://www.adobe.com/cfusion/knowledgebase/index.cfm?id=6a253b75
3136	*/
3137	function showExpressInstall(regObj) {
3138		isExpressInstallActive = true;
3139		var obj = getElementById(regObj.id);
3140		if (obj) {
3141			if (regObj.altContentId) {
3142				var ac = getElementById(regObj.altContentId);
3143				if (ac) {
3144					storedAltContent = ac;
3145					storedAltContentId = regObj.altContentId;
3146				}
3147			}
3148			else {
3149				storedAltContent = abstractAltContent(obj);
3150			}
3151			if (!(/%$/.test(regObj.width)) && parseInt(regObj.width, 10) < 310) {
3152				regObj.width = "310";
3153			}
3154			if (!(/%$/.test(regObj.height)) && parseInt(regObj.height, 10) < 137) {
3155				regObj.height = "137";
3156			}
3157			doc.title = doc.title.slice(0, 47) + " - Flash Player Installation";
3158			var pt = ua.ie && ua.win ? "ActiveX" : "PlugIn",
3159				dt = doc.title,
3160				fv = "MMredirectURL=" + win.location + "&MMplayerType=" + pt + "&MMdoctitle=" + dt,
3161				replaceId = regObj.id;
3162			// For IE when a SWF is loading (AND: not available in cache) wait for the onload event to fire to remove the original object element
3163			// In IE you cannot properly cancel a loading SWF file without breaking browser load references, also obj.onreadystatechange doesn't work
3164			if (ua.ie && ua.win && obj.readyState != 4) {
3165				var newObj = createElement("div");
3166				replaceId += "SWFObjectNew";
3167				newObj.setAttribute("id", replaceId);
3168				obj.parentNode.insertBefore(newObj, obj); // Insert placeholder div that will be replaced by the object element that loads expressinstall.swf
3169				obj.style.display = "none";
3170				var fn = function() {
3171					obj.parentNode.removeChild(obj);
3172				};
3173				addListener(win, "onload", fn);
3174			}
3175			createSWF({ data:regObj.expressInstall, id:EXPRESS_INSTALL_ID, width:regObj.width, height:regObj.height }, { flashvars:fv }, replaceId);
3176		}
3177	}
3178
3179	/* Functions to abstract and display alternative content
3180	*/
3181	function displayAltContent(obj) {
3182		if (ua.ie && ua.win && obj.readyState != 4) {
3183			// For IE when a SWF is loading (AND: not available in cache) wait for the onload event to fire to remove the original object element
3184			// In IE you cannot properly cancel a loading SWF file without breaking browser load references, also obj.onreadystatechange doesn't work
3185			var el = createElement("div");
3186			obj.parentNode.insertBefore(el, obj); // Insert placeholder div that will be replaced by the alternative content
3187			el.parentNode.replaceChild(abstractAltContent(obj), el);
3188			obj.style.display = "none";
3189			var fn = function() {
3190				obj.parentNode.removeChild(obj);
3191			};
3192			addListener(win, "onload", fn);
3193		}
3194		else {
3195			obj.parentNode.replaceChild(abstractAltContent(obj), obj);
3196		}
3197	}
3198
3199	function abstractAltContent(obj) {
3200		var ac = createElement("div");
3201		if (ua.win && ua.ie) {
3202			ac.innerHTML = obj.innerHTML;
3203		}
3204		else {
3205			var nestedObj = obj.getElementsByTagName(OBJECT)[0];
3206			if (nestedObj) {
3207				var c = nestedObj.childNodes;
3208				if (c) {
3209					var cl = c.length;
3210					for (var i = 0; i < cl; i++) {
3211						if (!(c[i].nodeType == 1 && c[i].nodeName == "PARAM") && !(c[i].nodeType == 8)) {
3212							ac.appendChild(c[i].cloneNode(true));
3213						}
3214					}
3215				}
3216			}
3217		}
3218		return ac;
3219	}
3220
3221	/* Cross-browser dynamic SWF creation
3222	*/
3223	function createSWF(attObj, parObj, id) {
3224		var r, el = getElementById(id);
3225		if (el) {
3226			if (typeof attObj.id == UNDEF) { // if no 'id' is defined for the object element, it will inherit the 'id' from the alternative content
3227				attObj.id = id;
3228			}
3229			if (ua.ie && ua.win) { // IE, the object element and W3C DOM methods do not combine: fall back to outerHTML
3230				var att = "";
3231				for (var i in attObj) {
3232					if (attObj[i] != Object.prototype[i]) { // Filter out prototype additions from other potential libraries, like Object.prototype.toJSONString = function() {}
3233						if (i.toLowerCase() == "data") {
3234							parObj.movie = attObj[i];
3235						}
3236						else if (i.toLowerCase() == "styleclass") { // 'class' is an ECMA4 reserved keyword
3237							att += ' class="' + attObj[i] + '"';
3238						}
3239						else if (i.toLowerCase() != "classid") {
3240							att += ' ' + i + '="' + attObj[i] + '"';
3241						}
3242					}
3243				}
3244				var par = "";
3245				for (var j in parObj) {
3246					if (parObj[j] != Object.prototype[j]) { // Filter out prototype additions from other potential libraries
3247						par += '<param name="' + j + '" value="' + parObj[j] + '" />';
3248					}
3249				}
3250				el.outerHTML = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"' + att + '>' + par + '</object>';
3251				objIdArr[objIdArr.length] = attObj.id; // Stored to fix object 'leaks' on unload (dynamic publishing only)
3252				r = getElementById(attObj.id);
3253			}
3254			else if (ua.webkit && ua.webkit < 312) { // Older webkit engines ignore the object element's nested param elements: fall back to the proprietary embed element
3255				var e = createElement("embed");
3256				e.setAttribute("type", FLASH_MIME_TYPE);
3257				for (var k in attObj) {
3258					if (attObj[k] != Object.prototype[k]) { // Filter out prototype additions from other potential libraries
3259						if (k.toLowerCase() == "data") {
3260							e.setAttribute("src", attObj[k]);
3261						}
3262						else if (k.toLowerCase() == "styleclass") { // 'class' is an ECMA4 reserved keyword
3263							e.setAttribute("class", attObj[k]);
3264						}
3265						else if (k.toLowerCase() != "classid") { // Filter out IE specific attribute
3266							e.setAttribute(k, attObj[k]);
3267						}
3268					}
3269				}
3270				for (var l in parObj) {
3271					if (parObj[l] != Object.prototype[l]) { // Filter out prototype additions from other potential libraries
3272						if (l.toLowerCase() != "movie") { // Filter out IE specific param element
3273							e.setAttribute(l, parObj[l]);
3274						}
3275					}
3276				}
3277				el.parentNode.replaceChild(e, el);
3278				r = e;
3279			}
3280			else { // Well-behaving browsers
3281				var o = createElement(OBJECT);
3282				o.setAttribute("type", FLASH_MIME_TYPE);
3283				for (var m in attObj) {
3284					if (attObj[m] != Object.prototype[m]) { // Filter out prototype additions from other potential libraries
3285						if (m.toLowerCase() == "styleclass") { // 'class' is an ECMA4 reserved keyword
3286							o.setAttribute("class", attObj[m]);
3287						}
3288						else if (m.toLowerCase() != "classid") { // Filter out IE specific attribute
3289							o.setAttribute(m, attObj[m]);
3290						}
3291					}
3292				}
3293				for (var n in parObj) {
3294					if (parObj[n] != Object.prototype[n] && n.toLowerCase() != "movie") { // Filter out prototype additions from other potential libraries and IE specific param element
3295						createObjParam(o, n, parObj[n]);
3296					}
3297				}
3298				el.parentNode.replaceChild(o, el);
3299				r = o;
3300			}
3301		}
3302		return r;
3303	}
3304
3305	function createObjParam(el, pName, pValue) {
3306		var p = createElement("param");
3307		p.setAttribute("name", pName);
3308		p.setAttribute("value", pValue);
3309		el.appendChild(p);
3310	}
3311
3312	/* Cross-browser SWF removal
3313		- Especially needed to safely and completely remove a SWF in Internet Explorer
3314	*/
3315	function removeSWF(id) {
3316		var obj = getElementById(id);
3317		if (obj && (obj.nodeName == "OBJECT" || obj.nodeName == "EMBED")) {
3318			if (ua.ie && ua.win) {
3319				if (obj.readyState == 4) {
3320					removeObjectInIE(id);
3321				}
3322				else {
3323					win.attachEvent("onload", function() {
3324						removeObjectInIE(id);
3325					});
3326				}
3327			}
3328			else {
3329				obj.parentNode.removeChild(obj);
3330			}
3331		}
3332	}
3333
3334	function removeObjectInIE(id) {
3335		var obj = getElementById(id);
3336		if (obj) {
3337			for (var i in obj) {
3338				if (typeof obj[i] == "function") {
3339					obj[i] = null;
3340				}
3341			}
3342			obj.parentNode.removeChild(obj);
3343		}
3344	}
3345
3346	/* Functions to optimize JavaScript compression
3347	*/
3348	function getElementById(id) {
3349		var el = null;
3350		try {
3351			el = doc.getElementById(id);
3352		}
3353		catch (e) {}
3354		return el;
3355	}
3356
3357	function createElement(el) {
3358		return doc.createElement(el);
3359	}
3360
3361	/* Updated attachEvent function for Internet Explorer
3362		- Stores attachEvent information in an Array, so on unload the detachEvent functions can be called to avoid memory leaks
3363	*/
3364	function addListener(target, eventType, fn) {
3365		target.attachEvent(eventType, fn);
3366		listenersArr[listenersArr.length] = [target, eventType, fn];
3367	}
3368
3369	/* Flash Player and SWF content version matching
3370	*/
3371	function hasPlayerVersion(rv) {
3372		var pv = ua.pv, v = rv.split(".");
3373		v[0] = parseInt(v[0], 10);
3374		v[1] = parseInt(v[1], 10) || 0; // supports short notation, e.g. "9" instead of "9.0.0"
3375		v[2] = parseInt(v[2], 10) || 0;
3376		return (pv[0] > v[0] || (pv[0] == v[0] && pv[1] > v[1]) || (pv[0] == v[0] && pv[1] == v[1] && pv[2] >= v[2])) ? true : false;
3377	}
3378
3379	/* Cross-browser dynamic CSS creation
3380		- Based on Bobby van der Sluis' solution: http://www.bobbyvandersluis.com/articles/dynamicCSS.php
3381	*/
3382	function createCSS(sel, decl) {
3383		if (ua.ie && ua.mac) {
3384			return;
3385		}
3386		var h = doc.getElementsByTagName("head")[0], s = createElement("style");
3387		s.setAttribute("type", "text/css");
3388		s.setAttribute("media", "screen");
3389		if (!(ua.ie && ua.win) && typeof doc.createTextNode != UNDEF) {
3390			s.appendChild(doc.createTextNode(sel + " {" + decl + "}"));
3391		}
3392		h.appendChild(s);
3393		if (ua.ie && ua.win && typeof doc.styleSheets != UNDEF && doc.styleSheets.length > 0) {
3394			var ls = doc.styleSheets[doc.styleSheets.length - 1];
3395			if (typeof ls.addRule == OBJECT) {
3396				ls.addRule(sel, decl);
3397			}
3398		}
3399	}
3400
3401	function setVisibility(id, isVisible) {
3402		var v = isVisible ? "visible" : "hidden";
3403		if (isDomLoaded && getElementById(id)) {
3404			getElementById(id).style.visibility = v;
3405		}
3406		else {
3407			createCSS("#" + id, "visibility:" + v);
3408		}
3409	}
3410
3411	/* Filter to avoid XSS attacks
3412	*/
3413	function urlEncodeIfNecessary(s) {
3414		var regex = /[\\\"<>\.;]/;
3415		var hasBadChars = regex.exec(s) != null;
3416		return hasBadChars ? encodeURIComponent(s) : s;
3417	}
3418
3419	/* Release memory to avoid memory leaks caused by closures, fix hanging audio/video threads and force open sockets/NetConnections to disconnect (Internet Explorer only)
3420	*/
3421	var cleanup = function() {
3422		if (ua.ie && ua.win) {
3423			window.attachEvent("onunload", function() {
3424				// remove listeners to avoid memory leaks
3425				var ll = listenersArr.length;
3426				for (var i = 0; i < ll; i++) {
3427					listenersArr[i][0].detachEvent(listenersArr[i][1], listenersArr[i][2]);
3428				}
3429				// cleanup dynamically embedded objects to fix audio/video threads and force open sockets and NetConnections to disconnect
3430				var il = objIdArr.length;
3431				for (var j = 0; j < il; j++) {
3432					removeSWF(objIdArr[j]);
3433				}
3434				// cleanup library's main closures to avoid memory leaks
3435				for (var k in ua) {
3436					ua[k] = null;
3437				}
3438				ua = null;
3439				for (var l in swfobject) {
3440					swfobject[l] = null;
3441				}
3442				swfobject = null;
3443			});
3444		}
3445	}();
3446
3447
3448	return {
3449		/* Public API
3450			- Reference: http://code.google.com/p/swfobject/wiki/SWFObject_2_0_documentation
3451		*/
3452		registerObject: function(objectIdStr, swfVersionStr, xiSwfUrlStr) {
3453			if (!ua.w3cdom || !objectIdStr || !swfVersionStr) {
3454				return;
3455			}
3456			var regObj = {};
3457			regObj.id = objectIdStr;
3458			regObj.swfVersion = swfVersionStr;
3459			regObj.expressInstall = xiSwfUrlStr ? xiSwfUrlStr : false;
3460			regObjArr[regObjArr.length] = regObj;
3461			setVisibility(objectIdStr, false);
3462		},
3463
3464		getObjectById: function(objectIdStr) {
3465			var r = null;
3466			if (ua.w3cdom) {
3467				var o = getElementById(objectIdStr);
3468				if (o) {
3469					var n = o.getElementsByTagName(OBJECT)[0];
3470					if (!n || (n && typeof o.SetVariable != UNDEF)) {
3471							r = o;
3472					}
3473					else if (typeof n.SetVariable != UNDEF) {
3474						r = n;
3475					}
3476				}
3477			}
3478			return r;
3479		},
3480
3481		embedSWF: function(swfUrlStr, replaceElemIdStr, widthStr, heightStr, swfVersionStr, xiSwfUrlStr, flashvarsObj, parObj, attObj) {
3482			if (!ua.w3cdom || !swfUrlStr || !replaceElemIdStr || !widthStr || !heightStr || !swfVersionStr) {
3483				return;
3484			}
3485			widthStr += ""; // Auto-convert to string
3486			heightStr += "";
3487			if (hasPlayerVersion(swfVersionStr)) {
3488				setVisibility(replaceElemIdStr, false);
3489				var att = {};
3490				if (attObj && typeof attObj === OBJECT) {
3491					for (var i in attObj) {
3492						if (attObj[i] != Object.prototype[i]) { // Filter out prototype additions from other potential libraries
3493							att[i] = attObj[i];
3494						}
3495					}
3496				}
3497				att.data = swfUrlStr;
3498				att.width = widthStr;
3499				att.height = heightStr;
3500				var par = {};
3501				if (parObj && typeof parObj === OBJECT) {
3502					for (var j in parObj) {
3503						if (parObj[j] != Object.prototype[j]) { // Filter out prototype additions from other potential libraries
3504							par[j] = parObj[j];
3505						}
3506					}
3507				}
3508				if (flashvarsObj && typeof flashvarsObj === OBJECT) {
3509					for (var k in flashvarsObj) {
3510						if (flashvarsObj[k] != Object.prototype[k]) { // Filter out prototype additions from other potential libraries
3511							if (typeof par.flashvars != UNDEF) {
3512								par.flashvars += "&" + k + "=" + flashvarsObj[k];
3513							}
3514							else {
3515								par.flashvars = k + "=" + flashvarsObj[k];
3516							}
3517						}
3518					}
3519				}
3520				addDomLoadEvent(function() {
3521					createSWF(att, par, replaceElemIdStr);
3522					if (att.id == replaceElemIdStr) {
3523						setVisibility(replaceElemIdStr, true);
3524					}
3525				});
3526			}
3527			else if (xiSwfUrlStr && !isExpressInstallActive && hasPlayerVersion("6.0.65") && (ua.win || ua.mac)) {
3528				isExpressInstallActive = true; // deferred execution
3529				setVisibility(replaceElemIdStr, false);
3530				addDomLoadEvent(function() {
3531					var regObj = {};
3532					regObj.id = regObj.altContentId = replaceElemIdStr;
3533					regObj.width = widthStr;
3534					regObj.height = heightStr;
3535					regObj.expressInstall = xiSwfUrlStr;
3536					showExpressInstall(regObj);
3537				});
3538			}
3539		},
3540
3541		getFlashPlayerVersion: function() {
3542			return { major:ua.pv[0], minor:ua.pv[1], release:ua.pv[2] };
3543		},
3544
3545		hasFlashPlayerVersion: hasPlayerVersion,
3546
3547		createSWF: function(attObj, parObj, replaceElemIdStr) {
3548			if (ua.w3cdom) {
3549				return createSWF(attObj, parObj, replaceElemIdStr);
3550			}
3551			else {
3552				return undefined;
3553			}
3554		},
3555
3556		removeSWF: function(objElemIdStr) {
3557			if (ua.w3cdom) {
3558				removeSWF(objElemIdStr);
3559			}
3560		},
3561
3562		createCSS: function(sel, decl) {
3563			if (ua.w3cdom) {
3564				createCSS(sel, decl);
3565			}
3566		},
3567
3568		addDomLoadEvent: addDomLoadEvent,
3569
3570		addLoadEvent: addLoadEvent,
3571
3572		getQueryParamValue: function(param) {
3573			var q = doc.location.search || doc.location.hash;
3574			if (param == null) {
3575				return urlEncodeIfNecessary(q);
3576			}
3577			if (q) {
3578				var pairs = q.substring(1).split("&");
3579				for (var i = 0; i < pairs.length; i++) {
3580					if (pairs[i].substring(0, pairs[i].indexOf("=")) == param) {
3581						return urlEncodeIfNecessary(pairs[i].substring((pairs[i].indexOf("=") + 1)));
3582					}
3583				}
3584			}
3585			return "";
3586		},
3587
3588		// For internal usage only
3589		expressInstallCallback: function() {
3590			if (isExpressInstallActive && storedAltContent) {
3591				var obj = getElementById(EXPRESS_INSTALL_ID);
3592				if (obj) {
3593					obj.parentNode.replaceChild(storedAltContent, obj);
3594					if (storedAltContentId) {
3595						setVisibility(storedAltContentId, true);
3596						if (ua.ie && ua.win) {
3597							storedAltContent.style.display = "block";
3598						}
3599					}
3600					storedAltContent = null;
3601					storedAltContentId = null;
3602					isExpressInstallActive = false;
3603				}
3604			}
3605		}
3606	};
3607}();
3608
3609return swfobject;
3610
3611})();
3612/**
3613 * The English language translation for Shadowbox.
3614 */
3615
3616S.lang = {
3617    code:       'en',
3618    of:         'of',
3619    loading:    'loading',
3620    cancel:     'Cancel',
3621    next:       'Next',
3622    previous:   'Previous',
3623    play:       'Play',
3624    pause:      'Pause',
3625    close:      'Close',
3626    errors:     {
3627        single: 'You must install the <a href="{0}">{1}</a> browser plugin to view this content.',
3628        shared: 'You must install both the <a href="{0}">{1}</a> and <a href="{2}">{3}</a> browser plugins to view this content.',
3629        either: 'You must install either the <a href="{0}">{1}</a> or the <a href="{2}">{3}</a> browser plugin to view this content.'
3630    }
3631}
3632/**
3633 * The FLV player for Shadowbox.
3634 */
3635
3636/**
3637 * The height (in pixels) of the JW FLV player controller.
3638 *
3639 * @type    {Number}
3640 * @private
3641 */
3642var jwControllerHeight = 20;
3643
3644/**
3645 * Constructor. The Flash video player class for Shadowbox.
3646 *
3647 * @constructor
3648 * @param   {Object}    obj     The content object
3649 * @param   {String}    id      The player id
3650 * @public
3651 */
3652S.flv = function(obj, id) {
3653    this.obj = obj;
3654    this.id = id;
3655
3656    // height/width default to 300 pixels
3657    this.height = obj.height ? parseInt(obj.height, 10) : 300;
3658    if (S.options.showMovieControls)
3659        this.height += jwControllerHeight;
3660    this.width = obj.width ? parseInt(obj.width, 10) : 300;
3661}
3662
3663S.flv.ext = ["flv", "m4v"];
3664
3665S.flv.prototype = {
3666
3667    /**
3668     * Appends this movie to the document.
3669     *
3670     * @param   {HTMLElement}   body    The body element
3671     * @param   {Object}        dims    The current Shadowbox dimensions
3672     * @public
3673     */
3674    append: function(body, dims) {
3675        // append temporary content element to replace
3676        var tmp = document.createElement('div');
3677        tmp.id = this.id;
3678        body.appendChild(tmp);
3679
3680        var height = dims.innerHeight,
3681            width = dims.innerWidth,
3682            swf = S.path + "player.swf",
3683            version = S.options.flashVersion,
3684            express = S.path + "expressInstall.swf",
3685            flashvars = apply({
3686                file:       this.obj.content,
3687                height:     height,
3688                width:      width,
3689                autostart:  (S.options.autoplayMovies ? "true" : "false"),
3690                controlbar: (S.options.showMovieControls ? "bottom" : "none"),
3691                backcolor:  "0x000000",
3692                frontcolor: "0xCCCCCC",
3693                lightcolor: "0x557722"
3694            }, S.options.flashVars),
3695            params = S.options.flashParams;
3696
3697        S.flash.embedSWF(swf, this.id, width, height, version, express, flashvars, params);
3698    },
3699
3700    /**
3701     * Removes this movie from the document.
3702     *
3703     * @public
3704     */
3705    remove: function() {
3706        // call express install callback here in case express install is
3707        // active and user has not selected anything
3708        S.flash.expressInstallCallback();
3709        S.flash.removeSWF(this.id);
3710    },
3711
3712    /**
3713     * Called when the window is resized.
3714     *
3715     * @public
3716     */
3717    onWindowResize: function() {
3718        var dims = S.dimensions,
3719            el = get(this.id);
3720        el.height = dims.innerHeight;
3721        el.width = dims.innerWidth;
3722    }
3723
3724}
3725/**
3726 * The HTML player for Shadowbox.
3727 */
3728
3729/**
3730 * Constructor. The HTML player class for Shadowbox.
3731 *
3732 * @constructor
3733 * @param   {Object}    obj     The content object
3734 * @param   {String}    id      The player id
3735 * @public
3736 */
3737S.html = function(obj, id) {
3738    this.obj = obj;
3739    this.id = id;
3740
3741    // height defaults to 300, width defaults to 500
3742    this.height = obj.height ? parseInt(obj.height, 10) : 300;
3743    this.width = obj.width ? parseInt(obj.width, 10) : 500;
3744}
3745
3746S.html.prototype = {
3747
3748    /**
3749     * Appends this object to the DOM.
3750     *
3751     * @param   {HTMLElement}   body    The body element
3752     * @param   {Object}        dims    The current Shadowbox dimensions
3753     * @public
3754     */
3755    append: function(body, dims) {
3756        var div = document.createElement("div");
3757        div.id = this.id;
3758        div.className = "html"; // give special class to enable scrolling
3759        div.innerHTML = this.obj.content;
3760
3761        body.appendChild(div);
3762    },
3763
3764    /**
3765     * Removes this object from the DOM.
3766     *
3767     * @public
3768     */
3769    remove: function() {
3770        var el = get(this.id);
3771        if (el)
3772            remove(el);
3773    }
3774
3775}
3776/**
3777 * The iframe player for Shadowbox.
3778 */
3779
3780/**
3781 * Constructor. The iframe player class for Shadowbox.
3782 *
3783 * @constructor
3784 * @param   {Object}    obj     The content object
3785 * @param   {String}    id      The player id
3786 * @public
3787 */
3788S.iframe = function(obj, id) {
3789    this.obj = obj;
3790    this.id = id;
3791
3792    // height/width default to full viewport height/width
3793    var overlay = get("sb-overlay");
3794    this.height = obj.height ? parseInt(obj.height, 10) : overlay.offsetHeight;
3795    this.width = obj.width ? parseInt(obj.width, 10) : overlay.offsetWidth;
3796}
3797
3798S.iframe.prototype = {
3799
3800    /**
3801     * Appends this iframe to the DOM.
3802     *
3803     * @param   {HTMLElement}   body    The body element
3804     * @param   {Object}        dims    The current Shadowbox dimensions
3805     * @public
3806     */
3807    append: function(body, dims) {
3808        var html = '<iframe id="' + this.id + '" name="' + this.id + '" height="100%" ' +
3809            'width="100%" frameborder="0" marginwidth="0" marginheight="0" ' +
3810            'style="visibility:hidden" onload="this.style.visibility=\'visible\'" ' +
3811            'scrolling="auto"';
3812
3813        if (S.isIE) {
3814            // prevent brief whiteout while loading iframe source
3815            html += ' allowtransparency="true"';
3816
3817            // prevent "secure content" warning for https on IE6
3818            // see http://www.zachleat.com/web/2007/04/24/adventures-in-i-frame-shims-or-how-i-learned-to-love-the-bomb/
3819            if (S.isIE6)
3820                html += ' src="javascript:false;document.write(\'\');"';
3821        }
3822
3823        html += '></iframe>';
3824
3825        // use innerHTML method of insertion here instead of appendChild
3826        // because IE renders frameborder otherwise
3827        body.innerHTML = html;
3828    },
3829
3830    /**
3831     * Removes this iframe from the DOM.
3832     *
3833     * @public
3834     */
3835    remove: function() {
3836        var el = get(this.id);
3837        if (el) {
3838            remove(el);
3839            if (S.isGecko)
3840                delete window.frames[this.id]; // needed for Firefox
3841        }
3842    },
3843
3844    /**
3845     * An optional callback function to process after this content has been loaded.
3846     *
3847     * @public
3848     */
3849    onLoad: function() {
3850        var win = S.isIE ? get(this.id).contentWindow : window.frames[this.id];
3851        win.location.href = this.obj.content;
3852    }
3853
3854}
3855/**
3856 * The image player for Shadowbox.
3857 */
3858
3859/**
3860 * Resource used to preload images. It's class-level so that when a new image is requested,
3861 * the same resource can be reassigned, cancelling the original's callback.
3862 *
3863 * @type    {Image}
3864 * @private
3865 */
3866var pre,
3867
3868/**
3869 * The id to use for the drag proxy element.
3870 *
3871 * @type    {String}
3872 * @private
3873 */
3874proxyId = "sb-drag-proxy",
3875
3876/**
3877 * Keeps track of 4 floating values (x, y, startx, & starty) that are used in the drag calculations.
3878 *
3879 * @type    {Object}
3880 * @private
3881 */
3882dragData,
3883
3884/**
3885 * The transparent element that is used to listen for drag events.
3886 *
3887 * @type    {HTMLElement}
3888 * @private
3889 */
3890dragProxy,
3891
3892/**
3893 * The draggable element.
3894 *
3895 * @type    {HTMLElement}
3896 * @private
3897 */
3898dragTarget;
3899
3900/**
3901 * Resets the class drag variable.
3902 *
3903 * @private
3904 */
3905function resetDrag() {
3906    dragData = {
3907        x:      0,
3908        y:      0,
3909        startX: null,
3910        startY: null
3911    };
3912}
3913
3914/**
3915 * Updates the drag proxy dimensions.
3916 *
3917 * @private
3918 */
3919function updateProxy() {
3920    var dims = S.dimensions;
3921    apply(dragProxy.style, {
3922        height: dims.innerHeight + "px",
3923        width: dims.innerWidth + "px"
3924    });
3925}
3926
3927/**
3928 * Enables a transparent drag layer on top of images.
3929 *
3930 * @private
3931 */
3932function enableDrag() {
3933    resetDrag();
3934
3935    // add transparent proxy layer to prevent browser dragging of actual image
3936    var style = [
3937        "position:absolute",
3938        "cursor:" + (S.isGecko ? "-moz-grab" : "move"),
3939        "background-color:" + (S.isIE ? "#fff;filter:alpha(opacity=0)" : "transparent")
3940    ].join(";");
3941    S.appendHTML(S.skin.body, '<div id="' + proxyId + '" style="' + style + '"></div>');
3942
3943    dragProxy = get(proxyId);
3944    updateProxy();
3945
3946    addEvent(dragProxy, "mousedown", startDrag);
3947}
3948
3949/**
3950 * Disables the drag layer.
3951 *
3952 * @private
3953 */
3954function disableDrag() {
3955    if (dragProxy) {
3956        removeEvent(dragProxy, "mousedown", startDrag);
3957        remove(dragProxy);
3958        dragProxy = null;
3959    }
3960
3961    dragTarget = null;
3962}
3963
3964/**
3965 * Sets up a drag listener on the document.
3966 *
3967 * @param   {Event}     e   The mousedown event
3968 * @private
3969 */
3970function startDrag(e) {
3971    // prevent browser dragging
3972    preventDefault(e);
3973
3974    var xy = getPageXY(e);
3975    dragData.startX = xy[0];
3976    dragData.startY = xy[1];
3977
3978    dragTarget = get(S.player.id);
3979
3980    addEvent(document, "mousemove", positionDrag);
3981    addEvent(document, "mouseup", endDrag);
3982
3983    if (S.isGecko)
3984        dragProxy.style.cursor = "-moz-grabbing";
3985}
3986
3987/**
3988 * Positions an oversized image on drag.
3989 *
3990 * @param   {Event}     e   The mousemove event
3991 * @private
3992 */
3993function positionDrag(e) {
3994    var player = S.player,
3995        dims = S.dimensions,
3996        xy = getPageXY(e);
3997
3998    var moveX = xy[0] - dragData.startX;
3999    dragData.startX += moveX;
4000    dragData.x = Math.max(Math.min(0, dragData.x + moveX), dims.innerWidth - player.width);
4001
4002    var moveY = xy[1] - dragData.startY;
4003    dragData.startY += moveY;
4004    dragData.y = Math.max(Math.min(0, dragData.y + moveY), dims.innerHeight - player.height);
4005
4006    apply(dragTarget.style, {
4007        left: dragData.x + "px",
4008        top: dragData.y + "px"
4009    });
4010}
4011
4012/**
4013 * Removes the drag listener from the document.
4014 *
4015 * @private
4016 */
4017function endDrag() {
4018    removeEvent(document, "mousemove", positionDrag);
4019    removeEvent(document, "mouseup", endDrag);
4020
4021    if (S.isGecko)
4022        dragProxy.style.cursor = "-moz-grab";
4023}
4024
4025/**
4026 * Constructor. The image player class for Shadowbox.
4027 *
4028 * @constructor
4029 * @param   {Object}    obj     The content object
4030 * @param   {String}    id      The player id
4031 * @public
4032 */
4033S.img = function(obj, id) {
4034    this.obj = obj;
4035    this.id = id;
4036
4037    // preload the image
4038    this.ready = false;
4039    var self = this;
4040    pre = new Image();
4041    pre.onload = function() {
4042        // height/width defaults to image height/width
4043        self.height = obj.height ? parseInt(obj.height, 10) : pre.height;
4044        self.width = obj.width ? parseInt(obj.width, 10) : pre.width;
4045
4046        // ready to go
4047        self.ready = true;
4048
4049        // clean up to prevent memory leak in IE
4050        pre.onload = null;
4051        pre = null;
4052    }
4053    pre.src = obj.content;
4054}
4055
4056S.img.ext = ["bmp", "gif", "jpg", "jpeg", "png"];
4057
4058S.img.prototype = {
4059
4060    /**
4061     * Appends this image to the document.
4062     *
4063     * @param   {HTMLElement}   body    The body element
4064     * @param   {Object}        dims    The current Shadowbox dimensions
4065     * @public
4066     */
4067    append: function(body, dims) {
4068        var img = document.createElement("img");
4069        img.id = this.id;
4070        img.src = this.obj.content;
4071        img.style.position = "absolute";
4072
4073        var height, width;
4074        if (dims.oversized && S.options.handleOversize == "resize") {
4075            height = dims.innerHeight;
4076            width = dims.innerWidth;
4077        } else {
4078            height = this.height;
4079            width = this.width;
4080        }
4081
4082        // need to use setAttribute here for IE's sake
4083        img.setAttribute("height", height);
4084        img.setAttribute("width", width);
4085
4086        body.appendChild(img);
4087    },
4088
4089    /**
4090     * Removes this image from the document.
4091     *
4092     * @public
4093     */
4094    remove: function() {
4095        var el = get(this.id);
4096        if (el)
4097            remove(el);
4098
4099        disableDrag();
4100
4101        // prevent old image requests from loading
4102        if (pre) {
4103            pre.onload = null;
4104            pre = null;
4105        }
4106    },
4107
4108    /**
4109     * An optional callback function to process after this content has been
4110     * loaded.
4111     *
4112     * @public
4113     */
4114    onLoad: function() {
4115        var dims = S.dimensions;
4116
4117        // listen for drag when image is oversized
4118        if (dims.oversized && S.options.handleOversize == "drag")
4119            enableDrag();
4120    },
4121
4122    /**
4123     * Called when the window is resized.
4124     *
4125     * @public
4126     */
4127    onWindowResize: function() {
4128        var dims = S.dimensions;
4129
4130        switch (S.options.handleOversize) {
4131        case "resize":
4132            var el = get(this.id);
4133            el.height = dims.innerHeight;
4134            el.width = dims.innerWidth;
4135            break;
4136        case "drag":
4137            if (dragTarget) {
4138                var top = parseInt(S.getStyle(dragTarget, "top")),
4139                    left = parseInt(S.getStyle(dragTarget, "left"));
4140
4141                // fix positioning when viewport is enlarged
4142                if (top + this.height < dims.innerHeight)
4143                    dragTarget.style.top = dims.innerHeight - this.height + "px";
4144                if (left + this.width < dims.innerWidth)
4145                    dragTarget.style.left = dims.innerWidth - this.width + "px";
4146
4147                updateProxy();
4148            }
4149            break;
4150        }
4151    }
4152
4153}
4154/**
4155 * The QuickTime player for Shadowbox.
4156 */
4157
4158/**
4159 * The height (in pixels) of the QuickTime controller.
4160 *
4161 * @type    {Number}
4162 * @private
4163 */
4164var qtControllerHeight = 16;
4165
4166/**
4167 * Constructor. The QuickTime player class for Shadowbox.
4168 *
4169 * @param   {Object}    obj     The content object
4170 * @param   {String}    id      The player id
4171 * @public
4172 */
4173S.qt = function(obj, id) {
4174    this.obj = obj;
4175    this.id = id;
4176
4177    // height/width default to 300 pixels
4178    this.height = obj.height ? parseInt(obj.height, 10) : 300;
4179    if (S.options.showMovieControls)
4180        this.height += qtControllerHeight;
4181    this.width = obj.width ? parseInt(obj.width, 10) : 300;
4182}
4183
4184S.qt.ext = ["dv", "mov", "moov", "movie", "mp4", "avi", "mpg", "mpeg"];
4185
4186S.qt.prototype = {
4187
4188    /**
4189     * Appends this movie to the document.
4190     *
4191     * @param   {HTMLElement}   body    The body element
4192     * @param   {Object}        dims    The current Shadowbox dimensions
4193     * @public
4194     */
4195    append: function(body, dims) {
4196        var opt = S.options,
4197            autoplay = String(opt.autoplayMovies),
4198            controls = String(opt.showMovieControls);
4199
4200        var html = "<object",
4201            movie = {
4202                id:         this.id,
4203                name:       this.id,
4204                height:     this.height,
4205                width:      this.width,
4206                kioskmode:  "true"
4207            };
4208
4209        if (S.isIE) {
4210            movie.classid = "clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B";
4211            movie.codebase = "http://www.apple.com/qtactivex/qtplugin.cab#ver
4211sion=6,0,2,0";
4212        } else {
4213            movie.type = "video/quicktime";
4214            movie.data = this.obj.content;
4215        }
4216
4217        for (var m in movie)
4218            html += " " + m + '="' + movie[m] + '"';
4219        html += ">";
4220
4221        var params = {
4222            src:        this.obj.content,
4223            scale:      "aspect",
4224            controller: controls,
4225            autoplay:   autoplay
4226        };
4227
4228        for (var p in params)
4229            html += '<param name="' + p + '" value="' + params[p] + '">';
4230        html += "</object>";
4231
4232        body.innerHTML = html;
4233    },
4234
4235    /**
4236     * Removes this movie from the DOM.
4237     *
4238     * @public
4239     */
4240    remove: function() {
4241        try {
4242            document[this.id].Stop(); // stop QT video stream
4243        } catch(e) {}
4244
4245        var el = get(this.id);
4246        if (el)
4247            remove(el);
4248    }
4249
4250}
4251/**
4252 * The SWF player for Shadowbox.
4253 */
4254
4255/**
4256 * Constructor. The SWF movie player class for Shadowbox.
4257 *
4258 * @constructor
4259 * @param   {Object}    obj     The content object
4260 * @param   {String}    id      The player id
4261 * @public
4262 */
4263S.swf = function(obj, id) {
4264    this.obj = obj;
4265    this.id = id;
4266
4267    // height/width default to 300 pixels
4268    this.height = obj.height ? parseInt(obj.height, 10) : 300;
4269    this.width = obj.width ? parseInt(obj.width, 10) : 300;
4270}
4271
4272S.swf.ext = ["swf"];
4273
4274S.swf.prototype = {
4275
4276    /**
4277     * Appends this swf to the document.
4278     *
4279     * @param   {HTMLElement}   body    The body element
4280     * @param   {Object}        dims    The current Shadowbox dimensions
4281     * @public
4282     */
4283    append: function(body, dims){
4284        // append temporary content element to replace
4285        var tmp = document.createElement("div");
4286        tmp.id = this.id;
4287        body.appendChild(tmp);
4288
4289        var height = dims.innerHeight,
4290            width = dims.innerWidth,
4291            swf = this.obj.content,
4292            version = S.options.flashVersion,
4293            express = S.path + "expressInstall.swf",
4294            flashvars = S.options.flashVars,
4295            params = S.options.flashParams;
4296
4297        S.flash.embedSWF(swf, this.id, width, height, version, express, flashvars, params);
4298    },
4299
4300    /**
4301     * Removes this swf from the document.
4302     *
4303     * @public
4304     */
4305    remove: function() {
4306        // call express install callback here in case express install is
4307        // active and user has not selected anything
4308        S.flash.expressInstallCallback();
4309        S.flash.removeSWF(this.id);
4310    },
4311
4312    /**
4313     * Called when the window is resized.
4314     *
4315     * @public
4316     */
4317    onWindowResize: function() {
4318        var dims = S.dimensions,
4319            el = get(this.id);
4320        el.height = dims.innerHeight;
4321        el.width = dims.innerWidth;
4322    }
4323
4324}
4325/**
4326 * The WMP player for Shadowbox.
4327 */
4328
4329/**
4330 * The height (in pixels) of the Windows Media Player controller.
4331 *
4332 * @type    {Number}
4333 * @private
4334 */
4335var wmpControllerHeight = (S.isIE ? 70 : 45);
4336
4337/**
4338 * Constructor. The Windows Media player class for Shadowbox.
4339 *
4340 * @param   {Object}    obj     The content object
4341 * @param   {String}    id      The player id
4342 * @public
4343 */
4344S.wmp = function(obj, id) {
4345    this.obj = obj;
4346    this.id = id;
4347
4348    // height/width default to 300 pixels
4349    this.height = obj.height ? parseInt(obj.height, 10) : 300;
4350    if (S.options.showMovieControls)
4351        this.height += wmpControllerHeight;
4352    this.width = obj.width ? parseInt(obj.width, 10) : 300;
4353}
4354
4355S.wmp.ext = ["asf", "avi", "mpg", "mpeg", "wm", "wmv"];
4356
4357S.wmp.prototype = {
4358
4359    /**
4360     * Appends this movie to the document.
4361     *
4362     * @param   {HTMLElement}   body    The body element
4363     * @param   {Object}        dims    The current Shadowbox dimensions
4364     * @public
4365     */
4366    append: function(body, dims) {
4367        var opt = S.options,
4368            autoplay = opt.autoplayMovies ? 1 : 0;
4369
4370        var movie = '<object id="' + this.id +
4371            '" name="' + this.id +
4372            '" height="' + this.height +
4373            '" width="' + this.width + '"',
4374            params = { autostart: opt.autoplayMovies ? 1 : 0 };
4375
4376        if (S.isIE) {
4377            // movie += ' type="application/x-oleobject"';
4378            movie += ' classid="clsid:6BF52A52-394A-11d3-B153-00C04F79FAA6"';
4379            params.url = this.obj.content;
4380            params.uimode = opt.showMovieControls ? "full" : "none";
4381        } else {
4382            movie += ' type="video/x-ms-wmv"';
4383            movie += ' data="' + this.obj.content + '"'
4384            params.showcontrols = opt.showMovieControls ? 1 : 0;
4385        }
4386
4387        movie += ">";
4388
4389        for (var p in params)
4390            movie += '<param name="' + p + '" value="' + params[p] + '">';
4391
4392        movie += "</object>";
4393
4394        body.innerHTML = movie;
4395    },
4396
4397    /**
4398     * Removes this movie from the document.
4399     *
4400     * @return  void
4401     * @public
4402     */
4403    remove: function(){
4404        if (S.isIE) {
4405            try {
4406                window[this.id].controls.stop(); // stop the movie
4407                window[this.id].URL = "movie" + now() + ".wmv"; // force player refresh
4408                window[this.id] = function(){}; // remove from window object
4409            } catch(e) {}
4410        }
4411
4412        var el = get(this.id);
4413        if (el) {
4414            // using setTimeout here prevents browser crashes with WMP
4415            setTimeout(function() {
4416                remove(el);
4417            }, 10);
4418        }
4419    }
4420
4421}
4422/**
4423 * Keeps track of whether or not the overlay is activated.
4424 *
4425 * @type    {Boolean}
4426 * @private
4427 */
4428var overlayOn = false,
4429
4430/**
4431 * A cache of elements that are troublesome for modal overlays.
4432 *
4433 * @type    {Array}
4434 * @private
4435 */
4436visibilityCache = [],
4437
4438/**
4439 * Id's of elements that need transparent PNG support.
4440 *
4441 * @type    {Array}
4442 * @private
4443 */
4444pngIds = [
4445    "sb-nav-close",
4446    "sb-nav-next",
4447    "sb-nav-play",
4448    "sb-nav-pause",
4449    "sb-nav-previous"
4450],
4451
4452/**
4453 * The container element.
4454 *
4455 * @type    {HTMLElement}
4456 * @private
4457 */
4458container,
4459
4460/**
4461 * The overlay element.
4462 *
4463 * @type    {HTMLElement}
4464 * @private
4465 */
4466overlay,
4467
4468/**
4469 * The wrapper element.
4470 *
4471 * @type    {HTMLElement}
4472 * @private
4473 */
4474wrapper,
4475
4476/**
4477 * True if the window resize event is allowed to fire.
4478 *
4479 * @type    {Boolean}
4480 * @private
4481 */
4482doWindowResize = true;
4483
4484/**
4485 * Animates the given property of el to the given value over a specified duration. If a
4486 * callback is provided, it will be called when the animation is finished.
4487 *
4488 * @param   {HTMLElement}   el
4489 * @param   {String}        property
4490 * @param   {mixed}         to
4491 * @param   {Number}        duration
4492 * @param   {Function}      callback
4493 * @private
4494 */
4495function animate(el, property, to, duration, callback) {
4496    var isOpacity = (property == "opacity"),
4497    anim = isOpacity ? S.setOpacity : function(el, value) {
4498        // default unit is px for properties other than opacity
4499        el.style[property] = "" +
4500            value + "px";
4501    };
4502
4503    if (duration == 0 || (!isOpacity && !S.options.animate) || (isOpacity && !S.options.animateFade)) {
4504        anim(el, to);
4505        if (callback)
4506            callback();
4507        return;
4508    }
4509
4510    var from = parseFloat(S.getStyle(el, property)) || 0;
4511    var delta = to - from;
4512    if (delta == 0) {
4513        if (callback)
4514            callback();
4515        return; // nothing to animate
4516    }
4517
4518    duration *= 1000; // convert to milliseconds
4519
4520    var begin = now(),
4521        ease = S.ease,
4522        end = begin + duration,
4523        time;
4524
4525    var interval = setInterval(function() {
4526        time = now();
4527        if (time >= end) {
4528            clearInterval(interval);
4529            interval = null;
4530            anim(el, to);
4531            if (callback)
4532                callback();
4533        } else {
4534            anim(el, from + ease((time - begin) / duration) * delta);
4535        }
4536    }, 10); // 10 ms interval is minimum on WebKit
4537}
4538
4539/**
4540 * Sets the size of the container element.
4541 *
4542 * @private
4543 */
4544function setSize() {
4545    container.style.height = S.getWindowSize("Height") + "px";
4546    container.style.width = S.getWindowSize("Width") + "px";
4547}
4548
4549/**
4550 * Sets the top of the container element. This is only necessary in browsers that
4551 * don't support fixed positioning, such as IE6.
4552 *
4553 * @private
4554 */
4555function setPosition() {
4556    container.style.top = document.documentElement.scrollTop + "px";
4557    container.style.left = document.documentElement.scrollLeft + "px";
4558}
4559
4560/**
4561 * Toggles the visibility of elements that are troublesome for overlays.
4562 *
4563 * @param   {Boolean}   on  True to make visible, false to hide
4564 * @private
4565 */
4566function toggleTroubleElements(on) {
4567    if (on) {
4568        each(visibilityCache, function(i, el){
4569            el[0].style.visibility = el[1] || '';
4570        });
4571    } else {
4572        visibilityCache = [];
4573        each(S.options.troubleElements, function(i, tag) {
4574            each(document.getElementsByTagName(tag), function(j, el) {
4575                visibilityCache.push([el, el.style.visibility]);
4576                el.style.visibility = "hidden";
4577            });
4578        });
4579    }
4580}
4581
4582/**
4583 * Toggles the display of the nav control with the given id.
4584 *
4585 * @param   {String}    id      The id of the navigation control
4586 * @param   {Boolean}   on      True to toggle on, false to toggle off
4587 * @private
4588 */
4589function toggleNav(id, on) {
4590    var el = get("sb-nav-" + id);
4591    if (el)
4592        el.style.display = on ? "" : "none";
4593}
4594
4595/**
4596 * Toggles the visibility of the loading layer.
4597 *
4598 * @param   {Boolean}   on          True to toggle on, false to toggle off
4599 * @param   {Function}  callback    The callback to use when finished
4600 * @private
4601 */
4602function toggleLoading(on, callback) {
4603    var loading = get("sb-loading"),
4604        playerName = S.getCurrent().player,
4605        anim = (playerName == "img" || playerName == "html"); // fade on images & html
4606
4607    if (on) {
4608        S.setOpacity(loading, 0);
4609        loading.style.display = "block";
4610
4611        var wrapped = function() {
4612            S.clearOpacity(loading);
4613            if (callback)
4614                callback();
4615        }
4616
4617        if (anim) {
4618            animate(loading, "opacity", 1, S.options.fadeDuration, wrapped);
4619        } else {
4620            wrapped();
4621        }
4622    } else {
4623        var wrapped = function() {
4624            loading.style.display = "none";
4625            S.clearOpacity(loading);
4626            if (callback)
4627                callback();
4628        }
4629
4630        if (anim) {
4631            animate(loading, "opacity", 0, S.options.fadeDuration, wrapped);
4632        } else {
4633            wrapped();
4634        }
4635    }
4636}
4637
4638/**
4639 * Builds the content for the title and information bars.
4640 *
4641 * @param   {Function}  callback    The callback to use when finished
4642 * @private
4643 */
4644function buildBars(callback) {
4645    var obj = S.getCurrent();
4646
4647    get("sb-title-inner").innerHTML = obj.title || "";
4648
4649    // build the nav
4650    var close, next, play, pause, previous;
4651    if (S.options.displayNav) {
4652        close = true;
4653        var len = S.gallery.length;
4654        if (len > 1) {
4655            if (S.options.continuous) {
4656                next = previous = true;
4657            } else {
4658                next = (len - 1) > S.current; // not last in gallery, show next
4659                previous = S.current > 0; // not first in gallery, show previous
4660            }
4661        }
4662        // in a slideshow?
4663        if (S.options.slideshowDelay > 0 && S.hasNext()) {
4664            pause = !S.isPaused();
4665            play = !pause;
4666        }
4667    } else {
4668        close = next = play = pause = previous = false;
4669    }
4670    toggleNav("close", close);
4671    toggleNav("next", next);
4672    toggleNav("play", play);
4673    toggleNav("pause", pause);
4674    toggleNav("previous", previous);
4675
4676    // build the counter
4677    var counter = "";
4678    if (S.options.displayCounter && S.gallery.length > 1) {
4679        var len = S.gallery.length;
4680        if (S.options.counterType == "skip") {
4681            // limit the counter?
4682            var i = 0,
4683                end = len,
4684                limit = parseInt(S.options.counterLimit) || 0;
4685
4686            if (limit < len && limit > 2) { // support large galleries
4687                var h = Math.floor(limit / 2);
4688                i = S.current - h;
4689                if (i < 0)
4690                    i += len;
4691                end = S.current + (limit - h);
4692                if (end > len)
4693                    end -= len;
4694            }
4695
4696            while (i != end) {
4697                if (i == len)
4698                    i = 0;
4699                counter += '<a onclick="Shadowbox.change(' + i + ');"'
4700                if (i == S.current)
4701                    counter += ' class="sb-counter-current"';
4702                counter += ">" + (++i) + "</a>";
4703            }
4704        } else {
4705            counter = [S.current + 1, S.lang.of, len].join(' ');
4706        }
4707    }
4708
4709    get("sb-counter").innerHTML = counter;
4710
4711    callback();
4712}
4713
4714/**
4715 * Shows the title and info bars.
4716 *
4717 * @param   {Function}  callback    The callback to use when finished
4718 * @private
4719 */
4720function showBars(callback) {
4721    var titleInner = get("sb-title-inner"),
4722        infoInner = get("sb-info-inner"),
4723        duration = 0.35;
4724
4725    // clear visibility before animating into view
4726    titleInner.style.visibility = infoInner.style.visibility = "";
4727
4728    if (titleInner.innerHTML != "")
4729        animate(titleInner, "marginTop", 0, duration);
4730    animate(infoInner, "marginTop", 0, duration, callback);
4731}
4732
4733/**
4734 * Hides the title and info bars.
4735 *
4736 * @param   {Boolean}   anim        True to animate the transition
4737 * @param   {Function}  callback    The callback to use when finished
4738 * @private
4739 */
4740function hideBars(anim, callback) {
4741    var title = get("sb-title"),
4742        info = get("sb-info"),
4743        titleHeight = title.offsetHeight,
4744        infoHeight = info.offsetHeight,
4745        titleInner = get("sb-title-inner"),
4746        infoInner = get("sb-info-inner"),
4747        duration = (anim ? 0.35 : 0);
4748
4749    animate(titleInner, "marginTop", titleHeight, duration);
4750    animate(infoInner, "marginTop", infoHeight * -1, duration, function() {
4751        titleInner.style.visibility = infoInner.style.visibility = "hidden";
4752        callback();
4753    });
4754}
4755
4756/**
4757 * Adjusts the height of #sb-wrapper-inner and centers #sb-wrapper vertically
4758 * in the viewport.
4759 *
4760 * @param   {Number}    height      The height (in pixels)
4761 * @param   {Number}    top         The top (in pixels)
4762 * @param   {Boolean}   anim        True to animate the transition
4763 * @param   {Function}  callback    The callback to use when finished
4764 * @private
4765 */
4766function adjustHeight(height, top, anim, callback) {
4767    var wrapperInner = get("sb-wrapper-inner"),
4768        duration = (anim ? S.options.resizeDuration : 0);
4769
4770    animate(wrapper, "top", top, duration);
4771    animate(wrapperInner, "height", height, duration, callback);
4772}
4773
4774/**
4775 * Adjusts the width and left position of #sb-wrapper.
4776 *
4777 * @param   {Number}    width       The width (in pixels)
4778 * @param   {Number}    left        The left (in pixels)
4779 * @param   {Boolean}   anim        True to animate the transition
4780 * @param   {Function}  callback    The callback to use when finished
4781 * @private
4782 */
4783function adjustWidth(width, left, anim, callback) {
4784    var duration = (anim ? S.options.resizeDuration : 0);
4785
4786    animate(wrapper, "left", left, duration);
4787    animate(wrapper, "width", width, duration, callback);
4788}
4789
4790/**
4791 * Calculates the dimensions for Shadowbox.
4792 *
4793 * @param   {Number}    height      The content height
4794 * @param   {Number}    width       The content width
4795 * @return  {Object}                The new dimensions object
4796 * @private
4797 */
4798function setDimensions(height, width) {
4799    var bodyInner = get("sb-body-inner"),
4800        height = parseInt(height),
4801        width = parseInt(width),
4802        topBottom = wrapper.offsetHeight - bodyInner.offsetHeight,
4803        leftRight = wrapper.offsetWidth - bodyInner.offsetWidth,
4804
4805        // overlay should provide proper window dimensions here
4806        maxHeight = overlay.offsetHeight,
4807        maxWidth = overlay.offsetWidth,
4808
4809        // default to the default viewport padding
4810        padding = parseInt(S.options.viewportPadding) || 20,
4811
4812        // only preserve aspect ratio if there is something to display and
4813        // it's not draggable
4814        preserveAspect = (S.player && S.options.handleOversize != "drag");
4815
4816    return S.setDimensions(height, width, maxHeight, maxWidth, topBottom, leftRight, padding, preserveAspect);
4817}
4818
4819/**
4820 * The Shadowbox.skin object.
4821 *
4822 * @type    {Object}
4823 * @public
4824 */
4825var K = {};
4826
4827/**
4828 * The HTML markup to use.
4829 *
4830 * @type    {String}
4831 * @public
4832 */
4833K.markup = "" +
4834'<div id="sb-container">' +
4835    '<div id="sb-overlay"></div>' +
4836    '<div id="sb-wrapper">' +
4837        '<div id="sb-title">' +
4838            '<div id="sb-title-inner"></div>' +
4839        '</div>' +
4840        '<div id="sb-wrapper-inner">' +
4841            '<div id="sb-body">' +
4842                '<div id="sb-body-inner"></div>' +
4843                '<div id="sb-loading">' +
4844                    '<div id="sb-loading-inner"><span>{loading}</span></div>' +
4845                '</div>' +
4846            '</div>' +
4847        '</div>' +
4848        '<div id="sb-info">' +
4849            '<div id="sb-info-inner">' +
4850                '<div id="sb-counter"></div>' +
4851                '<div id="sb-nav">' +
4852                    '<a id="sb-nav-close" title="{close}" onclick="Shadowbox.close()"></a>' +
4853                    '<a id="sb-nav-next" title="{next}" onclick="Shadowbox.next()"></a>' +
4854                    '<a id="sb-nav-play" title="{play}" onclick="Shadowbox.play()"></a>' +
4855                    '<a id="sb-nav-pause" title="{pause}" onclick="Shadowbox.pause()"></a>' +
4856                    '<a id="sb-nav-previous" title="{previous}" onclick="Shadowbox.previous()"></a>' +
4857                '</div>' +
4858            '</div>' +
4859        '</div>' +
4860    '</div>' +
4861'</div>';
4862
4863/**
4864 * Various options that control the behavior of Shadowbox' skin.
4865 *
4866 * @type    {Object}
4867 * @public
4868 */
4869K.options = {
4870
4871    /**
4872     * The sequence of the resizing animations. "hw" will resize height, then width. "wh" resizes
4873     * width, then height. "sync" resizes both simultaneously.
4874     *
4875     * @type    {String}
4876     */
4877    animSequence: "sync",
4878
4879    /**
4880     * The limit to the number of counter links that are displayed in a "skip"-style counter.
4881     *
4882     * @type    {Number}
4883     */
4884    counterLimit: 10,
4885
4886    /**
4887     * The counter type to use. May be either "default" or "skip". A skip counter displays a
4888     * link for each object in the gallery.
4889     *
4890     * @type    {String}
4891     */
4892    counterType: "default",
4893
4894    /**
4895     * True to display the gallery counter.
4896     *
4897     * @type    {Boolean}
4898     */
4899    displayCounter: true,
4900
4901    /**
4902     * True to show the navigation controls.
4903     *
4904     * @type    {Boolean}
4905     */
4906    displayNav: true,
4907
4908    /**
4909     * The duration (in seconds) of opacity animations.
4910     *
4911     * @type    {Number}
4912     */
4913    fadeDuration: 0.35,
4914
4915    /**
4916     * The initial height (in pixels).
4917     *
4918     * @type    {Number}
4919     */
4920    initialHeight: 160,
4921
4922    /**
4923     * The initial width (in pixels).
4924     *
4925     * @type    {Number}
4926     */
4927    initialWidth: 320,
4928
4929    /**
4930     * True to trigger Shadowbox.close when the overlay is clicked.
4931     *
4932     * @type    {Boolean}
4933     */
4934    modal: false,
4935
4936    /**
4937     * The color (in hex) to use for the overlay.
4938     *
4939     * @type    {String}
4940     */
4941    overlayColor: "#000",
4942
4943    /**
4944     * The opacity to use for the overlay.
4945     *
4946     * @type    {Number}
4947     */
4948    overlayOpacity: 0.5,
4949
4950    /**
4951     * The duration (in seconds) to use for resizing animations.
4952     *
4953     * @type    {Number}
4954     */
4955    resizeDuration: 0.35,
4956
4957    /**
4958     * True to show the overlay, false to hide it.
4959     *
4960     * @type    {Boolean}
4961     */
4962    showOverlay: true,
4963
4964    /**
4965     * Names of elements that should be hidden when the overlay is enabled.
4966     *
4967     * @type    {String}
4968     */
4969    troubleElements: ["select", "object", "embed", "canvas"]
4970
4971};
4972
4973/**
4974 * Initialization function. Called immediately after this skin's markup has been
4975 * appended to the document with all of the necessary language replacements done.
4976 *
4977 * @public
4978 */
4979K.init = function() {
4980    S.appendHTML(document.body, sprintf(K.markup, S.lang));
4981
4982    K.body = get("sb-body-inner");
4983
4984    // cache oft-used elements
4985    container = get("sb-container");
4986    overlay = get("sb-overlay");
4987    wrapper = get("sb-wrapper");
4988
4989    // use absolute positioning in browsers that don't support fixed
4990    if (!supportsFixed)
4991        container.style.position = "absolute";
4992
4993    if (!supportsOpacity) {
4994        // support transparent PNG's via AlphaImageLoader
4995        var el, m, re = /url\("(.*\.png)"\)/;
4996        each(pngIds, function(i, id) {
4997            el = get(id);
4998            if (el) {
4999                m = S.getStyle(el, "backgroundImage").match(re);
5000                if (m) {
5001                    el.style.backgroundImage = "none";
5002                    el.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true,src=" +
5003                        m[1] + ",sizingMethod=scale);";
5004                }
5005            }
5006        });
5007    }
5008
5009    // add window resize event handler, use 10 ms buffer to prevent jerky resizing
5010    var timer;
5011    addEvent(window, "resize", function() {
5012        if (timer) {
5013            clearTimeout(timer);
5014            timer = null;
5015        }
5016
5017        if (open)
5018            timer = setTimeout(K.onWindowResize, 10);
5019    });
5020}
5021
5022/**
5023 * Called when Shadowbox opens.
5024 *
5025 * @param   {Object}    obj         The object to open
5026 * @param   {Function}  callback    The callback to use when finished
5027 * @public
5028 */
5029K.onOpen = function(obj, callback) {
5030    // prevent window resize events from firing until we're finished
5031    doWindowResize = false;
5032
5033    container.style.display = "block";
5034
5035    setSize();
5036
5037    var dims = setDimensions(S.options.initialHeight, S.options.initialWidth);
5038    adjustHeight(dims.innerHeight, dims.top);
5039    adjustWidth(dims.width, dims.left);
5040
5041    if (S.options.showOverlay) {
5042        overlay.style.backgroundColor = S.options.overlayColor;
5043        S.setOpacity(overlay, 0);
5044
5045        if (!S.options.modal)
5046            addEvent(overlay, "click", S.close);
5047
5048        overlayOn = true;
5049    }
5050
5051    if (!supportsFixed) {
5052        setPosition();
5053        addEvent(window, "scroll", setPosition);
5054    }
5055
5056    toggleTroubleElements();
5057    container.style.visibility = "visible";
5058
5059    if (overlayOn) {
5060        animate(overlay, "opacity", S.options.overlayOpacity, S.options.fadeDuration, callback);
5061    } else {
5062        callback();
5063    }
5064}
5065
5066/**
5067 * Called when a new object is being loaded.
5068 *
5069 * @param   {Boolean}   changing    True if the content is changing from some
5070 *                                  previous object
5071 * @param   {Function}  callback    The callback to use when finished
5072 * @public
5073 */
5074K.onLoad = function(changing, callback) {
5075    toggleLoading(true);
5076
5077    // make sure the body doesn't have any children
5078    while (K.body.firstChild)
5079        remove(K.body.firstChild);
5080
5081    hideBars(changing, function() {
5082        if (!open)
5083            return;
5084
5085        if (!changing)
5086            wrapper.style.visibility = "visible";
5087
5088        buildBars(callback);
5089    });
5090}
5091
5092/**
5093 * Called when the content is ready to be loaded (e.g. when the image has finished
5094 * loading). Should resize the content box and make any other necessary adjustments.
5095 *
5096 * @param   {Function}  callback    The callback to use when finished
5097 * @public
5098 */
5099K.onReady = function(callback) {
5100    if (!open)
5101        return;
5102
5103    var player = S.player,
5104        dims = setDimensions(player.height, player.width);
5105
5106    var wrapped = function() {
5107        showBars(callback);
5108    }
5109
5110    switch (S.options.animSequence) {
5111    case "hw":
5112        adjustHeight(dims.innerHeight, dims.top, true, function() {
5113            adjustWidth(dims.width, dims.left, true, wrapped);
5114        });
5115        break;
5116    case "wh":
5117        adjustWidth(dims.width, dims.left, true, function() {
5118            adjustHeight(dims.innerHeight, dims.top, true, wrapped);
5119        });
5120        break;
5121    default: // sync
5122        adjustWidth(dims.width, dims.left, true);
5123        adjustHeight(dims.innerHeight, dims.top, true, wrapped);
5124    }
5125}
5126
5127/**
5128 * Called when the content is loaded into the box and is ready to be displayed.
5129 *
5130 * @param   {Function}  callback    The callback to use when finished
5131 * @public
5132 */
5133K.onShow = function(callback) {
5134    toggleLoading(false, callback);
5135
5136    // re-enable window resize events
5137    doWindowResize = true;
5138}
5139
5140/**
5141 * Called in Shadowbox.close.
5142 *
5143 * @public
5144 */
5145K.onClose = function() {
5146    if (!supportsFixed)
5147        removeEvent(window, "scroll", setPosition);
5148
5149    removeEvent(overlay, "click", S.close);
5150
5151    wrapper.style.visibility = "hidden";
5152
5153    var callback = function() {
5154        container.style.visibility = "hidden";
5155        container.style.display = "none";
5156        toggleTroubleElements(true);
5157    }
5158
5159    if (overlayOn) {
5160        animate(overlay, "opacity", 0, S.options.fadeDuration, callback);
5161    } else {
5162        callback();
5163    }
5164}
5165
5166/**
5167 * Called in Shadowbox.play.
5168 *
5169 * @public
5170 */
5171K.onPlay = function() {
5172    toggleNav("play", false);
5173    toggleNav("pause", true);
5174}
5175
5176/**
5177 * Called in Shadowbox.pause.
5178 *
5179 * @public
5180 */
5181K.onPause = function() {
5182    toggleNav("pause", false);
5183    toggleNav("play", true);
5184}
5185
5186/**
5187 * Called when the window is resized.
5188 *
5189 * @public
5190 */
5191K.onWindowResize = function() {
5192    if (!doWindowResize)
5193        return;
5194
5195    setSize();
5196
5197    var player = S.player,
5198        dims = setDimensions(player.height, player.width);
5199
5200    // adjust width first to eliminate horizontal scroll bar
5201    adjustWidth(dims.width, dims.left);
5202    adjustHeight(dims.innerHeight, dims.top);
5203
5204    if (player.onWindowResize)
5205        player.onWindowResize();
5206}
5207
5208S.skin = K;
5209// expose
5210window['Shadowbox'] = S;
5211
5212})(window);

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.