PageSourceSearch

https://edicola.ilmattinoquotidiano.it/mattino/jscripts/shadow/shadowbox2.js

js ilmattinoquotidiano.it collected 2026-10-02 13:23:02 UTC 93,047 bytes, 2,540 lines download raw bytes

1/**
2 * A media-viewer script for web pages that allows content to be viewed without
3 * navigating away from the original linking page.
4 *
5 * This file is part of Shadowbox.
6 *
7 * Shadowbox is free software: you can redistribute it and/or modify it under
8 * the terms of the GNU Lesser General Public License as published by the Free
9 * Software Foundation, either version 3 of the License, or (at your option)
10 * any later version.
11 *
12 * Shadowbox is distributed in the hope that it will be useful, but WITHOUT ANY
13 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
15 * more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with Shadowbox. If not, see <http://www.gnu.org/licenses/>.
19 *
20 * @author      Michael J. I. Jackson <[email protected]>
21 * @copyright   2007 Michael J. I. Jackson
22 * @license     http://www.gnu.org/licenses/lgpl-3.0.txt GNU LGPL 3.0
23 * @version     SVN: $Id: shadowbox.js 75 2008-02-21 16:51:29Z mjijackson $
24 */
25
26if(typeof Shadowbox == 'undefined'){
27    throw 'Unable to load Shadowbox, no base library adapter found.';
28}
29
30/**
31 * The Shadowbox class. Used to display different media on a web page using a
32 * Lightbox-like effect.
33 *
34 * Useful resources:
35 * - http://www.alistapart.com/articles/byebyeembed
36 * - http://www.w3.org/TR/html401/struct/objects.html
37 * - http://www.dyn-web.com/dhtml/iframes/
38 * - http://support.microsoft.com/kb/316992
39 * - http://www.apple.com/quicktime/player/specs.html
40 * - http://www.howtocreate.co.uk/wrongWithIE/?chapter=navigator.plugins
41 *
42 * @class       Shadowbox
43 * @author      Michael J. I. Jackson <[email protected]>
44 * @singleton
45 */
46(function(){
47
48    /**
49     * The current version of Shadowbox.
50     *
51     * @property    {String}    version
52     * @private
53     */
54    var version = '1.0';
55
56    /**
57     * Contains the default options for Shadowbox. This object is almost
58     * entirely customizable.
59     *
60     * @property    {Object}    options
61     * @private
62     */
63    var options = {
64
65        /**
66         * A base URL that will be prepended to the loadingImage, flvPlayer, and
67         * overlayBgImage options to save on typing.
68         *
69         * @var     {String}    assetURL
70         */
71        assetURL:           '',
72
73        /**
74         * The path to the image to display while loading.
75         *
76         * @var     {String}    loadingImage
77         */
78        loadingImage:       'images/loading2.gif',
79
80        /**
81         * Enable animations.
82         *
83         * @var     {Boolean}   animate
84         */
85        animate:            true,
86
87        /**
88         * Specifies the sequence of the height and width animations. May be
89         * 'wh' (width then height), 'hw' (height then width), or 'sync' (both
90         * at the same time). Of course this will only work if animate is true.
91         *
92         * @var     {String}    animSequence
93         */
94        animSequence:       'wh',
95
96        /**
97         * The path to flvplayer.swf.
98         *
99         * @var     {String}    flvPlayer
100         */
101        flvPlayer:          'flvplayer.swf',
102
103        /**
104         * The background color and opacity of the overlay. Note: When viewing
105         * movie files on FF Mac, the default background image will be used
106         * because that browser has problems displaying movies above layers
107         * that aren't 100% opaque.
108         *
109         * @var     {String}    overlayColor
110         */
111        overlayColor:       '#000000',
112
113        /**
114         * The background opacity to use for the overlay.
115         *
116         * @var     {Number}    overlayOpacity
117         */
118        overlayOpacity:     0.50,
119
120        /**
121         * A background image to use for browsers such as FF Mac that don't
122         * support displaying movie content over backgrounds that aren't 100%
123         * opaque.
124         *
125         * @var     {String}    overlayBgImage
126         */
127        overlayBgImage:     'images/overlay-85.png',
128
129        /**
130         * Listen to the overlay for clicks. If the user clicks the overlay,
131         * it will trigger Shadowbox.close().
132         *
133         * @var     {Boolean}   listenOverlay
134         */
135        listenOverlay:      true,
136
137        /**
138         * Automatically play movies.
139         *
140         * @var     {Boolean}   autoplayMovies
141         */
142        autoplayMovies:     true,
143
144        /**
145         * Enable movie controllers on movie players.
146         *
147         * @var     {Boolean}   showMovieControls
148         */
149        showMovieControls:  true,
150
151        /**
152         * The duration of the resizing animations (in seconds).
153         *
154         * @var     {Number}    resizeDuration
155         */
156        resizeDuration:     0.35,
157
158        /**
159         * The duration of the overlay fade animation (in seconds).
160         *
161         * @var     {Number}    fadeDuration
162         */
163        fadeDuration:       0.35,
164
165        /**
166         * Show the navigation controls.
167         *
168         * @var     {Boolean}   displayNav
169         */
170        displayNav:         true,
171
172        /**
173         * Enable continuous galleries. When this is true, users will be able
174         * to skip to the first gallery image from the last using next and vice
175         * versa.
176         *
177         * @var     {Boolean}   continuous
178         */
179        continuous:         false,
180
181        /**
182         * Display the gallery counter.
183         *
184         * @var     {Boolean}   displayCounter
185         */
186        displayCounter:     true,
187
188        /**
189         * This option may be either 'default' or 'skip'. The default counter is
190         * a simple '1 of 5' message. The skip counter displays a link for each
191         * piece in the gallery that enables a user to skip directly to any
192         * piece.
193         *
194         * @var     {String}    counterType
195         */
196        counterType:        'default',
197
198        /**
199         * The amount of padding to maintain around the viewport edge (in
200         * pixels). This only applies when the image is very large and takes up
201         * the entire viewport.
202         *
203         * @var     {Number}    viewportPadding
204         */
205        viewportPadding:    1,
206
207        /**
208         * How to handle images that are too large for the viewport. 'resize'
209         * will resize the image while preserving aspect ratio and display it at
210         * the smaller resolution. 'drag' will display the image at its native
211         * resolution but it will be draggable within the Shadowbox. 'none' will
212         * display the image at its native resolution but it may be cropped.
213         *
214         * @var     {String}    handleLgImages
215         */
216        handleLgImages:     'resize',
217
218        /**
219         * The initial height of Shadowbox (in pixels).
220         *
221         * @var     {Number}    initialHeight
222         */
223        initialHeight:      160,
224
225        /**
226         * The initial width of Shadowbox (in pixels).
227         *
228         * @var     {Number}    initialWidth
229         */
230        initialWidth:       320,
231
232        /**
233         * Enable keyboard control. Note: If you disable the keys, you may want
234         * to change the visual styles for the navigation elements that suggest
235         * keyboard shortcuts.
236         *
237         * @var     {Boolean}   enableKeys
238         */
239        enableKeys:         true,
240
241        /**
242         * The keys used to control Shadowbox. Note: In order to use these,
243         * enableKeys must be true. Key values or key codes may be used.
244         *
245         * @var     {Array}
246         */
247        keysClose:          [27], // c, q, or esc
248        keysNext:           ['n', 39],      // n or right arrow
249        keysPrev:           ['p', 37],      // p or left arrow
250
251        /**
252         * A hook function to be fired when Shadowbox opens. The single argument
253         * will be the current gallery element.
254         *
255         * @var     {Function}
256         */
257        onOpen:             null,
258
259        /**
260         * A hook function to be fired when Shadowbox finishes loading its
261         * content. The single argument will be the current gallery element on
262         * display.
263         *
264         * @var     {Function}
265         */
266        onFinish:           null,
267
268        /**
269         * A hook function to be fired when Shadowbox changes from one gallery
270         * element to the next. The single argument will be the current gallery
271         * element that is about to be displayed.
272         *
273         * @var     {Function}
274         */
275        onChange:           null,
276
277        /**
278         * A hook function that will be fired when Shadowbox closes. The single
279         * argument will be the gallery element most recently displayed.
280         *
281         * @var     {Function}
282         */
283        onClose:            null,
284
285        /**
286         * The mode to use when handling unsupported media. May be either
287         * 'remove' or 'link'. If it is 'remove', the unsupported gallery item
288         * will merely be removed from the gallery. If it is the only item in
289         * the gallery, the link will simply be followed. If it is 'link', a
290         * link will be provided to the appropriate plugin page in place of the
291         * gallery element.
292         *
293         * @var     {String}    handleUnsupported
294         */
295        handleUnsupported:  'link',
296
297        /**
298         * Skips calling Shadowbox.setup() in init(). This means that it must
299         * be called later manually.
300         *
301         * @var     {Boolean}   skipSetup
302         */
303        skipSetup:          false,
304
305        /**
306         * Text messages to use for Shadowbox. These are provided so they may be
307         * translated into different languages.
308         *
309         * @var     {Object}    text
310         */
311        text:           {
312
313            cancel:     'Annulla',
314
315            loading:    'loading',
316
317            close:      '<span class="shortcut" style="color:#ffffff; font-size:15px;">C</span><span style="color:#ffffff; font-size:15px;">hiudi</span>',
318
319            next:       '<span class="shortcut" style="color:#ffffff">S</span><span style="color:#ffffff">uccessiva</span>',
320
321            prev:       '<span class="shortcut" style="color:#ffffff">P</span><span style="color:#ffffff">recedente</span>',
322
323            errors:     {
324                single: 'You must install the <a href="{0}">{1}</a> browser plugin to view this content.',
325                shared: 'You must install both the <a href="{0}">{1}</a> and <a href="{2}">{3}</a> browser plugins to view this content.',
326                either: 'You must install either the <a href="{0}">{1}</a> or the <a href="{2}">{3}</a> browser plugin to view this content.'
327            }
328
329        },
330
331        /**
332         * An object containing names of plugins and links to their respective
333         * download pages.
334         *
335         * @var     {Object}    errors
336         */
337        errors:         {
338
339            fla:        {
340                name:   'Flash',
341                url:    'http://www.adobe.com/products/flashplayer/'
342            },
343
344            qt:         {
345                name:   'QuickTime',
346                url:    'http://www.apple.com/quicktime/download/'
347            },
348
349            wmp:        {
350                name:   'Windows Media Player',
351                url:    'http://www.microsoft.com/windows/windowsmedia/'
352            },
353
354            f4m:        {
355                name:   'Flip4Mac',
356                url:    'http://www.flip4mac.com/wmv_download.htm'
357            }
358
359        },
360
361        /**
362         * The HTML markup to use for Shadowbox. Note: The script depends on
363         * most of these elements being present, so don't modify this variable
364         * unless you know what you're doing.
365         *
366         * @var     {Object}    skin
367         */
368        skin:           {
369
370            main:       '<div id="shadowbox_overlay"></div>' +
371                        '<div id="shadowbox_container">' +
372                            '<div id="shadowbox">' +
373                                '<div id="shadowbox_title">' +
374                                    '<div id="shadowbox_title_inner"></div>' +
375                                '</div>' +
376                                '<div id="shadowbox_body">' +
377                                    '<div id="shadowbox_body_inner"></div>' +
378                                    '<div id="shadowbox_loading"></div>' +
379                                '</div>' +
380                                '<div id="shadowbox_toolbar">' +
381                                    '<div id="shadowbox_toolbar_inner"></div>' +
382                                '</div>' +
383                            '</div>' +
384                        '</div>',
385
386            loading:    '<img src="{0}" alt="{1}" />' +
387                        '<span><a href="javascript:Shadowbox.close();">{2}</a></span>',
388
389            counter:    '<div id="shadowbox_counter">{0}</div>',
390
391            close:      '<div id="shadowbox_nav_close">' +
392                            '<a href="javascript:Shadowbox.close();">{0}</a>' +
393                        '</div>',
394
395            next:       '<div id="shadowbox_nav_next">' +
396                            '<a href="javascript:Shadowbox.next();">{0}</a>' +
397                        '</div>',
398
399            prev:       '<div id="shadowbox_nav_previous">' +
400                            '<a href="javascript:Shadowbox.previous();">{0}</a>' +
401                        '</div>'
402
403        },
404
405        /**
406         * An object containing arrays of all supported file extensions. Each
407         * property of this object contains an array. If this object is to be
408         * modified, it must be done before calling init().
409         *
410         * - img: Supported image file extensions
411         * - qt: Movie file extensions supported by QuickTime
412         * - wmp: Movie file extensions supported by Windows Media Player
413         * - qtwmp: Movie file extensions supported by both QuickTime and Windows Media Player
414         * - iframe: File extensions that will be display in an iframe
415         *
416         * @var     {Object}    ext
417         */
418        ext:     {
419            img:        ['png', 'jpg', 'jpeg', 'gif', 'bmp'],
420            qt:         ['dv', 'mov', 'moov', 'movie', 'mp4'],
421            wmp:        ['asf', 'wm', 'wmv'],
422            qtwmp:      ['avi', 'mpg', 'mpeg'],
423            iframe:     ['asp', 'aspx', 'cgi', 'cfm', 'htm', 'html', 'pl', 'php', 'pdf',
424                        'php3', 'php4', 'php5', 'phtml', 'rb', 'rhtml', 'shtml', 'jsp',
425                        'txt', 'vbs']
426        }
427
428    };
429
430    /**
431     * Stores the default set of options in case a custom set of options is used
432     * on a link-by-link basis so we can restore them later.
433     *
434     * @property    {Object}    default_options
435     * @private
436     */
437    var default_options = null;
438
439    /**
440     * Shorthand for Shadowbox.lib.
441     *
442     * @property    {Object}        SL
443     * @private
444     */
445    var SL = Shadowbox.lib;
446
447    /**
448     * An object containing some regular expressions we'll need later. Compiled
449     * up front for speed.
450     *
451     * @property    {Object}        RE
452     * @private
453     */
454    var RE = {
455        resize:         /(img|swf|flv)/, // file types to resize
456        overlay:        /(img|iframe|html|inline)/, // content types to not use an overlay image for on FF Mac
457        swf:            /\.swf\s*$/i, // swf file extension
458        flv:            /\.flv\s*$/i, // flv file extension
459        domain:         /:\/\/(.*?)[:\/]/, // domain prefix
460        inline:         /#(.+)$/, // inline element id
461        rel:            /^(light|shadow)box/i, // rel attribute format
462        gallery:        /^(light|shadow)box\[(.*?)\]/i, // rel attribute format for gallery link
463        unsupported:    /^unsupported-(\w+)/, // unsupported media type
464        param:          /\s*([a-z_]*?)\s*=\s*(.+)\s*/, // rel string parameter
465        empty:          /^(?:br|frame|hr|img|input|link|meta|range|spacer|wbr|area|param|col)$/i // elements that don't have children
466    };
467
468    /**
469     * A cache of options for links that have been set up for use with
470     * Shadowbox.
471     *
472     * @property    {Array}         cache
473     * @private
474     */
475    var cache = [];
476
477    /**
478     * An array of pieces currently being viewed. In the case of non-gallery
479     * pieces, this will only hold one object.
480     *
481     * @property    {Array}         current_gallery
482     * @private
483     */
484    var current_gallery;
485
486    /**
487     * The array index of the current_gallery that is currently being viewed.
488     *
489     * @property    {Number}        current
490     * @private
491     */
492    var current;
493
494    /**
495     * Keeps track of the current optimal height of the box. We use this so that
496     * if the user resizes the browser window to get a better view, and we're
497     * currently at a size smaller than the optimal, we can resize easily.
498     *
499     * @see         resizeContent()
500     * @property    {Number}        optimal_height
501     * @private
502     */
503    var optimal_height = options.initialHeight;
504
505    /**
506     * Keeps track of the current optimal width of the box. See optimal_height
507     * explanation (above).
508     *
509     * @property    {Number}        optimal_width
510     * @private
511     */
512    var optimal_width = options.initialWidth;
513
514    /**
515     * Keeps track of the current height of the box. This is useful in drag
516     * calculations.
517     *
518     * @property    {Number}        current_height
519     * @private
520     */
521    var current_height = 0;
522
523    /**
524     * Keeps track of the current width of the box. Useful in drag calculations.
525     *
526     * @property    {Number}        current_width
527     * @private
528     */
529    var current_width = 0;
530
531    /**
532     * Resource used to preload images. It's class-level so that when a new
533     * image is requested, the same resource can be reassigned, cancelling
534     * the original's callback.
535     *
536     * @property    {HTMLElement}   preloader
537     * @private
538     */
539    var preloader;
540
541    /**
542     * Keeps track of whether or not Shadowbox has been initialized. We never
543     * want to initialize twice.
544     *
545     * @property    {Boolean}       initialized
546     * @private
547     */
548    var initialized = false;
549
550    /**
551     * Keeps track of whether or not Shadowbox is activated.
552     *
553     * @property    {Boolean}       activated
554     * @private
555     */
556    var activated = false;
557
558    /**
559     * Keeps track of 4 floating values (x, y, start_x, & start_y) that are used
560     * in the drag calculations.
561     *
562     * @property    {Object}        drag
563     * @private
564     */
565    var drag;
566
567    /**
568     * Holds the draggable element so we don't have to fetch it every time
569     * the mouse moves.
570     *
571     * @property    {HTMLElement}   draggable
572     * @private
573     */
574    var draggable;
575
576    /**
577     * Keeps track of whether or not we're currently using the overlay
578     * background image to display the current gallery. We do this because we
579     * use different methods for fading the overlay in and out. The color fill
580     * overlay fades in and out nicely, but the image overlay stutters. By
581     * keeping track of the type of overlay in use, we don't have to check again
582     * what type of overlay we're using when it's time to get rid of it later.
583     *
584     * @property    {Boolean}       overlay_img_needed
585     * @private
586     */
587    var overlay_img_needed;
588
589    /**
590     * These parameters for simple browser detection. Used in Ext.js.
591     *
592     * @ignore
593     */
594    var ua = navigator.userAgent.toLowerCase();
595    var isStrict = document.compatMode == 'CSS1Compat',
596        isOpera = ua.indexOf("opera") > -1,
597        isIE = ua.indexOf('msie') > -1,
598        isIE7 = ua.indexOf('msie 7') > -1,
599        isBorderBox = isIE && !isStrict,
600        isSafari = (/webkit|khtml/).test(ua),
601        isSafari3 = isSafari && !!(document.evaluate),
602        isGecko = !isSafari && ua.indexOf('gecko') > -1,
603        isWindows = (ua.indexOf('windows') != -1 || ua.indexOf('win32') != -1),
604        isMac = (ua.indexOf('macintosh') != -1 || ua.indexOf('mac os x') != -1),
605        isLinux = (ua.indexOf('linux') != -1);
606
607    /**
608     * Do we need to hack the position to make Shadowbox appear fixed? We could
609     * hack this using CSS, but let's just get over all the hacks and let IE6
610     * users get what they deserve! Down with hacks! Hmm...now that I think
611     * about it, I should just flash all kinds of alerts and annoying popups on
612     * their screens, and then redirect them to some foreign spyware site that
613     * will upload a nasty virus...
614     *
615     * @property    {Boolean}   absolute_pos
616     * @private
617     */
618    var absolute_pos = isIE && !isIE7;
619
620    /**
621     * Contains plugin support information. Each property of this object is a
622     * boolean indicating whether that plugin is supported.
623     *
624     * - fla: Flash player
625     * - qt: QuickTime player
626     * - wmp: Windows Media player
627     * - f4m: Flip4Mac plugin
628     *
629     * @property    {Object}    plugins
630     * @private
631     */
632    var plugins = null;
633
634    // detect plugin support
635    if(navigator.plugins && navigator.plugins.length){
636        var detectPlugin = function(plugin_name){
637            var detected = false;
638            for (var i = 0, len = navigator.plugins.length; i < len; ++i){
639                if(navigator.plugins[i].name.indexOf(plugin_name) > -1){
640                    detected = true;
641                    break;
642                }
643            }
644            return detected;
645        };
646        var f4m = detectPlugin('Flip4Mac');
647        var plugins = {
648            fla:    detectPlugin('Shockwave Flash'),
649            qt:     detectPlugin('QuickTime'),
650            wmp:    !f4m && detectPlugin('Windows Media'), // if it's Flip4Mac, it's not really WMP
651            f4m:    f4m
652        };
653    }else{
654        var detectPlugin = function(plugin_name){
655            var detected = false;
656            try {
657                var axo = new ActiveXObject(plugin_name);
658                if(axo){
659                    detected = true;
660                }
661            } catch (e) {}
662            return detected;
663        };
664        var plugins = {
665            fla:    detectPlugin('ShockwaveFlash.ShockwaveFlash'),
666            qt:     detectPlugin('QuickTime.QuickTime'),
667            wmp:    detectPlugin('wmplayer.ocx'),
668            f4m:    false
669        };
670    }
671
672    /**
673     * Applies all properties of e to o. This function is recursive so that if
674     * any properties of e are themselves objects, those objects will be applied
675     * to objects with the same key that may exist in o.
676     *
677     * @param   {Object}    o       The original object
678     * @param   {Object}    e       The extension object
679     * @return  {Object}            The original object with all properties
680     *                              of the extension object applied (deep)
681     * @private
682     */
683    var apply = function(o, e){
684        for(var p in e) o[p] = e[p];
685        return o;
686    };
687
688    /**
689     * Determines if the given object is an anchor/area element.
690     *
691     * @param   {mixed}     el      The object to check
692     * @return  {Boolean}           True if the object is a link element
693     * @private
694     */
695    var isLink = function(el){
696        return typeof el.tagName == 'string' && (el.tagName.toUpperCase() == 'A' || el.tagName.toUpperCase() == 'AREA');
697    };
698
699    /**
700     * Gets the height of the viewport in pixels. Note: This function includes
701     * scrollbars in Safari 3.
702     *
703     * @return  {Number}        The height of the viewport
704     * @public
705     * @static
706     */
707    SL.getViewportHeight = function(){
708        var height = window.innerHeight; // Safari
709        var mode = document.compatMode;
710        if((mode || isIE) && !isOpera){
711            height = isStrict ? document.documentElement.clientHeight : document.body.clientHeight;
712        }
713        return height;
714    };
715
716    /**
717     * Gets the width of the viewport in pixels. Note: This function includes
718     * scrollbars in Safari 3.
719     *
720     * @return  {Number}        The width of the viewport
721     * @public
722     * @static
723     */
724    SL.getViewportWidth = function(){
725        var width = window.innerWidth; // Safari
726        var mode = document.compatMode;
727        if(mode || isIE){
728            width = isStrict ? document.documentElement.clientWidth : document.body.clientWidth;
729        }
730        return width;
731    };
732
733    /**
734     * Gets the height of the document (body and its margins) in pixels.
735     *
736     * @return  {Number}        The height of the document
737     * @public
738     * @static
739     */
740    SL.getDocumentHeight = function(){
741        var scrollHeight = isStrict ? document.documentElement.scrollHeight : document.body.scrollHeight;
742        return Math.max(scrollHeight, SL.getViewportHeight());
743    };
744
745    /**
746     * Gets the width of the document (body and its margins) in pixels.
747     *
748     * @return  {Number}        The width of the document
749     * @public
750     * @static
751     */
752    SL.getDocumentWidth = function(){
753        var scrollWidth = isStrict ? document.documentElement.scrollWidth : document.body.scrollWidth;
754        return Math.max(scrollWidth, SL.getViewportWidth());
755    };
756
757    /**
758     * A utility function used by the fade functions to clear the opacity
759     * style setting of the given element. Required in some cases for IE.
760     * Based on Ext.Element's clearOpacity.
761     *
762     * @param   {HTMLElement}   el      The DOM element
763     * @return  void
764     * @private
765     */
766    var clearOpacity = function(el){
767        if(isIE){
768            if(typeof el.style.filter == 'string' && (/alpha/i).test(el.style.filter)){
769                el.style.filter = '';
770            }
771        }else{
772            el.style.opacity = '';
773            el.style['-moz-opacity'] = '';
774            el.style['-khtml-opacity'] = '';
775        }
776    };
777
778    /**
779     * Fades the given element from 0 to the specified opacity.
780     *
781     * @param   {HTMLElement}   el              The DOM element to fade
782     * @param   {Number}        endingOpacity   The final opacity to animate to
783     * @param   {Number}        duration        The duration of the animation
784     *                                          (in seconds)
785     * @param   {Function}      callback        A callback function to call
786     *                                          when the animation completes
787     * @return  void
788     * @private
789     */
790    var fadeIn = function(el, endingOpacity, duration, callback){
791        if(options.animate){
792            SL.setStyle(el, 'opacity', 0);
793            el.style.visibility = 'visible';
794            SL.animate(el, {
795                opacity: { to: endingOpacity }
796            }, duration, function(){
797                if(endingOpacity == 1) clearOpacity(el);
798                if(typeof callback == 'function') callback();
799            });
800        }else{
801            if(endingOpacity == 1){
802                clearOpacity(el);
803            }else{
804                SL.setStyle(el, 'opacity', endingOpacity);
805            }
806            el.style.visibility = 'visible';
807            if(typeof callback == 'function') callback();
808        }
809    };
810
811    /**
812     * Fades the given element from its current opacity to 0.
813     *
814     * @param   {HTMLElement}   el          The DOM element to fade
815     * @param   {Number}        duration    The duration of the fade animation
816     * @param   {Function}      callback    A callback function to call when
817     *                                      the animation completes
818     * @return  void
819     * @private
820     */
821    var fadeOut = function(el, duration, callback){
822        var cb = function(){
823            el.style.visibility = 'hidden';
824            clearOpacity(el);
825            if(typeof callback == 'function') callback();
826        };
827        if(options.animate){
828            SL.animate(el, {
829                opacity: { to: 0 }
830            }, duration, cb);
831        }else{
832            cb();
833        }
834    };
835
836    /**
837     * Appends an HTML fragment to the given element.
838     *
839     * @param   {String/HTMLElement}    el      The element to append to
840     * @param   {String}                html    The HTML fragment to use
841     * @return  {HTMLElement}                   The newly appended element
842     * @private
843     */
844    var appendHTML = function(el, html){
845        el = SL.get(el);
846        if(el.insertAdjacentHTML){
847            el.insertAdjacentHTML('BeforeEnd', html);
848            return el.lastChild;
849        }
850        if(el.lastChild){
851            var range = el.ownerDocument.createRange();
852            range.setStartAfter(el.lastChild);
853            var frag = range.createContextualFragment(html);
854            el.appendChild(frag);
855            return el.lastChild;
856        }else{
857            el.innerHTML = html;
858            return el.lastChild;
859        }
860    };
861
862    /**
863     * Overwrites the HTML of the given element.
864     *
865     * @param   {String/HTMLElement}    el      The element to overwrite
866     * @param   {String}                html    The new HTML to use
867     * @return  {HTMLElement}                   The new firstChild element
868     * @private
869     */
870    var overwriteHTML = function(el, html){
871        el = SL.get(el);
872        el.innerHTML = html;
873        return el.firstChild;
874    };
875
876    /**
877     * Gets either the offsetHeight or the height of the given element plus
878     * padding and borders (when offsetHeight is not available). Based on
879     * Ext.Element's getComputedHeight.
880     *
881     * @return  {Number}            The computed height of the element
882     * @private
883     */
884    var getComputedHeight = function(el){
885        var h = Math.max(el.offsetHeight, el.clientHeight);
886        if(!h){
887            h = parseInt(SL.getStyle(el, 'height'), 10) || 0;
888            if(!isBorderBox){
889                h += parseInt(SL.getStyle(el, 'padding-top'), 10)
890                    + parseInt(SL.getStyle(el, 'padding-bottom'), 10)
891                    + parseInt(SL.getStyle(el, 'border-top-width'), 10)
892                    + parseInt(SL.getStyle(el, 'border-bottom-width'), 10);
893            }
894        }
895        return h;
896    };
897
898    /**
899     * Gets either the offsetWidth or the width of the given element plus
900     * padding and borders (when offsetWidth is not available). Based on
901     * Ext.Element's getComputedWidth.
902     *
903     * @return  {Number}            The computed width of the element
904     * @private
905     */
906    var getComputedWidth = function(el){
907        var w = Math.max(el.offsetWidth, el.clientWidth);
908        if(!w){
909            w = parseInt(SL.getStyle(el, 'width'), 10) || 0;
910            if(!isBorderBox){
911                w += parseInt(SL.getStyle(el, 'padding-left'), 10)
912                    + parseInt(SL.getStyle(el, 'padding-right'), 10)
913                    + parseInt(SL.getStyle(el, 'border-left-width'), 10)
914                    + parseInt(SL.getStyle(el, 'border-right-width'), 10);
915            }
916        }
917        return w;
918    };
919
920    /**
921     * Determines the player needed to display the file at the given URL. If
922     * the file type is not supported, the return value will be 'unsupported'.
923     * If the file type is not supported but the correct player can be
924     * determined, the return value will be 'unsupported-*' where * will be the
925     * player abbreviation (e.g. 'qt' = QuickTime).
926     *
927     * @param   {String}        url     The url of the file
928     * @return  {String}                The name of the player to use
929     * @private
930     */
931    var getPlayerType = function(url){
932        if(RE.img.test(url)) return 'img';
933        var match = url.match(RE.domain);
934        var this_domain = match ? document.domain == match[1] : false;
935        if(url.indexOf('#') > -1 && this_domain) return 'inline';
936        var q_index = url.indexOf('?');
937        if(q_index > -1) url = url.substring(0, q_index); // strip query string for player detection purposes
938        if(RE.swf.test(url)) return plugins.fla ? 'swf' : 'unsupported-swf';
939        if(RE.flv.test(url)) return plugins.fla ? 'flv' : 'unsupported-flv';
940        if(RE.qt.test(url)) return plugins.qt ? 'qt' : 'unsupported-qt';
941        if(RE.wmp.test(url)){
942            if(plugins.wmp){
943                return 'wmp';
944            }else if(plugins.f4m){
945                return 'qt';
946            }else{
947                return isMac ? (plugins.qt ? 'unsupported-f4m' : 'unsupported-qtf4m') : 'unsupported-wmp';
948            }
949        }else if(RE.qtwmp.test(url)){
950            if(plugins.qt){
951                return 'qt';
952            }else if(plugins.wmp){
953                return 'wmp';
954            }else{
955                return isMac ? 'unsupported-qt' : 'unsupported-qtwmp';
956            }
957        }else if(!this_domain || RE.iframe.test(url)){
958            return 'iframe';
959        }
960        return 'unsupported';
961    };
962
963    /**
964     * Handles all clicks on links that have been set up to work with Shadowbox
965     * and cancels the default event behavior when appropriate.
966     *
967     * @param   {Event}         ev          The click event object
968     * @return  void
969     * @private
970     */
971    var handleClick = function(ev){
972        // get anchor/area element
973        var link;
974        if(isLink(this)){
975            link = this; // jQuery, Prototype, YUI
976        }else{
977            link = SL.getTarget(ev); // Ext
978            while(!isLink(link) && link.parentNode){
979                link = link.parentNode;
980            }
981        }
982
983        Shadowbox.open(link);
984        if(current_gallery.length) SL.preventDefault(ev);
985    };
986
987    /**
988     * Sets up the current gallery for the given object. Modifies the current
989     * and current_gallery variables to contain the appropriate information.
990     * Also, checks to see if there are any gallery pieces that are not
991     * supported by the client's browser/plugins. If there are, they will be
992     * handled according to the handleUnsupported option.
993     *
994     * @param   {Object}    obj         The content to get the gallery for
995     * @return  void
996     * @private
997     */
998    var setupGallery = function(obj){
999        // create a copy so it doesn't get modified later
1000        var copy = apply({}, obj);
1001
1002        // is it part of a gallery?
1003        if(!obj.gallery){ // single item, no gallery
1004            current_gallery = [copy];
1005            current = 0;
1006        }else{
1007            current_gallery = []; // clear the current gallery
1008            var index, ci;
1009            for(var i = 0, len = cache.length; i < len; ++i){
1010                ci = cache[i];
1011                if(ci.gallery){
1012                    if(ci.content == obj.content
1013                        && ci.gallery == obj.gallery
1014                        && ci.title == obj.title){ // compare content, gallery, & title
1015                            index = current_gallery.length; // key element found
1016                    }
1017                    if(ci.gallery == obj.gallery){
1018                        current_gallery.push(apply({}, ci));
1019                    }
1020                }
1021            }
1022            // if not found in cache, prepend to front of gallery
1023            if(index == null){
1024                current_gallery.unshift(copy);
1025                index = 0;
1026            }
1027            current = index;
1028        }
1029
1030        // are any media in the current gallery supported?
1031        var match, r;
1032        for(var i = 0, len = current_gallery.length; i < len; ++i){
1033            r = false;
1034            if(current_gallery[i].type == 'unsupported'){ // don't support this at all
1035                r = true;
1036            }else if(match = RE.unsupported.exec(current_gallery[i].type)){ // handle unsupported elements
1037                if(options.handleUnsupported == 'link'){
1038                    current_gallery[i].type = 'html';
1039                    // generate a link to the appropriate plugin download page(s)
1040                    var m;
1041                    switch(match[1]){
1042                        case 'qtwmp':
1043                            m = String.format(options.text.errors.either,
1044                                options.errors.qt.url, options.errors.qt.name,
1045                                options.errors.wmp.url, options.errors.wmp.name);
1046                        break;
1047                        case 'qtf4m':
1048                            m = String.format(options.text.errors.shared,
1049                                options.errors.qt.url, options.errors.qt.name,
1050                                options.errors.f4m.url, options.errors.f4m.name);
1051                        break;
1052                        default:
1053                            if(match[1] == 'swf' || match[1] == 'flv') match[1] = 'fla';
1054                            m = String.format(options.text.errors.single,
1055                                options.errors[match[1]].url, options.errors[match[1]].name);
1056                    }
1057                    current_gallery[i] = apply(current_gallery[i], {
1058                        height:     160, // error messages are short so they
1059                        width:      320, // only need a small box to display properly
1060                        content:    '<div class="shadowbox_message">' + m + '</div>'
1061                    });
1062                }else{
1063                    r = true;
1064                }
1065            }else if(current_gallery[i].type == 'inline'){ // handle inline elements
1066                // retrieve the innerHTML of the inline element
1067                var match = RE.inline.exec(current_gallery[i].content);
1068                if(match){
1069                    var el;
1070                    if(el = SL.get(match[1])){
1071                        current_gallery[i].content = el.innerHTML;
1072                    }else{
1073                        throw 'No element found with id ' + match[1];
1074                    }
1075                }else{
1076                    throw 'No element id found for inline content';
1077                }
1078            }
1079            if(r){
1080                // remove the element from the gallery
1081                current_gallery.splice(i, 1);
1082                if(i < current) --current;
1083                --i;
1084            }
1085        }
1086    };
1087
1088    /**
1089     * Hides the title bar and toolbar and populates them with the proper
1090     * content.
1091     *
1092     * @return  void
1093     * @private
1094     */
1095    var buildBars = function(){
1096        var link = current_gallery[current];
1097        if(!link) return; // nothing to build
1098
1099        // build the title
1100        var title_i = SL.get('shadowbox_title_inner');
1101        title_i.innerHTML = (link.title) ? link.title : '';
1102        // empty the toolbar
1103        var tool_i = SL.get('shadowbox_toolbar_inner');
1104        tool_i.innerHTML = '';
1105
1106        // build the nav
1107        if(options.displayNav){
1108            tool_i.innerHTML = String.format(options.skin.close, options.text.close);
1109            if(current_gallery.length > 1){
1110                if(options.continuous){
1111                    // show both
1112                    appendHTML(tool_i, String.format(options.skin.next, options.text.next));
1113                    appendHTML(tool_i, String.format(options.skin.prev, options.text.prev));
1114                }else{
1115                    // not last in the gallery, show the next link
1116                    if((current_gallery.length - 1) > current){
1117                        appendHTML(tool_i, String.format(options.skin.next, options.text.next));
1118                    }
1119                    // not first in the gallery, show the previous link
1120                    if(current > 0){
1121                        appendHTML(tool_i, String.format(options.skin.prev, options.text.prev));
1122                    }
1123                }
1124            }
1125        }
1126
1127        // build the counter
1128        if(current_gallery.length > 1 && options.displayCounter){
1129            // append the counter div
1130            var counter = '';
1131            if(options.counterType == 'skip'){
1132                for(var i = 0, len = current_gallery.length; i < len; ++i){
1133                    counter += '<a href="javascript:Shadowbox.change(' + i + ');"';
1134                    if(i == current){
1135                        counter += ' class="shadowbox_counter_current"';
1136                    }
1137                    counter += '>' + (i + 1) + '</a>';
1138                }
1139            }else{
1140                counter = (current + 1) + ' of ' + current_gallery.length;
1141            }
1142            appendHTML(tool_i, String.format(options.skin.counter, counter));
1143        }
1144    };
1145
1146    /**
1147     * Hides the title and tool bars.
1148     *
1149     * @param   {Function}  callback        A function to call on finish
1150     * @return  void
1151     * @private
1152     */
1153    var hideBars = function(callback){
1154        var title_m = getComputedHeight(SL.get('shadowbox_title'));
1155        var tool_m = 0 - getComputedHeight(SL.get('shadowbox_toolbar'));
1156        var title_i = SL.get('shadowbox_title_inner');
1157        var tool_i = SL.get('shadowbox_toolbar_inner');
1158
1159        if(options.animate && callback){
1160            // animate the transition
1161            SL.animate(title_i, {
1162                marginTop: { to: title_m }
1163            }, 0.2);
1164            SL.animate(tool_i, {
1165                marginTop: { to: tool_m }
1166            }, 0.2, callback);
1167        }else{
1168            SL.setStyle(title_i, 'marginTop', title_m + 'px');
1169            SL.setStyle(tool_i, 'marginTop', tool_m + 'px');
1170        }
1171    };
1172
1173    /**
1174     * Shows the title and tool bars.
1175     *
1176     * @param   {Function}  callback        A callback function to execute after
1177     *                                      the animation completes
1178     * @return  void
1179     * @private
1180     */
1181    var showBars = function(callback){
1182        var title_i = SL.get('shadowbox_title_inner');
1183        if(options.animate){
1184            if(title_i.innerHTML != ''){
1185                SL.animate(title_i, { marginTop: { to: 0 } }, 0.35);
1186            }
1187            SL.animate(SL.get('shadowbox_toolbar_inner'), {
1188                marginTop: { to: 0 }
1189            }, 0.35, callback);
1190        }else{
1191            if(title_i.innerHTML != ''){
1192                SL.setStyle(title_i, 'margin-top', '0px');
1193            }
1194            SL.setStyle(SL.get('shadowbox_toolbar_inner'), 'margin-top', '0px');
1195            callback();
1196        }
1197    };
1198
1199    /**
1200     * Resets the class drag variable.
1201     *
1202     * @return  void
1203     * @private
1204     */
1205    var resetDrag = function(){
1206        drag = {
1207            x:          0,
1208            y:          0,
1209            start_x:    null,
1210            start_y:    null
1211        };
1212    };
1213
1214    /**
1215     * Toggles the drag function on and off.
1216     *
1217     * @param   {Boolean}   on      True to toggle on, false to toggle off
1218     * @return  void
1219     * @private
1220     */
1221    var toggleDrag = function(on){
1222        if(on){
1223            resetDrag();
1224            // add drag layer to prevent browser dragging of actual image
1225            var styles = [
1226                'position:absolute',
1227                'cursor:' + (isGecko ? '-moz-grab' : 'move')
1228            ];
1229            // make drag layer transparent
1230            styles.push(isIE ? 'background-color:#fff;filter:alpha(opacity=0)' : 'background-color:transparent');
1231            appendHTML('shadowbox_body_inner', '<div id="shadowbox_drag_layer" style="' + styles.join(';') + '"></div>');
1232            SL.addEvent(SL.get('shadowbox_drag_layer'), 'mousedown', listenDrag);
1233        }else{
1234            var d = SL.get('shadowbox_drag_layer');
1235            if(d){
1236                SL.removeEvent(d, 'mousedown', listenDrag);
1237                SL.remove(d);
1238            }
1239        }
1240    };
1241
1242    /**
1243     * Sets up a drag listener on the document. Called when the mouse button is
1244     * pressed (mousedown).
1245     *
1246     * @param   {mixed}     ev      The mousedown event
1247     * @return  void
1248     * @private
1249     */
1250    var listenDrag = function(ev){
1251        drag.start_x = ev.clientX;
1252        drag.start_y = ev.clientY;
1253        draggable = SL.get('shadowbox_content');
1254        SL.addEvent(document, 'mousemove', positionDrag);
1255        SL.addEvent(document, 'mouseup', unlistenDrag);
1256        if(isGecko) SL.setStyle(SL.get('shadowbox_drag_layer'), 'cursor', '-moz-grabbing');
1257    };
1258
1259    /**
1260     * Removes the drag listener. Called when the mouse button is released
1261     * (mouseup).
1262     *
1263     * @return  void
1264     * @private
1265     */
1266    var unlistenDrag = function(){
1267        SL.removeEvent(document, 'mousemove', positionDrag);
1268        SL.removeEvent(document, 'mouseup', unlistenDrag); // clean up
1269        if(isGecko) SL.setStyle(SL.get('shadowbox_drag_layer'), 'cursor', '-moz-grab');
1270    };
1271
1272    /**
1273     * Positions an oversized image on drag.
1274     *
1275     * @param   {mixed}     ev      The drag event
1276     * @return  void
1277     * @private
1278     */
1279    var positionDrag = function(ev){
1280        var move_y = ev.clientY - drag.start_y;
1281        drag.start_y = drag.start_y + move_y;
1282        drag.y = Math.max(Math.min(0, drag.y + move_y), current_height - optimal_height); // y boundaries
1283        SL.setStyle(draggable, 'top', drag.y + 'px');
1284        var move_x = ev.clientX - drag.start_x;
1285        drag.start_x = drag.start_x + move_x;
1286        drag.x = Math.max(Math.min(0, drag.x + move_x), current_width - optimal_width); // x boundaries
1287        SL.setStyle(draggable, 'left', drag.x + 'px');
1288    };
1289
1290    /**
1291     * Loads the Shadowbox with the current piece.
1292     *
1293     * @return  void
1294     * @private
1295     */
1296    var loadContent = function(){
1297        var obj = current_gallery[current];
1298        if(!obj) return; // invalid
1299
1300        buildBars();
1301
1302        switch(obj.type){
1303            case 'img':
1304                // preload the image
1305                preloader = new Image();
1306                preloader.onload = function(){
1307                    // images default to image height and width
1308                    var h = obj.height ? parseInt(obj.height, 10) : preloader.height;
1309                    var w = obj.width ? parseInt(obj.width, 10) : preloader.width;
1310                    resizeContent(h, w, function(dims){
1311                        showBars(function(){
1312                            setContent({
1313                                tag:    'img',
1314                                height: dims.i_height,
1315                                width:  dims.i_width,
1316                                src:    obj.content,
1317                                style:  'position:absolute'
1318                            });
1319                            if(dims.enableDrag && options.handleLgImages == 'drag'){
1320                                // listen for drag
1321                                toggleDrag(true);
1322                                SL.setStyle(SL.get('shadowbox_drag_layer'), {
1323                                    height:     dims.i_height + 'px',
1324                                    width:      dims.i_width + 'px'
1325                                });
1326                            }
1327                            finishContent();
1328                        });
1329                    });
1330
1331                    preloader.onload = function(){}; // clear onload for IE
1332                };
1333                preloader.src = obj.content;
1334            break;
1335
1336            case 'swf':
1337            case 'flv':
1338            case 'qt':
1339            case 'wmp':
1340                var markup = Shadowbox.movieMarkup(obj);
1341                resizeContent(markup.height, markup.width, function(){
1342                    showBars(function(){
1343                        setContent(markup);
1344                        finishContent();
1345                    });
1346                });
1347            break;
1348
1349            case 'iframe':
1350                // iframes default to full viewport height and width
1351                var h = obj.height ? parseInt(obj.height, 10) : SL.getViewportHeight();
1352                var w = obj.width ? parseInt(obj.width, 10) : SL.getViewportWidth();
1353                var content = {
1354                    tag:            'iframe',
1355                    name:           'shadowbox_content',
1356                    height:         '100%',
1357                    width:          '100%',
1358                    frameborder:    '0',
1359                    marginwidth:    '0',
1360                    marginheight:   '0',
1361                    scrolling:      'auto',
1362                    allowfullscreen: 'true'
1363                };
1364
1365                resizeContent(h, w, function(dims){
1366                    showBars(function(){
1367                        setContent(content);
1368                        var win = (isIE)
1369                            ? SL.get('shadowbox_content').contentWindow
1370                            : window.frames['shadowbox_content'];
1371                        win.location = obj.content;
1372                        finishContent();
1373                    });
1374                });
1375            break;
1376
1377            case 'html':
1378            case 'inline':
1379                // HTML content defaults to full viewport height and width
1380                var h = obj.height ? parseInt(obj.height, 10) : SL.getViewportHeight();
1381                var w = obj.width ? parseInt(obj.width, 10) : SL.getViewportWidth();
1382                var content = {
1383                    tag:    'div',
1384                    cls:    'html', /* give special class to make scrollable */
1385                    html:   obj.content
1386                };
1387                resizeContent(h, w, function(){
1388                    showBars(function(){
1389                        setContent(content);
1390                        finishContent();
1391                    });
1392                });
1393            break;
1394
1395            default:
1396                // should never happen
1397                throw 'Shadowbox cannot open content of type ' + obj.type;
1398        }
1399
1400        // preload neighboring images
1401        if(current_gallery.length > 0){
1402            var next = current_gallery[current + 1];
1403            if(!next){
1404                next = current_gallery[0];
1405            }
1406            if(next.type == 'img'){
1407                var preload_next = new Image();
1408                preload_next.src = next.href;
1409            }
1410
1411            var prev = current_gallery[current - 1];
1412            if(!prev){
1413                prev = current_gallery[current_gallery.length - 1];
1414            }
1415            if(prev.type == 'img'){
1416                var preload_prev = new Image();
1417                preload_prev.src = prev.href;
1418            }
1419        }
1420    };
1421
1422    /**
1423     * Removes old content and sets the new content of the Shadowbox.
1424     *
1425     * @param   {Object}        obj     The content to set (appropriate to pass
1426     *                                  directly to Shadowbox.createHTML())
1427     * @return  {HTMLElement}           The newly appended element (or null if
1428     *                                  none is provided)
1429     * @private
1430     */
1431    var setContent = function(obj){
1432        var id = 'shadowbox_content';
1433        var content = SL.get(id);
1434        if(content){
1435            // remove old content first
1436            switch(content.tagName.toUpperCase()){
1437                case 'OBJECT':
1438                    // if we're in a gallery (i.e. changing and there's a new
1439                    // object) we want the LAST link object
1440                    var link = current_gallery[(obj ? current - 1 : current)];
1441                    if(link.type == 'wmp' && isIE){
1442                        try{
1443                            shadowbox_content.controls.stop(); // stop the movie
1444                            shadowbox_content.URL = 'non-existent.wmv'; // force player refresh
1445                            window.shadowbox_content = function(){}; // remove from window
1446                        }catch(e){}
1447                    }else if(link.type == 'qt' && isSafari){
1448                        try{
1449                            document.shadowbox_content.Stop(); // stop QT movie
1450                        }catch(e){}
1451                        // stop QT audio stream for movies that have not yet loaded
1452                        content.innerHTML = '';
1453                        // console.log(document.shadowbox_content);
1454                    }
1455                    setTimeout(function(){ // using setTimeout prevents browser crashes with WMP
1456                        SL.remove(content);
1457                    }, 10);
1458                break;
1459                case 'IFRAME':
1460                    SL.remove(content);
1461                    if(isGecko) delete window.frames[id]; // needed for Firefox
1462                break;
1463                default:
1464                    SL.remove(content);
1465            }
1466        }
1467        if(obj){
1468            if(!obj.id) obj.id = id;
1469            return appendHTML('shadowbox_body_inner', Shadowbox.createHTML(obj));
1470        }
1471        return null;
1472    };
1473
1474    /**
1475     * This function is used as the callback after the Shadowbox has been
1476     * positioned, resized, and loaded with content.
1477     *
1478     * @return  void
1479     * @private
1480     */
1481    var finishContent = function(){
1482        var obj = current_gallery[current];
1483        if(!obj) return; // invalid
1484        hideLoading(function(){
1485            listenKeyboard(true);
1486            // fire onFinish handler
1487            if(options.onFinish && typeof options.onFinish == 'function'){
1488                options.onFinish(obj);
1489            }
1490        });
1491    };
1492
1493    /**
1494     * Resizes and positions the content box using the given height and width.
1495     * If the callback parameter is missing, the transition will not be
1496     * animated. If the callback parameter is present, it will be passed the
1497     * new calculated dimensions object as its first parameter. Note: the height
1498     * and width here should represent the optimal height and width of the box.
1499     *
1500     * @param   {Function}  callback    A callback function to use when the
1501     *                                  resize completes
1502     * @return  void
1503     * @private
1504     */
1505    var resizeContent = function(height, width, callback){
1506        // update optimal height and width
1507        optimal_height = height;
1508        optimal_width = width;
1509        var resizable = RE.resize.test(current_gallery[current].type);
1510        var dims = getDimensions(optimal_height, optimal_width, resizable);
1511        if(callback){
1512            var cb = function(){ callback(dims); };
1513            switch(options.animSequence){
1514                case 'hw':
1515                    adjustHeight(dims.height, dims.top, true, function(){
1516                        adjustWidth(dims.width, true, cb);
1517                    });
1518                break;
1519                case 'wh':
1520                    adjustWidth(dims.width, true, function(){
1521                        adjustHeight(dims.height, dims.top, true, cb);
1522                    });
1523                break;
1524                default: // sync
1525                    adjustWidth(dims.width, true);
1526                    adjustHeight(dims.height, dims.top, true, cb);
1527            }
1528        }else{ // window resize
1529            adjustWidth(dims.width, false);
1530            adjustHeight(dims.height, dims.top, false);
1531            // resize content images & flash in 'resize' mode
1532            if(options.handleLgImages == 'resize' && resizable){
1533                var content = SL.get('shadowbox_content');
1534                if(content){ // may be animating, not present
1535                    content.height = dims.i_height;
1536                    content.width = dims.i_width;
1537                }
1538            }
1539        }
1540    };
1541
1542    /**
1543     * Calculates the dimensions for Shadowbox, taking into account the borders,
1544     * margins, and surrounding elements of the shadowbox_body. If the image
1545     * is still to large for Shadowbox, and options.handleLgImages is 'resize',
1546     * the resized dimensions will be returned (preserving the original aspect
1547     * ratio). Otherwise, the originally calculated dimensions will be returned.
1548     * The returned object will have the following properties:
1549     *
1550     * - height: The height to use for shadowbox_body_inner
1551     * - width: The width to use for shadowbox
1552     * - i_height: The height to use for resizable content
1553     * - i_width: The width to use for resizable content
1554     * - top: The top to use for shadowbox
1555     * - enableDrag: True if dragging should be enabled (image is oversized)
1556     *
1557     * @param   {Number}    o_height    The optimal height
1558     * @param   {Number}    o_width     The optimal width
1559     * @param   {Boolean}   resizable   True if the content is able to be
1560     *                                  resized. Defaults to false.
1561     * @return  {Object}                The resize dimensions (see above)
1562     * @private
1563     */
1564    var getDimensions = function(o_height, o_width, resizable){
1565        if(typeof resizable == 'undefined') resizable = false;
1566
1567        var height = o_height = parseInt(o_height);
1568        var width = o_width = parseInt(o_width);
1569        var shadowbox_b = SL.get('shadowbox_body');
1570
1571        // calculate the max height
1572        var view_height = SL.getViewportHeight();
1573        var extra_height = parseInt(SL.getStyle(shadowbox_b, 'border-top-width'), 10)
1574            + parseInt(SL.getStyle(shadowbox_b, 'border-bottom-width'), 10)
1575            + parseInt(SL.getStyle(shadowbox_b, 'margin-top'), 10)
1576            + parseInt(SL.getStyle(shadowbox_b, 'margin-bottom'), 10)
1577            + getComputedHeight(SL.get('shadowbox_title'))
1578            + getComputedHeight(SL.get('shadowbox_toolbar'))
1579            + (2 * options.viewportPadding);
1580        if((height + extra_height) >= view_height){
1581            height = view_height - extra_height;
1582        }
1583
1584        // calculate the max width
1585        var view_width = SL.getViewportWidth();
1586        var extra_body_width = parseInt(SL.getStyle(shadowbox_b, 'border-left-width'), 10)
1587            + parseInt(SL.getStyle(shadowbox_b, 'border-right-width'), 10)
1588            + parseInt(SL.getStyle(shadowbox_b, 'margin-left'), 10)
1589            + parseInt(SL.getStyle(shadowbox_b, 'margin-right'), 10);
1590        var extra_width = extra_body_width + (2 * options.viewportPadding);
1591        if((width + extra_width) >= view_width){
1592            width = view_width - extra_width;
1593        }
1594
1595        // handle oversized images & flash
1596        var enableDrag = false;
1597        var i_height = o_height;
1598        var i_width = o_width;
1599        var handle = options.handleLgImages;
1600        if(resizable && (handle == 'resize' || handle == 'drag')){
1601            var change_h = (o_height - height) / o_height;
1602            var change_w = (o_width - width) / o_width;
1603            if(handle == 'resize'){
1604                if(change_h > change_w){
1605                    width = Math.round((o_width / o_height) * height);
1606                }else if(change_w > change_h){
1607                    height = Math.round((o_height / o_width) * width);
1608                }
1609                // adjust image height or width accordingly
1610                i_width = width;
1611                i_height = height;
1612            }else{
1613                // drag on oversized images only
1614                var link = current_gallery[current];
1615                if(link) enableDrag = link.type == 'img' && (change_h > 0 || change_w > 0);
1616            }
1617        }
1618
1619        return {
1620            height: height,
1621            width: width + extra_body_width,
1622            i_height: i_height,
1623            i_width: i_width,
1624            top: ((view_height - (height + extra_height)) / 2) + options.viewportPadding,
1625            enableDrag: enableDrag
1626        };
1627    };
1628
1629    /**
1630     * Centers Shadowbox vertically in the viewport. Needs to be called on
1631     * scroll in IE6 because it does not support fixed positioning.
1632     *
1633     * @return  void
1634     * @private
1635     */
1636    var centerVertically = function(){
1637        var shadowbox = SL.get('shadowbox');
1638        var scroll = document.documentElement.scrollTop;
1639        var s_top = scroll + Math.round((SL.getViewportHeight() - (shadowbox.offsetHeight || 0)) / 2);
1640        SL.setStyle(shadowbox, 'top', s_top + 'px');
1641    };
1642
1643    /**
1644     * Adjusts the height of shadowbox_body_inner and centers Shadowbox
1645     * vertically in the viewport.
1646     *
1647     * @param   {Number}    height      The height of shadowbox_body_inner
1648     * @param   {Number}    top         The top of the Shadowbox
1649     * @param   {Boolean}   animate     True to animate the transition
1650     * @param   {Function}  callback    A callback to use when the animation completes
1651     * @return  void
1652     * @private
1653     */
1654    var adjustHeight = function(height, top, animate, callback){
1655        height = parseInt(height);
1656
1657        // update current_height
1658        current_height = height;
1659
1660        // adjust the height
1661        var sbi = SL.get('shadowbox_body_inner');
1662        if(animate && options.animate){
1663            SL.animate(sbi, {
1664                height: { to: height }
1665            }, options.resizeDuration, callback);
1666        }else{
1667            SL.setStyle(sbi, 'height', height + 'px');
1668            if(typeof callback == 'function') callback();
1669        }
1670
1671        // manually adjust the top because we're using fixed positioning in IE6
1672        if(absolute_pos){
1673            // listen for scroll so we can adjust
1674            centerVertically();
1675            SL.addEvent(window, 'scroll', centerVertically);
1676
1677            // add scroll to top
1678            top += document.documentElement.scrollTop;
1679        }
1680
1681        // adjust the top
1682        var shadowbox = SL.get('shadowbox');
1683        if(animate && options.animate){
1684            SL.animate(shadowbox, {
1685                top: { to: top }
1686            }, options.resizeDuration);
1687        }else{
1688            SL.setStyle(shadowbox, 'top', top + 'px');
1689        }
1690    };
1691
1692    /**
1693     * Adjusts the width of shadowbox.
1694     *
1695     * @param   {Number}    width       The width to use
1696     * @param   {Boolean}   animate     True to animate the transition
1697     * @param   {Function}  callback    A callback to use when the animation completes
1698     * @return  void
1699     * @private
1700     */
1701    var adjustWidth = function(width, animate, callback){
1702        width = parseInt(width);
1703
1704        // update current_width
1705        current_width = width;
1706
1707        var shadowbox = SL.get('shadowbox');
1708        if(animate && options.animate){
1709            SL.animate(shadowbox, {
1710                width: { to: width }
1711            }, options.resizeDuration, callback);
1712        }else{
1713            SL.setStyle(shadowbox, 'width', width + 'px');
1714            if(typeof callback == 'function') callback();
1715        }
1716    };
1717
1718    /**
1719     * Sets up a listener on the document for keystrokes.
1720     *
1721     * @param   {Boolean}   on      True to enable the listner, false to turn
1722     *                              it off
1723     * @return  void
1724     * @private
1725     */
1726    var listenKeyboard = function(on){
1727        if(!options.enableKeys) return;
1728        if(on){
1729            document.onkeydown = handleKey;
1730        }else{
1731            document.onkeydown = '';
1732        }
1733    };
1734
1735    /**
1736     * Asserts the given key or code is present in the array of valid keys.
1737     *
1738     * @param   {Array}     valid       An array of valid keys and codes
1739     * @param   {String}    key         The character that was pressed
1740     * @param   {Number}    code        The key code that was pressed
1741     * @return  {Boolean}               True if the key is valid
1742     * @private
1743     */
1744    var assertKey = function(valid, key, code){
1745        return (valid.indexOf(key) != -1 || valid.indexOf(code) != -1);
1746    };
1747
1748    /**
1749     * A listener function that will act on a key pressed.
1750     *
1751     * @param   {Event}     e       The event object
1752     * @return  void
1753     * @private
1754     */
1755    var handleKey = function(e){
1756        var code = e ? e.which : event.keyCode;
1757        var key = String.fromCharCode(code).toLowerCase();
1758        if(assertKey(options.keysClose, key, code)){
1759            Shadowbox.close();
1760        }else if(assertKey(options.keysPrev, key, code)){
1761            Shadowbox.previous();
1762        }else if(assertKey(options.keysNext, key, code)){
1763            Shadowbox.next();
1764        }
1765    };
1766
1767    /**
1768     * Shows and hides elements that are troublesome for modal overlays.
1769     *
1770     * @param   {Boolean}   on      True to show the elements, false otherwise
1771     * @return  void
1772     * @private
1773     */
1774    var toggleTroubleElements = function(on){
1775        var vis = (on ? 'visible' : 'hidden');
1776        var selects = document.getElementsByTagName('select');
1777        for(i = 0, len = selects.length; i < len; ++i){
1778            selects[i].style.visibility = vis;
1779        }
1780/*
1781        var objects = document.getElementsByTagName('object');
1782        for(i = 0, len = objects.length; i < len; ++i){
1783            objects[i].style.visibility = vis;
1784        }
1785        var embeds = document.getElementsByTagName('embed');
1786        for(i = 0, len = embeds.length; i < len; ++i){
1787            embeds[i].style.visibility = vis;
1788        }
1789        */
1790    };
1791
1792    /**
1793     * Fills the Shadowbox with the loading skin.
1794     *
1795     * @return  void
1796     * @private
1797     */
1798    var showLoading = function(){
1799        var loading = SL.get('shadowbox_loading');
1800        overwriteHTML(loading, String.format(options.skin.loading,
1801            options.assetURL + options.loadingImage,
1802            options.text.loading,
1803            options.text.cancel));
1804        loading.style.visibility = 'visible';
1805    };
1806
1807    /**
1808     * Hides the Shadowbox loading skin.
1809     *
1810     * @param   {Function}  callback        The callback function to call after
1811     *                                      hiding the loading skin
1812     * @return  void
1813     * @private
1814     */
1815    var hideLoading = function(callback){
1816        var t = current_gallery[current].type;
1817        var anim = (t == 'img' || t == 'html'); // fade on images & html
1818        var loading = SL.get('shadowbox_loading');
1819        if(anim){
1820            fadeOut(loading, 0.35, callback);
1821        }else{
1822            loading.style.visibility = 'hidden';
1823            callback();
1824        }
1825    };
1826
1827    /**
1828     * Sets the size of the overlay to the size of the document.
1829     *
1830     * @return  void
1831     * @private
1832     */
1833    var resizeOverlay = function(){
1834        var overlay = SL.get('shadowbox_overlay');
1835        SL.setStyle(overlay, {
1836            height: '100%',
1837            width: '100%'
1838        });
1839        SL.setStyle(overlay, 'height', SL.getDocumentHeight() + 'px');
1840        if(!isSafari3){
1841            // Safari3 includes vertical scrollbar in SL.getDocumentWidth()!
1842            // Leave overlay width at 100% for now...
1843            SL.setStyle(overlay, 'width', SL.getDocumentWidth() + 'px');
1844        }
1845    };
1846
1847    /**
1848     * Used to determine if the pre-made overlay background image is needed
1849     * instead of using the trasparent background overlay. A pre-made background
1850     * image is used for all but image pieces in FF Mac because it has problems
1851     * displaying correctly if the background layer is not 100% opaque. When
1852     * displaying a gallery, if any piece in the gallery meets these criteria,
1853     * the pre-made background image will be used.
1854     *
1855     * @return  {Boolean}       Whether or not an overlay image is needed
1856     * @private
1857     */
1858    var checkOverlayImgNeeded = function(){
1859        if(!(isGecko && isMac)) return false;
1860        for(var i = 0, len = current_gallery.length; i < len; ++i){
1861            if(!RE.overlay.exec(current_gallery[i].type)) return true;
1862        }
1863        return false;
1864    };
1865
1866    /**
1867     * Activates (or deactivates) the Shadowbox overlay. If a callback function
1868     * is provided, we know we're activating. Otherwise, deactivate the overlay.
1869     *
1870     * @param   {Function}  callback    A callback to call after activation
1871     * @return  void
1872     * @private
1873     */
1874    var toggleOverlay = function(callback){
1875        var overlay = SL.get('shadowbox_overlay');
1876        if(overlay_img_needed == null){
1877            overlay_img_needed = checkOverlayImgNeeded();
1878        }
1879
1880        if(callback){
1881            resizeOverlay(); // size the overlay before showing
1882            if(overlay_img_needed){
1883                SL.setStyle(overlay, {
1884                    visibility:         'visible',
1885                    backgroundColor:    'transparent',
1886                    backgroundImage:    'url(' + options.assetURL + options.overlayBgImage + ')',
1887                    backgroundRepeat:   'repeat',
1888                    opacity:            1
1889                });
1890                callback();
1891            }else{
1892                SL.setStyle(overlay, {
1893                    visibility:         'visible',
1894                    backgroundColor:    options.overlayColor,
1895                    backgroundImage:    'none'
1896                });
1897                fadeIn(overlay, options.overlayOpacity, options.fadeDuration,
1898                    callback);
1899            }
1900        }else{
1901            if(overlay_img_needed){
1902                SL.setStyle(overlay, 'visibility', 'hidden');
1903            }else{
1904                fadeOut(overlay, options.fadeDuration);
1905            }
1906
1907            // reset for next time
1908            overlay_img_needed = null;
1909        }
1910    };
1911
1912    /**
1913     * Initializes the Shadowbox environment. Appends Shadowbox' HTML to the
1914     * document and sets up listeners on the window and overlay element.
1915     *
1916     * @param   {Object}    opts    The default options to use
1917     * @return  void
1918     * @public
1919     * @static
1920     */
1921    Shadowbox.init = function(opts){
1922        if(initialized) return; // don't initialize twice
1923        options = apply(options, opts || {});
1924
1925        // add markup
1926        appendHTML(document.body, options.skin.main);
1927
1928        // compile file type regular expressions here for speed
1929        RE.img = new RegExp('\.(' + options.ext.img.join('|') + ')\s*$', 'i');
1930        RE.qt = new RegExp('\.(' + options.ext.qt.join('|') + ')\s*$', 'i');
1931        RE.wmp = new RegExp('\.(' + options.ext.wmp.join('|') + ')\s*$', 'i');
1932        RE.qtwmp = new RegExp('\.(' + options.ext.qtwmp.join('|') + ')\s*$', 'i');
1933        RE.iframe = new RegExp('\.(' + options.ext.iframe.join('|') + ')\s*$', 'i');
1934
1935        // handle window resize events
1936        var id = null;
1937        var resize = function(){
1938            clearInterval(id);
1939            id = null;
1940            resizeOverlay();
1941            resizeContent(optimal_height, optimal_width);
1942        };
1943        SL.addEvent(window, 'resize', function(){
1944            if(activated){
1945                // use event buffering to prevent jerky window resizing
1946                if(id){
1947                    clearInterval(id);
1948                    id = null;
1949                }
1950                if(!id) id = setInterval(resize, 50);
1951            }
1952        });
1953
1954        if(options.listenOverlay){
1955            // add a listener to the overlay
1956            SL.addEvent(SL.get('shadowbox_overlay'), 'click', Shadowbox.close);
1957        }
1958
1959        // adjust some positioning if needed
1960        if(absolute_pos){
1961            // give the container absolute positioning
1962            SL.setStyle(SL.get('shadowbox_container'), 'position', 'absolute');
1963            // give shadowbox_body "layout"...whatever that is
1964            SL.setStyle('shadowbox_body', 'zoom', 1);
1965            // need to listen to the container element because it c
1965overs the top
1966            // half of the page
1967            SL.addEvent(SL.get('shadowbox_container'), 'click', function(e){
1968                var target = SL.getTarget(e);
1969                if(target.id && target.id == 'shadowbox_container') Shadowbox.close();
1970            });
1971        }
1972
1973        // skip setup, will need to be done manually later
1974        if(!options.skipSetup) Shadowbox.setup();
1975        initialized = true;
1976    };
1977
1978    /**
1979     * Sets up listeners on the given links that will trigger Shadowbox. If no
1980     * links are given, this method will set up every anchor element on the page
1981     * with the appropriate rel attribute. Note: Because AREA elements do not
1982     * support the rel attribute, they must be explicitly passed to this method.
1983     *
1984     * @param   {Array}     links       An array (or array-like) list of anchor
1985     *                                  and/or area elements to set up
1986     * @param   {Object}    opts        Some options to use for the given links
1987     * @return  void
1988     * @public
1989     * @static
1990     */
1991    Shadowbox.setup = function(links, opts){
1992        // get links if none specified
1993        if(!links){
1994            var links = [];
1995            var a = document.getElementsByTagName('a'), rel;
1996            for(var i = 0, len = a.length; i < len; ++i){
1997                rel = a[i].getAttribute('rel');
1998                if(rel && RE.rel.test(rel)) links[links.length] = a[i];
1999            }
2000        }else if(!links.length){
2001            links = [links]; // one link
2002        }
2003
2004        var link;
2005        for(var i = 0, len = links.length; i < len; ++i){
2006            link = links[i];
2007            if(typeof link.shadowboxCacheKey == 'undefined'){
2008                // assign cache key expando
2009                // use integer primitive to avoid memory leak in IE
2010                link.shadowboxCacheKey = cache.length;
2011                SL.addEvent(link, 'click', handleClick); // add listener
2012            }
2013            cache[link.shadowboxCacheKey] = this.buildCacheObj(link, opts);
2014        }
2015    };
2016
2017    /**
2018     * Builds an object from the original link element data to store in cache.
2019     * These objects contain (most of) the following keys:
2020     *
2021     * - el: the link element
2022     * - title: the linked file title
2023     * - type: the linked file type
2024     * - content: the linked file's URL
2025     * - gallery: the gallery the file belongs to (optional)
2026     * - height: the height of the linked file (only necessary for movies)
2027     * - width: the width of the linked file (only necessary for movies)
2028     * - options: custom options to use (optional)
2029     *
2030     * @param   {HTMLElement}   link    The link element to process
2031     * @return  {Object}                An object representing the link
2032     * @public
2033     * @static
2034     */
2035    Shadowbox.buildCacheObj = function(link, opts){
2036        var href = link.href; // don't use getAttribute() here
2037        var o = {
2038            el:         link,
2039            title:      link.getAttribute('title'),
2040            type:       getPlayerType(href),
2041            options:    apply({}, opts || {}), // break the reference
2042            content:    href
2043        };
2044
2045        // remove link-level options from top-level options
2046        var opt, l_opts = ['title', 'type', 'height', 'width', 'gallery'];
2047        for(var i = 0, len = l_opts.length; i < len; ++i){
2048            opt = l_opts[i];
2049            if(typeof o.options[opt] != 'undefined'){
2050                o[opt] = o.options[opt];
2051                delete o.options[opt];
2052            }
2053        }
2054
2055        // HTML options always trump JavaScript options, so do these last
2056        var rel = link.getAttribute('rel');
2057        if(rel){
2058            // extract gallery name from shadowbox[name] format
2059            var match = rel.match(RE.gallery);
2060            if(match) o.gallery = escape(match[2]);
2061
2062            // other parameters
2063            var params = rel.split(';');
2064            for(var i = 0, len = params.length; i < len; ++i){
2065                match = params[i].match(RE.param);
2066                if(match){
2067                    if(match[1] == 'options'){
2068                        eval('o.options = apply(o.options, ' + match[2] + ')');
2069                    }else{
2070                        o[match[1]] = match[2];
2071                    }
2072                }
2073            }
2074        }
2075
2076        return o;
2077    };
2078
2079    /**
2080     * Applies the given set of options to those currently in use. Note: Options
2081     * will be reset on Shadowbox.open() so this function is only useful after
2082     * it has already been called (while Shadowbox is open).
2083     *
2084     * @param   {Object}    opts        The options to apply
2085     * @return  void
2086     * @public
2087     * @static
2088     */
2089    Shadowbox.applyOptions = function(opts){
2090        if(opts){
2091            // use apply here to break references
2092            default_options = apply({}, options); // store default options
2093            options = apply(options, opts); // apply options
2094        }
2095    };
2096
2097    /**
2098     * Reverts Shadowbox' options to the last default set in use before
2099     * Shadowbox.applyOptions() was called.
2100     *
2101     * @return  void
2102     * @public
2103     * @static
2104     */
2105    Shadowbox.revertOptions = function(){
2106        if(default_options){
2107            options = default_options; // revert to default options
2108            default_options = null; // erase for next time
2109        }
2110    };
2111
2112    /**
2113     * Opens the given object in Shadowbox. This object may be either an
2114     * anchor/area element, or an object similar to the one created by
2115     * Shadowbox.buildCacheObj().
2116     *
2117     * @param   {mixed}     obj         The object or link element that defines
2118     *                                  what to display
2119     * @return  void
2120     * @public
2121     * @static
2122     */
2123    Shadowbox.open = function(obj, opts){
2124        if(activated) return; // already open
2125        activated = true;
2126
2127        // is it a link?
2128        if(isLink(obj)){
2129            if(typeof obj.shadowboxCacheKey == 'undefined' || typeof cache[obj.shadowboxCacheKey] == 'undefined'){
2130                // link element that hasn't been set up before
2131                // create an object on-the-fly
2132                obj = this.buildCacheObj(obj, opts);
2133            }else{
2134                // link element that has been set up before, get from cache
2135                obj = cache[obj.shadowboxCacheKey];
2136            }
2137        }
2138
2139        this.revertOptions();
2140        if(obj.options || opts){
2141            // use apply here to break references
2142            this.applyOptions(apply(apply({}, obj.options || {}), opts || {}));
2143        }
2144
2145        // update current & current_gallery
2146        setupGallery(obj);
2147
2148        // anything to display?
2149        if(current_gallery.length){
2150            // fire onOpen hook
2151            if(options.onOpen && typeof options.onOpen == 'function'){
2152                options.onOpen(obj);
2153            }
2154
2155            // display:block here helps with correct dimension calculations
2156            SL.setStyle(SL.get('shadowbox'), 'display', 'block');
2157
2158            toggleTroubleElements(false);
2159            var dims = getDimensions(options.initialHeight, options.initialWidth);
2160            adjustHeight(dims.height, dims.top);
2161            adjustWidth(dims.width);
2162            hideBars(false);
2163
2164            // show the overlay and load the content
2165            toggleOverlay(function(){
2166                SL.setStyle(SL.get('shadowbox'), 'visibility', 'visible');
2167                showLoading();
2168                loadContent();
2169            });
2170        }
2171    };
2172
2173    /**
2174     * Jumps to the piece in the current gallery with index num.
2175     *
2176     * @param   {Number}    num     The gallery index to view
2177     * @return  void
2178     * @public
2179     * @static
2180     */
2181    Shadowbox.change = function(num){
2182        if(!current_gallery) return; // no current gallery
2183        if(!current_gallery[num]){ // index does not exist
2184            if(!options.continuous){
2185                return;
2186            }else{
2187                num = (num < 0) ? (current_gallery.length - 1) : 0; // loop
2188            }
2189        }
2190
2191        // update current
2192        current = num;
2193
2194        // stop listening for drag
2195        toggleDrag(false);
2196        // empty the content
2197        setContent(null);
2198        // turn this back on when done
2199        listenKeyboard(false);
2200
2201        // fire onChange handler
2202        if(options.onChange && typeof options.onChange == 'function'){
2203            options.onChange(current_gallery[current]);
2204        }
2205
2206        showLoading();
2207        hideBars(loadContent);
2208    };
2209
2210    /**
2211     * Jumps to the next piece in the gallery.
2212     *
2213     * @return  {Boolean}       True if the gallery changed to next item, false
2214     *                          otherwise
2215     * @public
2216     * @static
2217     */
2218    Shadowbox.next = function(){
2219        return this.change(current + 1);
2220    };
2221
2222    /**
2223     * Jumps to the previous piece in the gallery.
2224     *
2225     * @return  {Boolean}       True if the gallery changed to previous item,
2226     *                          false otherwise
2227     * @public
2228     * @static
2229     */
2230    Shadowbox.previous = function(){
2231        return this.change(current - 1);
2232    };
2233
2234    /**
2235     * Deactivates Shadowbox.
2236     *
2237     * @return  void
2238     * @public
2239     * @static
2240     */
2241    Shadowbox.close = function(){
2242        if(!activated) return; // already closed
2243
2244        // stop listening for keys
2245        listenKeyboard(false);
2246        // hide
2247        SL.setStyle(SL.get('shadowbox'), {
2248            display: 'none',
2249            visibility: 'hidden'
2250        });
2251        // stop listening for scroll on IE
2252        if(absolute_pos) SL.removeEvent(window, 'scroll', centerVertically);
2253        // stop listening for drag
2254        toggleDrag(false);
2255        // empty the content
2256        setContent(null);
2257        // prevent old image requests from loading
2258        if(preloader){
2259            preloader.onload = function(){};
2260            preloader = null;
2261        }
2262        // hide the overlay
2263        toggleOverlay(false);
2264        // turn on trouble elements
2265        toggleTroubleElements(true);
2266
2267        // fire onClose handler
2268        if(options.onClose && typeof options.onClose == 'function'){
2269            options.onClose(current_gallery[current]);
2270        }
2271
2272        activated = false;
2273    };
2274
2275    /**
2276     * Clears Shadowbox' cache and removes listeners and expandos from all
2277     * cached link elements. May be used to completely reset Shadowbox in case
2278     * links on a page change.
2279     *
2280     * @return  void
2281     * @public
2282     * @static
2283     */
2284    Shadowbox.clearCache = function(){
2285        for(var i = 0, len = cache.length; i < len; ++i){
2286            if(cache[i].el){
2287                SL.removeEvent(cache[i].el, 'click', handleClick);
2288                delete cache[i].shadowboxCacheKey;
2289            }
2290        }
2291        cache = [];
2292    };
2293
2294    /**
2295     * Generates the markup necessary to embed the movie file with the given
2296     * link element. This markup will be browser-specific. Useful for generating
2297     * the media test suite.
2298     *
2299     * @param   {HTMLElement}   link        The link to the media file
2300     * @return  {Object}                    The proper markup to use (see above)
2301     * @public
2302     * @static
2303     */
2304    Shadowbox.movieMarkup = function(obj){
2305        // movies default to 300x300 pixels
2306        var h = obj.height ? parseInt(obj.height, 10) : 300;
2307        var w = obj.width ? parseInt(obj.width, 10) : 300;
2308
2309        var autoplay = options.autoplayMovies;
2310        var controls = options.showMovieControls;
2311        if(obj.options){
2312            if(obj.options.autoplayMovies != null){
2313                autoplay = obj.options.autoplayMovies;
2314            }
2315            if(obj.options.showMovieControls != null){
2316                controls = obj.options.showMovieControls;
2317            }
2318        }
2319
2320        var markup = {
2321            tag:    'object',
2322            name:   'shadowbox_content'
2323        };
2324
2325        switch(obj.type){
2326            case 'swf':
2327                var dims = getDimensions(h, w, true);
2328                h = dims.height;
2329                w = dims.width;
2330                markup.type = 'application/x-shockwave-flash';
2331                markup.data = obj.content;
2332                markup.children = [
2333                    { tag: 'param', name: 'movie', value: obj.content }
2334                ];
2335            break;
2336            case 'flv':
2337                autoplay = autoplay ? 'true' : 'false';
2338                var showicons = 'false';
2339                var a = h/w; // aspect ratio
2340                if(controls){
2341                    showicons = 'true';
2342                    h += 20; // height of JW FLV player controller
2343                }
2344                var dims = getDimensions(h, h/a, true); // resize
2345                h = dims.height;
2346                w = (h-(controls?20:0))/a; // maintain aspect ratio
2347                var flashvars = [
2348                    'file=' + obj.content,
2349                    'height=' + h,
2350                    'width=' + w,
2351                    'autostart=' + autoplay,
2352                    'displayheight=' + (h - (controls?20:0)),
2353                    'showicons=' + showicons,
2354                    'backcolor=000000&amp;frontcolor=CCCCCC&amp;lightcolor=557722'
2355                ];
2356                markup.type = 'application/x-shockwave-flash';
2357                markup.data = options.assetURL + options.flvPlayer;
2358                markup.children = [
2359                    { tag: 'param', name: 'movie', value: options.assetURL + options.flvPlayer },
2360                    { tag: 'param', name: 'flashvars', value: flashvars.join('&amp;') },
2361                    { tag: 'param', name: 'allowfullscreen', value: 'true' }
2362                ];
2363            break;
2364            case 'qt':
2365                autoplay = autoplay ? 'true' : 'false';
2366                if(controls){
2367                    controls = 'true';
2368                    h += 16; // height of QuickTime controller
2369                }else{
2370                    controls = 'false';
2371                }
2372                markup.children = [
2373                    { tag: 'param', name: 'src', value: obj.content },
2374                    { tag: 'param', name: 'scale', value: 'aspect' },
2375                    { tag: 'param', name: 'controller', value: controls },
2376                    { tag: 'param', name: 'autoplay', value: autoplay }
2377                ];
2378                if(isIE){
2379                    markup.classid = 'clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B';
2380                    markup.codebase = 'http://www.apple.com/qtactivex/qtplugin.cab#version=6,0,2,0';
2381                }else{
2382                    markup.type = 'video/quicktime';
2383                    markup.data = obj.content;
2384                }
2385            break;
2386            case 'wmp':
2387                autoplay = autoplay ? 1 : 0;
2388                markup.children = [
2389                    { tag: 'param', name: 'autostart', value: autoplay }
2390                ];
2391                if(isIE){
2392                    if(controls){
2393                        controls = 'full';
2394                        h += 70; // height of WMP controller in IE
2395                    }else{
2396                        controls = 'none';
2397                    }
2398                    // markup.type = 'application/x-oleobject';
2399                    markup.classid = 'clsid:6BF52A52-394A-11d3-B153-00C04F79FAA6';
2400                    markup.children[markup.children.length] = { tag: 'param', name: 'url', value: obj.content };
2401                    markup.children[markup.children.length] = { tag: 'param', name: 'uimode', value: controls };
2402                }else{
2403                    if(controls){
2404                        controls = 1;
2405                        h += 45; // height of WMP controller in non-IE
2406                    }else{
2407                        controls = 0;
2408                    }
2409                    markup.type = 'video/x-ms-wmv';
2410                    markup.data = obj.content;
2411                    markup.children[markup.children.length] = { tag: 'param', name: 'showcontrols', value: controls };
2412                }
2413            break;
2414        }
2415
2416        markup.height = h; // new height includes controller
2417        markup.width = w;
2418
2419        return markup;
2420    };
2421
2422    /**
2423     * Creates an HTML string from an object representing HTML elements. Based
2424     * on Ext.DomHelper's createHtml.
2425     *
2426     * @param   {Object}    obj     The HTML definition object
2427     * @return  {String}            An HTML string
2428     * @public
2429     * @static
2430     */
2431    Shadowbox.createHTML = function(obj){
2432        var html = '<' + obj.tag;
2433        for(var attr in obj){
2434            if(attr == 'tag' || attr == 'html' || attr == 'children') continue;
2435            if(attr == 'cls'){
2436                html += ' class="' + obj['cls'] + '"';
2437            }else{
2438                html += ' ' + attr + '="' + obj[attr] + '"';
2439            }
2440        }
2441        if(RE.empty.test(obj.tag)){
2442            html += '/>\n';
2443        }else{
2444            html += '>\n';
2445            var cn = obj.children;
2446            if(cn){
2447                for(var i = 0, len = cn.length; i < len; ++i){
2448                    html += this.createHTML(cn[i]);
2449                }
2450            }
2451            if(obj.html) html += obj.html;
2452            html += '</' + obj.tag + '>\n';
2453        }
2454        return html;
2455    };
2456
2457    /**
2458     * Gets an object that lists which plugins are supported by the client. The
2459     * keys of this object will be:
2460     *
2461     * - fla: Adobe Flash Player
2462     * - qt: QuickTime Player
2463     * - wmp: Windows Media Player
2464     * - f4m: Flip4Mac QuickTime Player
2465     *
2466     * @return  {Object}        The plugins object
2467     * @public
2468     * @static
2469     */
2470    Shadowbox.getPlugins = function(){
2471        return plugins;
2472    };
2473
2474    /**
2475     * Gets the current options object in use.
2476     *
2477     * @return  {Object}        The options object
2478     * @public
2479     * @static
2480     */
2481    Shadowbox.getOptions = function(){
2482        return options;
2483    };
2484
2485    /**
2486     * Gets the current gallery object.
2487     *
2488     * @return  {Object}        The current gallery item
2489     * @public
2490     * @static
2491     */
2492    Shadowbox.getCurrent = function(){
2493        return current_gallery[current];
2494    };
2495
2496    /**
2497     * Gets the current version number of Shadowbox.
2498     *
2499     * @return  {String}        The current version
2500     * @public
2501     * @static
2502     */
2503    Shadowbox.getVersion = function(){
2504        return version;
2505    };
2506
2507})();
2508
2509/**
2510 * Finds the index of the given object in this array.
2511 *
2512 * @param   {mixed}     o   The object to search for
2513 * @return  {Number}        The index of the given object
2514 * @public
2515 */
2516Array.prototype.indexOf = Array.prototype.indexOf || function(o){
2517    for(var i = 0, len = this.length; i < len; ++i){
2518        if(this[i] == o) return i;
2519    }
2520    return -1;
2521};
2522
2523/**
2524 * Formats a string with the given parameters. The string for format must have
2525 * placeholders that correspond to the numerical index of the arguments passed
2526 * in surrounded by curly braces (e.g. 'Some {0} string {1}').
2527 *
2528 * @param   {String}    format      The string to format
2529 * @param   ...                     The parameters to put inside the string
2530 * @return  {String}                The string with the specified parameters
2531 *                                  replaced
2532 * @public
2533 * @static
2534 */
2535String.format = String.format || function(format){
2536    var args = Array.prototype.slice.call(arguments, 1);
2537    return format.replace(/\{(\d+)\}/g, function(m, i){
2538        return args[i];
2539    });
2540};

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.