/** * The Shadowbox class. * * This file is part of Shadowbox. * * Shadowbox is an online media viewer application that supports all of the * web's most popular media publishing formats. Shadowbox is written entirely * in JavaScript and CSS and is highly customizable. Using Shadowbox, website * authors can showcase a wide assortment of media in all major browsers without * navigating users away from the linking page. * * You should have received a license with this distribution explaining the terms * under which Shadowbox may be used. If you did not, you may obtain a copy of the * license at http://shadowbox-js.com/LICENSE * * @author      Michael J. I. Jackson <michael@mjijackson.com> * @copyright   2007-2009 Michael J. I. Jackson * @version     SVN: $Id: shadowbox.js 20M 2009-04-29 04:26:23Z (local) $ *//** * The Shadowbox class. Used to display different media on a web page using a * Lightbox-like effect. * * Known issues: * * - Location.toString exception in FF3 when loading Flash content into an *   iframe (such as a YouTube video). Known Flash bug, will not be fixed. *   http://bugs.adobe.com/jira/browse/FP-561 * * Useful resources: * * - http://www.alistapart.com/articles/byebyeembed * - http://www.w3.org/TR/html401/struct/objects.html * - http://www.dyn-web.com/dhtml/iframes/ * - http://www.apple.com/quicktime/player/specs.html * - http://www.apple.com/quicktime/tutorials/embed2.html * - http://www.howtocreate.co.uk/wrongWithIE/?chapter=navigator.plugins * - http://msdn.microsoft.com/en-us/library/ms532969.aspx * - http://support.microsoft.com/kb/316992 * - http://www.alistapart.com/articles/flashembedcagematch */var Shadowbox = function(){    var ua = navigator.userAgent.toLowerCase(),    // the Shadowbox object    S = {        /**         * The current version of Shadowbox.         *         * @var     String         * @public         */        version: "3.0b",        /**         * The name of the adapter currently being used.         *         * @var     String         * @public         */        adapter: null,        /**         * The array index of the current gallery that is currently being viewed.         *         * @var     Number         * @public         */        current: -1,        /**         * An array containing the gallery objects currently being viewed. In the         * case of non-gallery items, this will only hold one object.         *         * @var     Array         * @public         */        gallery: [],        /**         * A cache of options for links that have been set up for use with         * Shadowbox.         *         * @var     Array         * @public         */        cache: [],        /**         * The current content object.         *         * @var     Object         * @public         */        content: null,        /**         * Holds the current dimensions of Shadowbox as calculated by its skin.         * Contains the following properties:         *         * - height: The total height of #sb-wrapper (including title & info bars)         * - width: The total width of #sb-wrapper         * - inner_h: The height of #sb-body         * - inner_w: The width of #sb-body         * - top: The top to use for #sb-wrapper         * - left: The left to use for #sb-wrapper         * - oversized: True if the content is oversized (too large for the viewport)         * - resize_h: The height to use for resizable content         * - resize_w: The width to use for resizable content         *         * @var     Object         * @public         */        dimensions: null,        /**         * Contains plugin support information. Each property of this object is a         * boolean indicating whether that plugin is supported.         *         * - fla: Flash player         * - qt: QuickTime player         * - wmp: Windows Media player         * - f4m: Flip4Mac plugin         *         * @var     Object         * @public         */        plugins: null,        /**         * Contains the base path of the Shadowbox script.         *         * Note: This property will automatically be populated in Shadowbox.load.         *         * @var     String         * @public         */        path: '',        /**         * Contains the default options for Shadowbox.         *         * @var     Object         * @public         */        options: {            adapter: null,              // the library adapter to use            animate: true,              // enable all animations, except for fades            animateFade: true,          // enable fade animations            autoplayMovies: true,       // automatically play movies            autoDimensions: false,      // use the dimensions of the first piece as                                        // the initial dimensions (if they are                                        // available)            continuous: false,          // enables continuous galleries. When enabled,                                        // user will be able to skip to the first                                        // gallery item from the last using next and                                        // vice versa            counterLimit: 10,           // limit to the number of counter links that                                        // are displayed in a "skip" style counter            counterType: 'default',     // counter type. May be either "default" or                                        // "skip". Skip counter displays a link for                                        // each item in gallery            displayCounter: true,       // display the gallery counter            displayNav: true,           // show the navigation controls            /**             * Easing function used for animations. Based on a cubic polynomial.             *             * @param   Number      x       The state of the animation (% complete)             * @return  Number              The adjusted easing value             */            ease: function(x){                return 1 + Math.pow(x - 1, 3);            },            enableKeys: true,           // enable keyboard navigation            /**             * An object containing names of plugins and links to their respective             * download pages.             */            errors: {                fla: {                    name:   'Flash',                    url:    'http://www.adobe.com/products/flashplayer/'                },                qt: {                    name:   'QuickTime',                    url:    'http://www.apple.com/quicktime/download/'                },                wmp: {                    name:   'Windows Media Player',                    url:    'http://www.microsoft.com/windows/windowsmedia/'                },                f4m: {                    name:   'Flip4Mac',                    url:    'http://www.flip4mac.com/wmv_download.htm'                }            },            /**             * A map of players to the file extensions they support. Each member of             * this object is the name of a player (with one exception), whose value             * is an array of file extensions that player will "play". The one             * exception to this rule is the "qtwmp" member, which contains extensions             * that may be played using either QuickTime or Windows Media Player.             *             * - img: Image file extensions             * - swf: Flash SWF file extensions             * - flv: Flash video file extensions (will be played by JW FLV player)             * - qt: Movie file extensions supported by QuickTime             * - wmp: Movie file extensions supported by Windows Media Player             * - qtwmp: Movie file extensions supported by both QuickTime and Windows Media Player             * - iframe: File extensions that will be display in an iframe             *             * IMPORTANT: If this object is to be modified, it must be copied in its             * entirety and tweaked because it is not merged recursively with the             * default. Also, any modifications must be passed into Shadowbox.init             * for speed reasons.             */            ext: {                img:        ['png', 'jpg', 'jpeg', 'gif', 'bmp'],                swf:        ['swf'],                flv:        ['flv'],                qt:         ['dv', 'mov', 'moov', 'movie', 'mp4'],                wmp:        ['asf', 'wm', 'wmv'],                qtwmp:      ['avi', 'mpg', 'mpeg'],                iframe:     ['asp', 'aspx', 'cgi', 'cfm', 'htm', 'html', 'jsp',                    'pl', 'php', 'php3', 'php4', 'php5', 'phtml', 'rb', 'rhtml',                    'shtml', 'txt', 'vbs']            },            fadeDuration: 0.35,         // duration of the fading animations (seconds)            /**             * Parameters to pass to flash <object>'s.             */            flashParams: {                bgcolor:            '#000000',                allowFullScreen:    true            },            flashVars: {},              // flash vars            flashVersion: '9.0.115',    // minimum required flash version suggested                                        // by JW FLV player            /**             * How to handle content that is too large to display in its entirety             * (and is resizable). A value of 'resize' will resize the content while             * preserving aspect ratio and display it at the smaller resolution. If             * the content is an image, a value of 'drag' will display the image at             * its original resolution but it will be draggable within Shadowbox. A             * value of 'none' will display the content at its original resolution             * but it may be cropped.             */            handleOversize: 'resize',            /**             * The mode to use when handling unsupported media. May be either             * 'remove' or 'link'. If it is 'remove', the unsupported gallery item             * will merely be removed from the gallery. If it is the only item in             * the gallery, the link will simply be followed. If it is 'link', a             * link will be provided to the appropriate plugin page in place of the             * gallery element.             */            handleUnsupported: 'link',            initialHeight: 160,         // initial height (pixels)            initialWidth: 320,          // initial width (pixels)            language: 'en',             // the language to use            modal: false,               // trigger Shadowbox.close() when overlay is                                        // clicked            onChange: null,             // hook function to be fired when changing                                        // from one item to the next. Is passed the                                        // item that is about to be displayed            onClose: null,              // hook function to be fired when closing.                                        // is passed the most recent item            onFinish: null,             // hook function to be fired when finished                                        // loading content. Is passed current                                        // gallery item            onOpen: null,               // hook function to be fired when opening.                                        // is passed the current gallery item            overlayColor: '#000',       // color to use for modal overlay            overlayOpacity: 0.8,        // opacity to use for modal overlay            players: ['img'],           // the players to load            resizeDuration: 0.35,       // duration of resizing animations (seconds)            showOverlay: true,          // show the overlay            showMovieControls: true,    // enable movie controls on movie players            skipSetup: false,           // skip calling Shadowbox.setup() during                                        // shadowbox.init()            slideshowDelay: 0,          // delay to use for slideshows (seconds). If                                        // set to any duration other than 0, is interval                                        // at which slideshow will advance            useSizzle: true,            // use sizzle.js to support css selectors            viewportPadding: 20         // amount of padding to maintain around the                                        // edge of the viewport at all times (pixels)        },        /**         * Some simple browser detection variables.         *         * @var     Object         * @public         */        client: {            isIE:       ua.indexOf('msie') > -1,            isIE6:      ua.indexOf('msie 6') > -1,            isIE7:      ua.indexOf('msie 7') > -1,            isGecko:    ua.indexOf('gecko') > -1 && ua.indexOf('safari') == -1,            isWebkit:   ua.indexOf('applewebkit/') > -1,            isWindows:  ua.indexOf('windows') > -1 || ua.indexOf('win32') > -1,            isMac:      ua.indexOf('macintosh') > -1 || ua.indexOf('mac os x') > -1,            isLinux:    ua.indexOf('linux') > -1        },        /**         * An object containing some regular expressions we'll need later. Compiled         * up front for speed.         *         * @var     Object         * @public         */        regex: {            domain:         /:\/\/(.*?)[:\/]/,              // domain prefix            inline:         /#(.+)$/,                       // inline element id            rel:            /^(light|shadow)box/i,          // rel attribute format            gallery:        /^(light|shadow)box\[(.*?)\]/i, // rel attribute format for gallery link            unsupported:    /^unsupported-(\w+)/,           // unsupported media type            param:          /\s*([a-z_]*?)\s*=\s*(.+)\s*/   // rel string parameter        },        /**         * A map of library object names to their corresponding Shadowbox adapter         * names.         *         * @var     Object         * @public         */        libraries: {            Prototype:  'prototype',            jQuery:     'jquery',            MooTools:   'mootools',            YAHOO:      'yui',            dojo:       'dojo',            Ext:        'ext'        },        /**         * Applies the given set of options to those currently in use.         *         * Note: Options will be reset on Shadowbox.open() so this function is         * only useful after it has already been called (while Shadowbox is         * open).         *         * @param   Object      opts        The options to apply         * @return  void         * @public         */        applyOptions: function(opts){            if(opts){                // store defaults, use apply to break reference                default_options = apply({}, S.options);                apply(S.options, opts);            }        },        /**         * Builds an object from the original link element data to store in cache.         * These objects contain (most of) the following keys:         *         * - el: the link element         * - title: the object's title         * - player: the player to use for the object         * - content: the object's URL         * - gallery: the gallery the object belongs to (optional)         * - height: the height of the object (only necessary for movies)         * - width: the width of the object (only necessary for movies)         * - options: custom options to use (optional)         *         * A custom set of options may be passed in here that will be applied when         * this object is displayed. However, any options that are specified in         * the link's HTML markup will trump options given here.         *         * @param   HTMLElement     link    The link element to process         * @param   Object          opts    A set of options to use for the object         * @return  Object                  An object representing the link         * @public         */        buildCacheObj: function(link, opts){            var href = link.href, // don't use getAttribute() here            obj = {                el:         link,                title:      link.getAttribute('title'),                options:    apply({}, opts || {}),                content:    href            };            // remove link-level options from top-level options            each(['player', 'title', 'height', 'width', 'gallery'], function(o){                if(typeof obj.options[o] != 'undefined'){                    obj[o] = obj.options[o];                    delete obj.options[o];                }            });            if(!obj.player)                obj.player = getPlayer(href);            // HTML options always trump JavaScript options, so do these last            var rel = link.getAttribute('rel');            if(rel){                // extract gallery name from shadowbox[name] format                var m = rel.match(S.regex.gallery);                if(m)                    obj.gallery = escape(m[2]);                // other parameters                each(rel.split(';'), function(p){                    m = p.match(S.regex.param);                    if(m){                        if(m[1] == 'options')                            eval('apply(obj.options,' + m[2] + ')');                        else                            obj[m[1]] = m[2];                    }                });            }            return obj;        },        /**         * Jumps to the piece in the current gallery with the given index.         *         * @param   Number      n       The gallery index to view         * @return  void         * @public         */        change: function(n){            if(!S.gallery) return; // no current gallery            if(!S.gallery[n]){ // index does not exist                if(!S.options.continuous){                    return;                }else{                    n = n < 0 ? S.gallery.length - 1 : 0; // loop                }            }            // update current            S.current = n;            if(typeof slide_timer == 'number'){                clearTimeout(slide_timer);                slide_timer = null;                slide_delay = slide_start = 0; // reset slideshow variables            }            if(S.options.onChange)                S.options.onChange();            loadContent();        },        /**         * Removes all onclick listeners from elements that have been setup with         * Shadowbox and clears all objects from cache.         *         * @return  void         * @public         */        clearCache: function(){            each(S.cache, function(obj){                if(obj.el)                    S.lib.removeEvent(obj.el, 'click', handleClick);            });            S.cache = [];        },        /**         * Deactivates Shadowbox.         *         * @return  void         * @public         */        close: function(){            if(!active) return; // already closed            active = false;            listenKeys(false);            // remove the content            if(S.content){                S.content.remove();                S.content = null;            }            // clear slideshow variables            if(typeof slide_timer == 'number')                clearTimeout(slide_timer);            slide_timer = null;            slide_delay = 0;            if(S.options.onClose)                S.options.onClose();            S.skin.onClose();            S.revertOptions();            // reset troublesome elements to stored visibility settings            each(v_cache, function(c){                c[0].style.visibility = c[1];            });        },        /**         * Gets the id that should be used for content elements.         *         * @return  String          The content element id         * @public         */        contentId: function(){            return content_id;        },        /**         * Gets the values that should appear in the counter (if the skin         * supports counters). If a "skip" style counter is used, the return         * value should be an array of all counter indexes that should be         * displayed. If the "default" counter is used, the return value will         * be a simple "1 of 5" message as a string. The skin can use the return         * type to determine how to build the counter.         *         * @return  mixed           The counter as described above         * @public         */        getCounter: function(){            var len = S.gallery.length;            if(S.options.counterType == 'skip'){                // limit the counter?                var c = [],                    i = 0,                    end = len,                    limit = parseInt(S.options.counterLimit) || 0;                if(limit < len && limit > 2){ // support large galleries                    var h = Math.floor(limit / 2);                    i = S.current - h;                    if(i < 0) i += len;                    end = S.current + (limit - h);                    if(end > len) end -= len;                }                while(i != end){                    if(i == len) i = 0;                    c.push(i++);                }            }else                var c = (S.current + 1) + ' ' + S.lang.of + ' ' + len;            return c;        },        /**         * Gets the current gallery object.         *         * @return  Object          The current gallery item         * @public         */        getCurrent: function(){            return S.current > -1 ? S.gallery[S.current] : null;        },        /**         * Determines if there is a next piece to display in the current         * gallery.         *         * @return  Boolean         True if there is another piece         * @public         */        hasNext: function(){            return S.gallery.length > 1 &&                (S.current != S.gallery.length - 1 || S.options.continuous);        },        /**         * Initializes the Shadowbox environment. Should be called by the user in         * the <head> of the HTML document.         *         * Note: This function attempts to load all Shadowbox dependencies         * dynamically using document.write. However, if these dependencies are         * already included, they won't be loaded again.         *         * @param   Object      opts    (optional) The default options to use         * @return  void         * @public         */        init: function(opts){            if(initialized) return; // don't initialize twice            initialized = true;            opts = opts || {};            init_options = opts;            // apply options            if(opts)                apply(S.options, opts);            // compile file type regular expressions here for speed            for(var e in S.options.ext)                S.regex[e] = new RegExp('\.(' + S.options.ext[e].join('|') + ')\s*$', 'i');            if(!S.path){                // determine script path automatically                var path_re = /(.+)shadowbox\.js/i, path;                each(document.getElementsByTagName('script'), function(s){                    if((path = path_re.exec(s.src)) != null){                        S.path = path[1];                        return false;                    }                });            }            // determine adapter            if(S.options.adapter)                S.adapter = S.options.adapter;            else{                for(var lib in S.libraries){                    if(typeof window[lib] != 'undefined'){                        S.adapter = S.libraries[lib];                        break;                    }                }                if(!S.adapter)                    S.adapter = 'base';            }            // load dependencies            if(S.options.useSizzle && !window['Sizzle'])                U.include(S.path + '../scripts/libraries/sizzle/sizzle.js');            if(!S.lang)                U.include(S.path + '../scripts/languages/shadowbox-' + S.options.language + '.js');            each(S.options.players, function(p){                if((p == 'swf' || p == 'flv') && !window['swfobject'])                    U.include(S.path + '../scripts/libraries/swfobject/swfobject.js');                if(!S[p])                    U.include(S.path + '../scripts/players/shadowbox-' + p + '.js');            });            if(!S.lib)                U.include(S.path + '../scripts/../scripts/adapters/shadowbox-' + S.adapter + '.js');        },        /**         * Tells whether or not Shadowbox is currently activated.         *         * @return  Boolean         True if activated, false otherwise         * @public         */        isActive: function(){            return active;        },        /**         * Tells whether or not Shadowbox is currently in the middle of a         * slideshow in a paused state.         *         * @return  Boolean         True if paused, false otherwise         * @public         */        isPaused: function(){            return slide_timer == 'paused';        },        /**         * Loads Shadowbox into the DOM. Is called automatically by each adapter         * as soon as the DOM is ready.         *         * @return  void         * @public         */        load: function(){            // apply skin options, re-apply user init options in case they overwrite            if(S.skin.options){                apply(S.options, S.skin.options);                apply(S.options, init_options);            }            // append markup and initialize skin            var markup = S.skin.markup.replace(/\{(\w+)\}/g, function(m, p){                return S.lang[p];            });            S.lib.append(document.body, markup);            if(S.skin.init)                S.skin.init();            // set up window resize event handler            var id;            S.lib.addEvent(window, 'resize', function(){                // use 50 ms event buffering to prevent jerky window resizing                if(id){                    clearTimeout(id);                    id = null;                }                // check if activated because IE7 fires window resize event                // when container display is set to block                if(active){                    id = setTimeout(function(){                        if(S.skin.onWindowResize)                            S.skin.onWindowResize();                        var c = S.content;                        if(c && c.onWindowResize)                            c.onWindowResize();                    }, 50);                }            });            if(!S.options.skipSetup)                S.setup();        },        /**         * Jumps to the next piece in the gallery.         *         * @return  void         * @public         */        next: function(){            S.change(S.current + 1);        },        /**         * Opens the given object in Shadowbox. This object may be either an         * anchor/area element, or an object similar to the one created by         * Shadowbox.buildCacheObj().         *         * @param   mixed       obj         The object or link element that defines         *                                  what to display         * @return  void         * @public         */        open: function(obj){            // if it's a link element, build an object on the fly            if(U.isLink(obj))                obj = S.buildCacheObj(obj);            // set up the gallery            if(obj.constructor == Array){                S.gallery = obj;                S.current = 0;            }else{                if(!obj.gallery){                    // single item, no gallery                    S.gallery = [obj];                    S.current = 0;                }else{                    // gallery item, build gallery from cached gallery elements                    S.current = null;                    S.gallery = [];                    each(S.cache, function(c){                        if(c.gallery && c.gallery == obj.gallery){                            if(S.current == null && c.content == obj.content && c.title == obj.title)                                S.current = S.gallery.length;                            S.gallery.push(c);                        }                    });                    // if not found in cache, prepend to front of gallery                    if(S.current == null){                        S.gallery.unshift(obj);                        S.current = 0;                    }                }            }            obj = S.getCurrent();            if(obj.options){                S.revertOptions();                S.applyOptions(obj.options);            }            // filter gallery for unsupported elements            var g, r, m, s, a, oe = S.options.errors, msg, el;            for(var i = 0; i < S.gallery.length; ++i){                // use apply to break the reference to the original object here                // because we'll be modifying the properties of the gallery objects                // directly and we don't want to taint them in case they are used                // again in a future call                g = S.gallery[i] = apply({}, S.gallery[i]);                r = false; // remove the element?                if(g.player == 'unsupported'){                    // don't support this at all                    r = true;                }else if(m = S.regex.unsupported.exec(g.player)){                    // handle unsupported elements                    if(S.options.handleUnsupported == 'link'){                        g.player = 'html';                        // generate a link to the appropriate plugin download page(s)                        switch(m[1]){                            case 'qtwmp':                                s = 'either';                                a = [oe.qt.url, oe.qt.name, oe.wmp.url, oe.wmp.name];                            break;                            case 'qtf4m':                                s = 'shared';                                a = [oe.qt.url, oe.qt.name, oe.f4m.url, oe.f4m.name];                            break;                            default:                                s = 'single';                                if(m[1] == 'swf' || m[1] == 'flv') m[1] = 'fla';                                a = [oe[m[1]].url, oe[m[1]].name];                        }                        msg = S.lang.errors[s].replace(/\{(\d+)\}/g, function(m, n){                            return a[n];                        });                        g.content = '<div class="sb-message">' + msg + '</div>';                    }else                        r = true;                }else if(g.player == 'inline'){                    // inline element, retrieve innerHTML                    m = S.regex.inline.exec(g.content);                    if(m){                        var el = U.get(m[1]);                        if(el)                            g.content = el.innerHTML;                        else                            throw 'Cannot find element with id ' + m[1];                    }else                        throw 'Cannot find element id for inline content';                }else if(g.player == 'swf' || g.player == 'flv'){                    var version = (g.options && g.options.flashVersion) || S.options.flashVersion;                    if(!swfobject.hasFlashPlayerVersion(version)){                        // express install will be triggered because the client                        // does not have the minimum required version of flash                        // installed, set height and width to those of express                        // install swf                        g.width = 310;                        // minimum height is 127, but +20 pixels on top and bottom                        // looks better                        g.height = 177;                    }                }                if(r){                    S.gallery.splice(i, 1); // remove from gallery                    if(i < S.current)                        --S.current; // maintain integrity of S.current                    else if(i == S.current)                        S.current = i > 0 ? i - 1 : i; // look for supported neighbor                    --i; // decrement index for next loop                }            }            // anything left to display?            if(S.gallery.length){                if(!active){                    // fire onOpen hook                    if(typeof S.options.onOpen == 'function' && S.options.onOpen(obj) === false) return;                    // hide elements troublesome for modal overlays                    v_cache = [];                    each(['select', 'object', 'embed', 'canvas'], function(tag){                        each(document.getElementsByTagName(tag), function(el){                            v_cache.push([el, el.style.visibility || 'visible']);                            el.style.visibility = 'hidden';                        });                    });                    // set initial dimensions & load                    var h = S.options.autoDimensions && 'height' in obj                        ? obj.height                        : S.options.initialHeight;                    var w = S.options.autoDimensions && 'width' in obj                        ? obj.width                        : S.options.initialWidth;                    S.skin.onOpen(h, w, loadContent);                }else                    loadContent();                active = true;            }        },        /**         * Pauses the current slideshow.         *         * @return  void         * @public         */        pause: function(){            if(typeof slide_timer != 'number') return;            var time = new Date().getTime();            slide_delay = Math.max(0, slide_delay - (time - slide_start));            // if there's any time left on current slide, pause the timer            if(slide_delay){                clearTimeout(slide_timer);                slide_timer = 'paused';                if(S.skin.onPause)                    S.skin.onPause();            }        },        /**         * Sets the timer for the next image in the slideshow to be displayed.         *         * @return  void         * @public         */        play: function(){            if(!S.hasNext()) return;            if(!slide_delay) slide_delay = S.options.slideshowDelay * 1000;            if(slide_delay){                slide_start = new Date().getTime();                slide_timer = setTimeout(function(){                    slide_delay = slide_start = 0; // reset slideshow                    S.next();                }, slide_delay);                if(S.skin.onPlay)                    S.skin.onPlay();            }        },        /**         * Jumps to the previous piece in the gallery.         *         * @return  void         * @public         */        previous: function(){            S.change(S.current - 1);        },        /**         * Reverts Shadowbox' options to the last default set in use before         * Shadowbox.applyOptions() was called.         *         * @return  void         * @public         */        revertOptions: function(){            apply(S.options, default_options);        },        /**         * Calculates the dimensions for Shadowbox according to the given         * parameters. Will determine if content is oversized (too large for the         * viewport) and will automatically constrain resizable content         * according to user preference.         *         * @param   Number      height      The content height         * @param   Number      width       The content width         * @param   Number      max_h       The maximum height available (should         *                                  be the height of the viewport)         * @param   Number      max_w       The maximum width available (should         *                                  be the width of the viewport)         * @param   Number      tb          The extra top/bottom pixels that are         *                                  required for borders/toolbars         * @param   Number      lr          The extra left/right pixels that are         *                                  required for borders/toolbars         * @param   Boolean     resizable   True if the content is able to be         *                                  resized. Defaults to false         * @return  Object                  The Shadowbox.dimensions object         * @public         */        setDimensions: function(height, width, max_h, max_w, tb, lr, resizable){            var h = height = parseInt(height),                w = width = parseInt(width),                pad = parseInt(S.options.viewportPadding) || 0;            // calculate the max height/width            var extra_h = 2 * pad + tb;            if(h + extra_h >= max_h) h = max_h - extra_h;            var extra_w = 2 * pad + lr;            if(w + extra_w >= max_w) w = max_w - extra_w;            // handle oversized content            var resize_h = height,                resize_w = width,                change_h = (height - h) / height,                change_w = (width - w) / width,                oversized = (change_h > 0 || change_w > 0);            if(resizable && oversized && S.options.handleOversize == 'resize'){                // adjust resized height/width, preserve original aspect ratio                if(change_h > change_w)                    w = Math.round((width / height) * h);                else if(change_w > change_h)                    h = Math.round((height / width) * w);                resize_w = w;                resize_h = h;            }            // update Shadowbox.dimensions            S.dimensions = {                height:     h + tb,                width:      w + lr,                inner_h:    h,                inner_w:    w,                top:        (max_h - (h + extra_h)) / 2 + pad,                left:       (max_w - (w + extra_w)) / 2 + pad,                oversized:  oversized,                resize_h:   resize_h,                resize_w:   resize_w            };            return S.dimensions;        },        /**         * Sets up listeners on the given links that will trigger Shadowbox. If no         * links are given, this method will set up every anchor element on the page         * with the appropriate rel attribute. It is important to note that any         * options given here will be applied to all link elements. Multiple calls         * to this method may be needed if different options are desired.         *         * Note: Because AREA elements do not support the rel attribute, they must         * be explicitly passed to this method.         *         * @param   Array       links       An array (or array-like) list of anchor         *                                  and/or area elements to set up         * @param   Object      opts        Some options to use for the given links         * @return  void         * @public         */        setup: function(links, opts){            // get links if none specified            if(!links){                var links = [], rel;                each(document.getElementsByTagName('a'), function(a){                    rel = a.getAttribute('rel');                    if(rel && S.regex.rel.test(rel)) links.push(a);                });            }else{                var len = links.length;                if(len){                    if(window['Sizzle']){                        if(typeof links == 'string')                            links = Sizzle(links); // lone selector                        else if(len == 2 && links.push && typeof links[0] == 'string' && links[1].nodeType)                            links = Sizzle(links[0], links[1]); // selector + context                    }                }else                    links = [links]; // single link            }            each(links, function(link){                if(typeof link.shadowboxCacheKey == 'undefined'){                    // assign cache key expando, use integer primitive to avoid                    // memory leak in IE                    link.shadowboxCacheKey = S.cache.length;                    // add onclick listener                    S.lib.addEvent(link, 'click', handleClick);                }                S.cache[link.shadowboxCacheKey] = S.buildCacheObj(link, opts);            });        }    },    U = S.util = {        /**         * Animates any numeric (not color) style of the given element from its         * current state to the given value. Defaults to using pixel-based         * measurements.         *         * @param   HTMLElement     el      The element to animate         * @param   String          p       The property to animate (in camelCase)         * @param   mixed           to      The value to animate to         * @param   Number          d       The duration of the animation (in         *                                  seconds)         * @param   Function        cb      A callback function to call when the         *                                  animation completes         * @return  void         * @public         */        animate: function(el, p, to, d, cb){            var from = parseFloat(S.lib.getStyle(el, p));            if(isNaN(from)) from = 0;            var delta = to - from;            if(delta == 0){                if(cb) cb();                return; // nothing to animate            }            var op = p == 'opacity';            function fn(ease){                var to = from + ease * delta;                if(op)                    U.setOpacity(el, to);                else                    el.style[p] = to + 'px'; // default unit is px            }            // cancel the animation here if duration is 0 or if set in the options            if(!d || (!op && !S.options.animate) || (op && !S.options.animateFade)){                fn(1);                if(cb) cb();                return;            }            d *= 1000; // convert to milliseconds            var begin = new Date().getTime(),                end = begin + d,                time,                timer = setInterval(function(){                    time = new Date().getTime();                    if(time >= end){ // end of animation                        clearInterval(timer);                        fn(1);                        if(cb) cb();                    }else                        fn(S.options.ease((time - begin) / d));                }, 10); // 10 ms interval is minimum on webkit        },        /**         * Applies all properties of e to o.         *         * @param   Object      o       The original object         * @param   Object      e       The extension object         * @return  Object              The original object with all properties         *                              of the extension object applied         * @public         */        apply: function(o, e){            for(var p in e)                o[p] = e[p];            return o;        },        /**         * A utility function used by the fade functions to clear the opacity         * style setting of the given element. Required in some cases for IE.         *         * @param   HTMLElement     el      The element         * @return  void         * @public         */        clearOpacity: function(el){            var s = el.style;            if(window.ActiveXObject){                // be careful not to overwrite other filters!                if(typeof s.filter == 'string' && (/alpha/i).test(s.filter))                    s.filter = s.filter.replace(/[\w\.]*alpha\(.*?\);?/i, '');            }else                s.opacity = '';        },        /**         * Calls the given function for each element of obj. The obj element must         * be array-like (meaning it must have a length property and be able to         * be accessed using the array square bracket syntax). If scope is not         * explicitly given, the callback will be called with a scope of the         * current item. Will stop execution if a callback returns false.         *         * @param   mixed       obj     An array-like object containing the         *                              elements         * @param   Function    fn      The callback function         * @param   mixed       scope   The scope of the callback         * @return  void         * @public         */        each: function(obj, fn, scope){            for(var i = 0, len = obj.length; i < len; ++i)                if(fn.call(scope || obj[i], obj[i], i, obj) === false) return;        },        /**         * Gets an element by its id.         *         * @param   String      id      The element id         * @return  HTMLElement         A reference to the element with the         *                              given id         * @public         */        get: function(id){            return document.getElementById(id);        },        /**         * Dynamically includes a JavaScript file in the current page.         *         * @param   String      file    The name of the js file to include         * @return  void         * @public         */        include: function(){            var includes = {};            return function(file){                if(includes[file]) return; // don't include same file twice                includes[file] = true;                document.write('<scr' + 'ipt type="text/javascript" src="' + file + '"><\/script>');            }        }(),        /**         * Determines if the given object is an anchor/area element.         *         * @param   mixed       obj     The object to check         * @return  Boolean             True if the object is a link element         * @public         */        isLink: function(obj){            if(!obj || !obj.tagName) return false;            var up = obj.tagName.toUpperCase();            return up == 'A' || up == 'AREA';        },        /**         * Removes all child nodes from the given element.         *         * @param   HTMLElement     el      The element         * @return  void         * @public         */        removeChildren: function(el){            while(el.firstChild)                el.removeChild(el.firstChild);        },        /**         * Sets the opacity of the given element to the specified level.         *         * @param   HTMLElement     el      The element         * @param   Number          o       The opacity to use         * @return  void         * @public         */        setOpacity: function(el, o){            var s = el.style;            if(window.ActiveXObject){                s.zoom = 1; // trigger hasLayout                s.filter = (s.filter || '').replace(/\s*alpha\([^\)]*\)/gi, '') +                    (o == 1 ? '' : ' alpha(opacity=' + (o * 100) + ')');            }else                s.opacity = o;        }    },    // shorthand    apply = U.apply,    each = U.each,    /**     * The initial options object that was given by the user.     *     * @var     Object     * @private     */    init_options,    /**     * Keeps track of whether or not Shadowbox.init has been called.     *     * @var     Boolean     * @private     */    initialized = false,    /**     * Stores the default set of options in case a custom set of options is used     * on a link-by-link basis so we can restore them later.     *     * @var     Object     * @private     */    default_options = {},    /**     * The id to use for content objects.     *     * @var     String     * @private     */    content_id = 'sb-content',    /**     * Keeps track of whether or not Shadowbox is activated.     *     * @var     Boolean     * @private     */    active = false,    /**     * The timeout id for the slideshow transition function.     *     * @var     Number     * @private     */    slide_timer,    /**     * Keeps track of the time at which the current slideshow frame was     * displayed.     *     * @var     Number     * @private     */    slide_start,    /**     * The delay on which the next slide will display.     *     * @var     Number     * @private     */    slide_delay = 0,    /**     * A cache for elements that are troublesome for modal overlays.     *     * @var     Array     * @private     */    v_cache = [];    // detect plugin support    if(navigator.plugins && navigator.plugins.length){        var names = [];        each(navigator.plugins, function(p){            names.push(p.name);        });        names = names.join();        var detectPlugin = function(n){            return names.indexOf(n) > -1;        }        var f4m = detectPlugin('Flip4Mac');        S.plugins = {            fla:    detectPlugin('Shockwave Flash'),            qt:     detectPlugin('QuickTime'),            wmp:    !f4m && detectPlugin('Windows Media'), // if it's Flip4Mac, it's not really WMP            f4m:    f4m        }    }else{        function detectPlugin(n){            try{                var axo = new ActiveXObject(n);            }catch(e){}            return !!axo;        }        S.plugins = {            fla:    detectPlugin('ShockwaveFlash.ShockwaveFlash'),            qt:     detectPlugin('QuickTime.QuickTime'),            wmp:    detectPlugin('wmplayer.ocx'),            f4m:    false        }    }    /**     * Determines the player needed to display the file at the given URL. If     * the file type is not supported, the return value will be 'unsupported'.     * If the file type is not supported but the correct player can be     * determined, the return value will be 'unsupported-*' where * will be the     * player abbreviation (e.g. 'qt' = QuickTime).     *     * @param   String      url     The url of the file     * @return  String              The name of the player to use     * @private     */    function getPlayer(url){        var re = S.regex,            p = S.plugins,            m = url.match(re.domain),            d = m && document.domain == m[1]; // same domain        if(url.indexOf('#') > -1 && d) return 'inline';        var q = url.indexOf('?');        if(q > -1) url = url.substring(0, q); // strip query string for player detection purposes        if(re.img.test(url)) return 'img';        if(re.swf.test(url)) return p.fla ? 'swf' : 'unsupported-swf';        if(re.flv.test(url)) return p.fla ? 'flv' : 'unsupported-flv';        if(re.qt.test(url)) return p.qt ? 'qt' : 'unsupported-qt';        if(re.wmp.test(url)){            if(p.wmp) return 'wmp';            if(p.f4m) return 'qt';            if(S.client.isMac) return p.qt ? 'unsupported-f4m' : 'unsupported-qtf4m';            return 'unsupported-wmp';        }        if(re.qtwmp.test(url)){            if(p.qt) return 'qt';            if(p.wmp) return 'wmp';            return S.client.isMac ? 'unsupported-qt' : 'unsupported-qtwmp';        }        if(!d || re.iframe.test(url))            return 'iframe';        return 'unsupported'; // same domain, not supported    }    /**     * Handles all clicks on links that have been set up to work with Shadowbox     * and cancels the default event behavior when appropriate.     *     * @param   HTMLEvent   e           The click event object     * @return  void     * @private     */    function handleClick(e){        // get anchor/area element        var link;        if(U.isLink(this)){            link = this; // jQuery, Prototype, YUI        }else{            link = S.lib.getTarget(e); // Ext, standalone            while(!U.isLink(link) && link.parentNode)                link = link.parentNode;        }        //S.lib.preventDefault(e); // good for debugging        if(link){            // if this link has already been set up, use the cached version            var key = link.shadowboxCacheKey;            if(typeof key != 'undefined' && typeof S.cache[key] != 'undefined')                link = S.cache[key];            S.open(link);            if(S.gallery.length) S.lib.preventDefault(e); // stop event        }    }    /**     * Sets up a listener on the document for keystrokes.     *     * @param   Boolean     on      True to enable the listener, false to turn     *                              it off     * @return  void     * @private     */    function listenKeys(on){        if(!S.options.enableKeys) return;        S.lib[(on ? 'add' : 'remove') + 'Event'](document, 'keydown', handleKey);    }    /**     * A listener function that is fired when a key is pressed.     *     * @param   mixed       e       The event object     * @return  void     * @private     */    function handleKey(e){        var code = S.lib.keyCode(e);        // attempt to prevent default key action        S.lib.preventDefault(e);        switch(code){            case 81: // q            case 88: // x            case 27: // esc                S.close();                break;            case 37: // left                S.previous();                break;            case 39: // right                S.next();                break;            case 32: // space                S[(typeof slide_timer == 'number' ? 'pause' : 'play')]();        }    }    /**     * Loads the Shadowbox with the current piece.     *     * @return  void     * @private     */    function loadContent(){        var obj = S.getCurrent();        if(!obj) return;        // determine player, inline is really just HTML        var p = obj.player == 'inline' ? 'html' : obj.player;        if(typeof S[p] != 'function')            throw 'Unknown player: ' + p;        var change = false;        if(S.content){            // changing from some previous content            S.content.remove(); // remove old content            change = true;            S.revertOptions();            if(obj.options)                S.applyOptions(obj.options);        }        // make sure the body element doesn't have any children, just in case        U.removeChildren(S.skin.bodyEl());        // load the content        S.content = new S[p](obj);        listenKeys(false); // disable the keyboard while content is loading        S.skin.onLoad(S.content, change, function(){            if(!S.content) return;            if(typeof S.content.ready != 'undefined'){                // if content object has a ready property, wait for it to be                // ready before loading                var id = setInterval(function(){                    if(S.content){                        if(S.content.ready){                            clearInterval(id);                            id = null;                            S.skin.onReady(contentReady);                        }                    }else{ // content has been removed                        clearInterval(id);                        id = null;                    }                }, 100);            }else                S.skin.onReady(contentReady);        });        // preload neighboring gallery images        if(S.gallery.length > 1){            var next = S.gallery[S.current + 1] || S.gallery[0];            if(next.player == 'img'){                var a = new Image();                a.src = next.content;            }            var prev = S.gallery[S.current - 1] || S.gallery[S.gallery.length - 1];            if(prev.player == 'img'){                var b = new Image();                b.src = prev.content;            }        }    }    /**     * Callback that should be called with the content is ready to be loaded.     *     * @return  void     * @private     */    function contentReady(){        if(!S.content) return;        S.content.append(S.skin.bodyEl(), content_id, S.dimensions);        S.skin.onFinish(finishContent);    }    /**     * Callback that should be called when the content is finished loading.     *     * @return  void     * @private     */    function finishContent(){        if(!S.content) return;        if(S.content.onLoad)            S.content.onLoad();        if(S.options.onFinish)            S.options.onFinish();        if(!S.isPaused())            S.play(); // kick off next slide        listenKeys(true); // re-enable keyboard when finished    }    return S;}();/** * The default skin for Shadowbox. Separated out into its own class so that it may * be customized more easily by skin developers. */Shadowbox.skin = function(){    var S = Shadowbox,        U = S.util,    /**     * Keeps track of whether or not the overlay is activated.     *     * @var     Boolean     * @private     */    overlay_on = false,    /**     * Id's of elements that need transparent PNG support in IE6.     *     * @var     Array     * @private     */    png = [        'sb-nav-close',        'sb-nav-next',        'sb-nav-play',        'sb-nav-pause',        'sb-nav-previous'    ];    /**     * Sets the top of the container element. This is only necessary in IE6     * where the container uses absolute positioning instead of fixed.     *     * @return  void     * @private     */    function fixTop(){        U.get('sb-container').style.top = document.documentElement.scrollTop + 'px';    }    /**     * Toggles the visibility of #sb-container and sets its size (if on     * IE6). Also toggles the visibility of elements (<select>, <object>, and     * <embed>) that are troublesome for semi-transparent modal overlays. IE has     * problems with <select> elements, while Firefox has trouble with     * <object>s.     *     * @param   Function    cb      A callback to call after toggling on, absent     *                              when toggling off     * @return  void     * @private     */    function toggleVisible(cb){        var so = U.get('sb-overlay'),            sc = U.get('sb-container'),            sb = U.get('sb-wrapper');        if(cb){            if(S.client.isIE6){                // fix container top before showing                fixTop();                S.lib.addEvent(window, 'scroll', fixTop);            }            if(S.options.showOverlay){                overlay_on = true;                // set overlay color/opacity                so.style.backgroundColor = S.options.overlayColor;                U.setOpacity(so, 0);                if(!S.options.modal) S.lib.addEvent(so, 'click', S.close);                sb.style.display = 'none'; // cleared in onLoad            }            sc.style.visibility = 'visible';            if(overlay_on){                // fade in effect                var op = parseFloat(S.options.overlayOpacity);                U.animate(so, 'opacity', op, S.options.fadeDuration, cb);            }else                cb();        }else{            if(S.client.isIE6)                S.lib.removeEvent(window, 'scroll', fixTop);            S.lib.removeEvent(so, 'click', S.close);            if(overlay_on){                // fade out effect                sb.style.display = 'none';                U.animate(so, 'opacity', 0, S.options.fadeDuration, function(){                    // the following is commented because it causes the overlay to                    // be hidden on consecutive activations in IE8, even though we                    // set the visibility to "visible" when we reactivate                    //sc.style.visibility = 'hidden';                    sc.style.display = '';                    sb.style.display = '';                    U.clearOpacity(so);                });            }else                sc.style.visibility = 'hidden';        }    }    /**     * Toggles the display of the nav control with the given id on and off.     *     * @param   String      id      The id of the navigation control     * @param   Boolean     on      True to toggle on, false to toggle off     * @return  void     * @private     */    function toggleNav(id, on){        var el = U.get('sb-nav-' + id);        if(el) el.style.display = on ? '' : 'none';    }    /**     * Toggles the visibility of the "loading" layer.     *     * @param   Boolean     on      True to toggle on, false to toggle off     * @param   Function    cb      The callback function to call when toggling     *                              completes     * @return  void     * @private     */    function toggleLoading(on, cb){        var ld = U.get('sb-loading'),            p = S.getCurrent().player,            anim = (p == 'img' || p == 'html'); // fade on images & html        if(on){            function fn(){                U.clearOpacity(ld);                if(cb) cb();            }            U.setOpacity(ld, 0);            ld.style.display = '';            if(anim)                U.animate(ld, 'opacity', 1, S.options.fadeDuration, fn);            else                fn();        }else{            function fn(){                ld.style.display = 'none';                U.clearOpacity(ld);                if(cb) cb();            }            if(anim)                U.animate(ld, 'opacity', 0, S.options.fadeDuration, fn);            else                fn();        }    }    /**     * Builds the content for the title and information bars.     *     * @param   Function    cb      A callback function to execute after the     *                              bars are built     * @return  void     * @private     */    function buildBars(cb){        var obj = S.getCurrent();        // build the title, if present        U.get('sb-title-inner').innerHTML = obj.title || '';        // build the nav        var c, n, pl, pa, p;        if(S.options.displayNav){            c = true;            // next & previous links            var len = S.gallery.length;            if(len > 1){                if(S.options.continuous)                    n = p = true; // show both                else{                    n = (len - 1) > S.current; // not last in gallery, show next                    p = S.current > 0; // not first in gallery, show previous                }            }            // in a slideshow?            if(S.options.slideshowDelay > 0 && S.hasNext()){                pa = !S.isPaused();                pl = !pa;            }        }else{            c = n = pl = pa = p = false;        }        toggleNav('close', c);        toggleNav('next', n);        toggleNav('play', pl);        toggleNav('pause', pa);        toggleNav('previous', p);        // build the counter        var c = '';        if(S.options.displayCounter && S.gallery.length > 1){            var count = S.getCounter();            if(typeof count == 'string') // default                c = count;            else{                U.each(count, function(i){                    c += '<a onclick="Shadowbox.change(' + i + ');"'                    if(i == S.current) c += ' class="sb-counter-current"';                    c += '>' + (i + 1) + '</a>';                });            }        }        U.get('sb-counter').innerHTML = c;        cb();    }    /**     * Hides the title and info bars.     *     * @param   Boolean     anim    True to animate the transition     * @param   Function    cb      A callback function to execute after the     *                              animation completes     * @return  void     * @private     */    function hideBars(anim, cb){        var sw = U.get('sb-wrapper'),            st = U.get('sb-title'),            si = U.get('sb-info'),            ti = U.get('sb-title-inner'),            ii = U.get('sb-info-inner'),            t = parseInt(S.lib.getStyle(ti, 'height')) || 0,            b = parseInt(S.lib.getStyle(ii, 'height')) || 0;        function fn(){            // hide bars here in case of overflow, build after hidden            ti.style.visibility = ii.style.visibility = 'hidden';            buildBars(cb);        }        if(anim){            U.animate(st, 'height', 0, 0.35);            U.animate(si, 'height', 0, 0.35);            U.animate(sw, 'paddingTop', t, 0.35);            U.animate(sw, 'paddingBottom', b, 0.35, fn);        }else{            st.style.height = si.style.height = '0px';            sw.style.paddingTop = t + 'px';            sw.style.paddingBottom = b + 'px';            fn();        }    }    /**     * Shows the title and info bars.     *     * @param   Function    cb      A callback function to execute after the     *                              animation completes     * @return  void     * @private     */    function showBars(cb){        var sw = U.get('sb-wrapper'),            st = U.get('sb-title'),            si = U.get('sb-info'),            ti = U.get('sb-title-inner'),            ii = U.get('sb-info-inner'),            t = parseInt(S.lib.getStyle(ti, 'height')) || 0,            b = parseInt(S.lib.getStyle(ii, 'height')) || 0;        // clear visibility before animating into view        ti.style.visibility = ii.style.visibility = '';        // show title?        if(ti.innerHTML != ''){            U.animate(st, 'height', t, 0.35);            U.animate(sw, 'paddingTop', 0, 0.35);        }        U.animate(si, 'height', b, 0.35);        U.animate(sw, 'paddingBottom', 0, 0.35, cb);    }    /**     * Adjusts the height of #sb-body and centers #sb-wrapper vertically     * in the viewport.     *     * @param   Number      height      The height to use for #sb-body     * @param   Number      top         The top to use for #sb-wrapper     * @param   Boolean     anim        True to animate the transition     * @param   Function    cb          A callback to use when the animation     *                                  completes     * @return  void     * @private     */    function adjustHeight(height, top, anim, cb){        var sb = U.get('sb-body'),            s = U.get('sb-wrapper'),            h = parseInt(height),            t = parseInt(top);        if(anim){            U.animate(sb, 'height', h, S.options.resizeDuration);            U.animate(s, 'top', t, S.options.resizeDuration, cb);        }else{            sb.style.height = h + 'px';            s.style.top = t + 'px';            if(cb) cb();        }    }    /**     * Adjusts the width and left of #sb-wrapper.     *     * @param   Number      width       The width to use for #sb-wrapper     * @param   Number      left        The left to use for #sb-wrapper     * @param   Boolean     anim        True to animate the transition     * @param   Function    cb          A callback to use when the animation     *                                  completes     * @return  void     * @private     */    function adjustWidth(width, left, anim, cb){        var s = U.get('sb-wrapper'),            w = parseInt(width),            l = parseInt(left);        if(anim){            U.animate(s, 'width', w, S.options.resizeDuration);            U.animate(s, 'left', l, S.options.resizeDuration, cb);        }else{            s.style.width = w + 'px';            s.style.left = l + 'px';            if(cb) cb();        }    }    /**     * Resizes Shadowbox to the appropriate height and width for the current     * content.     *     * @param   Function    cb      A callback function to execute after the     *                              resize completes     * @return  void     * @private     */    function resizeContent(cb){        var c = S.content;        if(!c) return;        // set new dimensions        var d = setDimensions(c.height, c.width, c.resizable);        switch(S.options.animSequence){            case 'hw':                adjustHeight(d.inner_h, d.top, true, function(){                    adjustWidth(d.width, d.left, true, cb);                });            break;            case 'wh':                adjustWidth(d.width, d.left, true, function(){                    adjustHeight(d.inner_h, d.top, true, cb);                });            break;            default: // sync                adjustWidth(d.width, d.left, true);                adjustHeight(d.inner_h, d.top, true, cb);        }    }    /**     * Calculates the dimensions for Shadowbox, taking into account the borders     * and surrounding elements of #sb-body.     *     * @param   Number      height      The content height     * @param   Number      width       The content width     * @param   Boolean     resizable   True if the content is able to be     *                                  resized. Defaults to false     * @return  Object                  The new dimensions object     * @private     */    function setDimensions(height, width, resizable){        var sbi = U.get('sb-body-inner')            sw = U.get('sb-wrapper'),            so = U.get('sb-overlay'),            tb = sw.offsetHeight - sbi.offsetHeight,            lr = sw.offsetWidth - sbi.offsetWidth,            max_h = so.offsetHeight, // measure overlay to get viewport size for IE6            max_w = so.offsetWidth;        return S.setDimensions(height, width, max_h, max_w, tb, lr, resizable);    }    return {        /**         * The HTML markup to use.         *         * @var     String         * @public         */        markup: '<div id="sb-container">' +                    '<div id="sb-overlay"></div>' +                    '<div id="sb-wrapper">' +                        '<div id="sb-title">' +                            '<div id="sb-title-inner"></div>' +                        '</div>' +                        '<div id="sb-body">' +                            '<div id="sb-body-inner"></div>' +                            '<div id="sb-loading">' +                                '<a onclick="Shadowbox.close()">{cancel}</a>' +                            '</div>' +                        '</div>' +                        '<div id="sb-info">' +                            '<div id="sb-info-inner">' +                                '<div id="sb-counter"></div>' +                                '<div id="sb-nav">' +                                    '<a id="sb-nav-close" title="{close}" onclick="Shadowbox.close()"></a>' +                                    '<a id="sb-nav-next" title="{next}" onclick="Shadowbox.next()"></a>' +                                    '<a id="sb-nav-play" title="{play}" onclick="Shadowbox.play()"></a>' +                                    '<a id="sb-nav-pause" title="{pause}" onclick="Shadowbox.pause()"></a>' +                                    '<a id="sb-nav-previous" title="{previous}" onclick="Shadowbox.previous()"></a>' +                                '</div>' +                                '<div style="clear:both"></div>' +                            '</div>' +                        '</div>' +                    '</div>' +                '</div>',        options: {            /**             * Determines the sequence of the resizing animations. A value of             * "hw" will resize first the height, then the width. Likewise, "wh"             * will resize the width first, then the height. The default is             * "sync" and will resize both simultaneously.             *             * @var     String             * @public             */            animSequence: 'sync'        },        /**         * Initialization function. Called immediately after this skin's markup         * has been appended to the document with all of the necessary language         * replacements done.         *         * @return  void         * @public         */        init: function(){            // several fixes for IE6            if(S.client.isIE6){                // trigger hasLayout on sb-body                U.get('sb-body').style.zoom = 1;                // support transparent PNG's via AlphaImageLoader                var el, m, re = /url\("(.*\.png)"\)/;                U.each(png, function(id){                    el = U.get(id);                    if(el){                        m = S.lib.getStyle(el, 'backgroundImage').match(re);                        if(m){                            el.style.backgroundImage = 'none';                            el.style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true,src=' +                                m[1] + ',sizingMethod=scale);';                        }                    }                });            }        },        /**         * Gets the element that content should be appended to.         *         * @return  HTMLElement     The body element         * @public         */        bodyEl: function(){            return U.get('sb-body-inner');        },        /**         * Called when Shadowbox opens from an inactive state.         *         * @param   Number      h       The initial height to use         * @param   Number      w       The initial width to use         * @param   Function    cb      The function to call when finished         * @return  void         * @public         */        onOpen: function(h, w, cb){            U.get('sb-container').style.display = 'block';            var d = setDimensions(h, w);            adjustHeight(d.inner_h, d.top, false);            adjustWidth(d.width, d.left, false);            toggleVisible(cb);        },        /**         * Called when a new piece of content is being loaded.         *         * @param   mixed       content     The content object         * @param   Boolean     change      True if the content is changing         *                                  from some previous content         * @param   Function    cb          A callback that should be fired when         *                                  this function is finished         * @return  void         * @public         */        onLoad: function(content, change, cb){            toggleLoading(true);            hideBars(change, function(){ // if changing, animate the bars transition                if(!content) return;                // if opening, clear #sb-wrapper display                if(!change) U.get('sb-wrapper').style.display = '';                cb();            });        },        /**         * Called when the content is ready to be loaded (e.g. when the image         * has finished loading). Should resize the content box and make any         * other necessary adjustments.         *         * @param   Function    cb          A callback that should be fired when         *                                  this function is finished         * @return  void         * @public         */        onReady: function(cb){            resizeContent(function(){                showBars(cb);            });        },        /**         * Called when the content is loaded into the box and is ready to be         * displayed.         *         * @param   Function    cb          A callback that should be fired when         *                                  this function is finished         * @return  void         * @public         */        onFinish: function(cb){            toggleLoading(false, cb);        },        /**         * Called when Shadowbox is closed.         *         * @return  void         * @public         */        onClose: function(){            toggleVisible(false);        },        /**         * Called in Shadowbox.play().         *         * @return  void         * @public         */        onPlay: function(){            toggleNav('play', false);            toggleNav('pause', true);        },        /**         * Called in Shadowbox.pause().         *         * @return  void         * @public         */        onPause: function(){            toggleNav('pause', false);            toggleNav('play', true);        },        /**         * Called when the window is resized.         *         * @return  void         * @public         */        onWindowResize: function(){            var c = S.content;            if(!c) return;            // set new dimensions            var d = setDimensions(c.height, c.width, c.resizable);            adjustWidth(d.width, d.left, false);            adjustHeight(d.inner_h, d.top, false);            var el = U.get(S.contentId());            if(el){                // resize resizable content when in resize mode                if(c.resizable && S.options.handleOversize == 'resize'){                    el.height = d.resize_h;                    el.width = d.resize_w;                }            }        }    };}();