/*!
 * Stack
 *
 * A utility class that helps keeping method calls in sync by
 * managing one or more stacks. "In sync" means that calling
 * a registered method will only be directly executed if there
 * is no other method call in the stack waiting for execution
 * or it is the first one in stack and another method just
 * released the stack lock by calling callStack().
 *
 * Especially useful if you start some async events (Fx,
 * Request, etc.) in one method and have to wait for a
 * completion event which is caught by another method. In the
 * meantime it should be assured that no other synced method
 * is executed.
 *
 * Note: If you call a synced method, there will be no return
 * value if the stack of the called method is currently busy.
 * You can use the isStackLocked() method to check if a stack
 * is currently idle. In that case only you will be able to
 * receive a return value.
 *
 * Warning: If you announce a method call manually with
 * checkStack( arguments ) you _must_ do a call for callStack()
 * to release the stack lock, best at the very end of your method
 * body. If you do not include this call in your code, the stack
 * will be locked forever and no registered method for that
 * specific stack will ever be executed anymore!
 *
 * Besides manually announcing method calls you can always use
 * the utility method registerSyncMethods(). The passed methods
 * will automatically be running in sync.
 *
 * It is possible to register a method for more than one stack.
 * This is a rather complex scenario which requires you to have
 * a very stealth code!
 *
 * Stack-called methods will run in the Class' instance context.
 *
 * Version : 1.2
 * Author  : Felix Zandanel <fz (at) onwebworx (dot) net>
 * Company : zandanel webmedia
 * License : MIT-style license
 */

( function()
{
    var defaultKey = '$default',
        checkKey = function( key )
        {
            if( !key || typeOf( key ) != 'string' || key.length == 0 ) return defaultKey;
            return key;
        },
        registerKey = function( key )
        {
            if( !this.$stack.hasOwnProperty( key ) ) initKey.call( this, key );
        },
        initKey = function( key )
        {
            this.$stack[key] = [];
            this.$stackLock[key] = false;
        }
        registerMethods = function( key, methods )
        {
            var that = this;
        
            key = checkKey( key );
        
            Array.from( methods ).each( function( methodName )
            {
                var original = that[methodName],
                    wrapped;
            
                if( !original || typeOf( original ) != 'function' ) return;
                
                if( original.$original ) original = original.$original;
                
                wrapped = ( function()
                {
                    var args = arguments,
                        value;
                    
                    if( that.checkStack( args, key ) )
                    {
                        value = original.apply( that, args );
                        that.callStack( key );
                        return value;
                    }
                } ).bind( that );

                wrapped.$original = original;
                that[methodName] = wrapped;
            }, that );
        };
    
    this.Stack = new Class( {
        
        $stack: {},
        $stackLock: {},
        
        checkStack: function( args, key )
        {
            var that = this;
            
            key = checkKey( key );
            registerKey.call( that, key );
        
            if( that.$stackLock[key] )
            {
                that.$stack[key].push( args.callee.pass( args, that ) );
                return false;
            }

            return that.$stackLock[key] = true;
        },
        
        callStack: function( key )
        {
            var that = this;
            
            key = checkKey( key );
            registerKey.call( that, key )
            that.$stackLock[key] = false;
            if( that.$stack[key].length ) that.$stack[key].shift()();
        },
        
        clearStack: function( key )
        {
            var that = this;
            
            if( !key ) Object.each( that.$stack, function( value, key ) { initKey.call( that, key ); }, that );
            else initKey.call( that, checkKey( key ) );
        },
        
        isStackLocked: function( key )
        {
            return !!this.$stackLock[checkKey( key )];
        },
        
        registerSyncMethods: function()
        {
            var that = this,
                args = Array.link( arguments, {
                    'map'     : Type.isObject,
                    'key'     : Type.isString,
                    'methods' : Type.isArray
                } );
        
            if( args.map ) Object.each( args.map, function( methods, key ) { registerMethods.call( that, key, methods ); }, that );
            else if( args.methods ) registerMethods.call( that, args.key, args.methods );
        }
        
    } );
  
} )();


/*!
 * SHOW D'VISION Frontend UI
 *
 * Singleton!
 *
 * Version : 1.0
 * Author  : Felix Zandanel <fz (at) onwebworx (dot) net>
 * Company : zandanel webmedia
 * License : MIT-style license
 */

SdvFrontend = 
new new Class( {
    Binds: [
        '_domreadyListener',
    
        '_resetGhost',
        '_resizeGhost',
        
        '_updateGallery',
        
        '_prepareContent',
        '_loadContentImages',
        
        '_prepareOverlay',
        '_overlayOpenListener',
        '_overlayCloseListener',
        '_overlayFailureListener',
        '_overlaySwitchComplete',
        
        '_contentSwitchStartListener',
        '_contentArrivedListener',
        '_dataFailureListener',
        '_preContentSwitchCompleteListener',
        '_contentSwitchCompleteListener',
        
        '_galleryBeforeSlideListener',
        '_maskHiddenListener',
        '_initCompleteListener',
        '_historyChangeListener',
        '_clickListener',
        '_readyListener'
    ],

    debug: false,

    initialize: function()
    {
        if( !this.debug )
        {
            var h = top.location.href,
                baseDomain = 'www.showdvision.com';
            
            baseDomain.replace( /\./, '\.' );
            if( !h.match( '^http:\/\/' + baseDomain ) )
            {
                top.location.href = h.replace( /^http:\/\/[^\/]*(\/.*)$/, 'http://' + baseDomain + '$1' );
                return;
            }
        }
        
        window.addEvent( 'domready', this._domreadyListener );
    },

    _domreadyListener: function()
    {
        var that = this,
            root, mask,
            hasFullIntro,
            mobileDevice;
    
        // IE fix switch
        // Use conditional comments instead of Mootools' Browser object!
        // Remember: This will only work for IE6-8, but as of IE9 we don't need to
        // hack anyway, hooray!
        that.needsFix = $$( 'html' )[0].get( 'class' ).test( /($|\s)ie[\d]+(\s*|^)/ );
        
        // Mobile device?
        isMobile = Browser.isMobile = ( function()
        {
		    var ua = navigator.userAgent.toLowerCase();
		    var systems = ['android', 'iphone', 'ipod', 'ipad', 'windows ce'];
		    for( var i = 0; i < systems.length; i++ ) { if( ua.indexOf( systems[i] ) >= 0 ) return true; }
		    return false;
		} )();
        
        // Intro cookie
        that._cookieName = 'sdv_i'; 
        
        // Analytics key
        that._gaKey = 'UA-25908550-1';
        
        // Base host
        that._baseHost = location.host;
     
        // Initialize layout
        root = that.root = new Element( 'div', { id: 'root' } ).inject( $( document.body ) ).grab( new Element( 'div', { id: 'page-bg' } ) );
        ['header', '#main', 'footer'].each( function( selector )
        {
            var key = selector.replace( /#/, '' );
            that[key + 'Wrapper'] = new Element( 'div', { id: key + '-wrapper' } ).inject( root ).grab( $$( selector )[0] );
        } );
        mask = that.mask = $( 'mask' ).inject( root ).setStyle( 'position', 'absolute' );
        
        // Initialize mobile Layout
        if( isMobile ) that._initApplyMobileLayout();
        
        // Initialize page info
        that._updatePageInfo( window.pi );
        
        // Initially set fallback page
        that._setPrevUrl( that.currentPageInfo.a );
 
        // Display intro?
        hasFullIntro = !isMobile && !Browser.safari && !Browser.webkit && !that._checkCookie();
        
        // Never allow the escape key default actions
        window.addEvent( 'keydown', function( e ) { if( e.key && e.key == 'esc' ) e.preventDefault(); } );
        
        // Init ready listener
        // We have to wait for:
        //   - intro
        //   - layout images
        //   - logo slider
        //   - primary initialization / initial content change (includes gallery)
        that.readyCountdown = new CountdownManager( 4 ).addEvent( 'trigger', that._readyListener );
 
        // Set up spinner and content loader and cache initial page info
        var contentWrapper = $( 'content-wrapper' ),
            spinner = that.spinner = new Spinner( root, {
                imgSrc         : '/img/spinner.gif', 
                modes          : ['content', 'overlay', 'intro'],
                containerId    : 'spinner-wrapper',
                containerClass : 'centered',
                fxDuration     : 200,
                minVisibleTime : 200
            } ),
            overlayContainer = new Element( 'div', { id: 'overlay-wrapper', 'class': 'centered' } ).inject( root ),
            contentLoader = new ContentLoader().store( '/json' + that.currentPageInfo.a, Object.merge( that.currentPageInfo, { c: contentWrapper.getFirst( '.content' ).get( 'html' ) } ), true ),
            urlCheck = function( url )
            {
                if( !url.match( /^\/json/ ) ) url = '/json' + url;
                return url;
            };

        // Set up the main content switcher and inform it that we are still in init phase,
        // so it won't do any time consuming fx that the user doesn't see anyway.
        // Then add some listeners.
        that.contentSwitcher = new MainContentSwitcher( contentWrapper, contentLoader, spinner, {
            contentClass      : 'content',
            slideFxDuration   : 300,
            urlCheckCallback  : urlCheck,
            spinnerMode       : 'content'
        } ).disableFx().addEvents( {
            'goto'                 : that._contentSwitchStartListener,
            'data-arrived'         : that._contentArrivedListener,
            'data-prepare'         : that._prepareContent,
            'data-failure'         : that._dataFailureListener,
            'phase3-complete'      : that._preContentSwitchCompleteListener,
            'phase4-complete'      : that._contentSwitchCompleteListener,
            'phase4-complete:once' : that.readyCountdown.decrease
        } );
        
        // Register async content preparation tasks
        that.contentSwitcher.registerPreparationTasks( [that._updateGallery, that._loadContentImages], true );
        
        // Set up the overlay content switcher
        that.overlaySwitcher = new OverlayContentSwitcher( overlayContainer, mask, contentLoader, spinner, {
            contentClass        : 'overlay-container',
            maskFxDuration      : 350,
            containerFxDuration : 300,
            urlCheckCallback    : urlCheck,
            spinnerMode         : 'overlay'
        } ).addEvents( {
            'goto'            : that._overlayOpenListener,
            'data-prepare'    : that._prepareOverlay,
            'data-failure'    : that._overlayFailureListener,
            'mask-hidden'     : that._maskHiddenListener,
            'close'           : that._overlayCloseListener,
            'phase4-complete' : that._overlaySwitchComplete
        } );
        
        // Fullsize background gallery
        that.gallery = new SlideGallery( root, {
            fadeOnReady         : false,
            minWidth            : isMobile ? 0 : 960,
            verticalCenter      : false,
            horizontalCenter    : true,
            remainOffsetPercent : 6,
            endFxDuration       : 200,
            slideshowInterval   : 5000,
            basePath            : '/files/gallery/',
            protect             : !isMobile
        } ).addEvents( {
            'before-slide'      : that._galleryBeforeSlideListener,
            'picture-set-ready' : that.contentSwitcher.preparationComplete
        } );
        
        // History change listener
        History.addEvent( 'change', that._historyChangeListener );
        
        // General click listener
        root.addEvent( 'click:relay(a)', that._clickListener );
        
        // Initial hash state check. Will eventually trigger an history change here
        // or a straight location redirect. In the last case we will never reach ready
        // status, so the page stays black. That's no problem because a new page will
        // load anyway.
        if( !that._handleInitialState( '/', that.currentPageInfo.a ) )
        {
            that[hasFullIntro ? '_showFullIntro' : '_showLiteIntro']();
        
            that._lateInit();
            
            if( !that.contentSwitchOnInit ) that._reInitPage();
        }
    },
    
    _lateInit: function()
    {
        var that = this;
    
        // Custom scrollbar
        if( true || !Browser.isMobile ) that.boxScroller = new BoxScroller( $( 'main' ), ( function()
        {
            return {
                x: that.contentSections.text.getSize().x + 5,
                y: that.contentSections.text.getStyle( 'top' ).toInt()
            };
        } ) );
        
        // Load all layout assets
        // Gives us control if they are loaded completely to be interpreted as layout ready.
        new ImagePreloader().addEvent( 'load', that.readyCountdown.decrease ).preload( [
            'spinner.gif',
            'arrow.assets.png',
            'button.assets.png',
            'button.inline.png',
            'box.bg.png',
            'nav.assets.png',
            'page.bg.png',
            'sub-nav.bg.png',
            'list.png',
            'overlay.close.png',
            'scroller.assets.png',
            'scroller.bar.png'
        ].append( !Modernizr.rgba ? ['box.bg.png'] : [] ), '/img/' );
        
        // Logo slider
        that.logoSlider = ( function()
        {
            var logoContainer = $( 'logo' ).grab( new Element( 'span', { 'class': 'dvision' } ) ),
                logoLink = logoContainer.getElement( 'a' ),
                lCSizeX = logoContainer.getSize().x,
                afterLogoSwitch = function()
                {
                    var lCLeftDouble = logoContainer.getStyle( 'left' ).toInt() * 2,
                        href = that.currentPageInfo.a.replace( /^(\/json)/, '' ),
                        matches = href.match( /(\/.*){3}/ );
                        
                    if( matches ) href = href.substr( 0, href.length - matches[1].length );
                        
                    logoLink.setStyles( {
                        left: -lCLeftDouble - 10,
                        width: lCSizeX + 20 + lCLeftDouble
                    } ).set( 'href', href );
                };
            
            return new OffsetSlider( logoContainer, {
                imgSrc: '/img/logo.assets.png',
                totalWidth: 269,
                offsetTop: 50,
                singleHeight: 50,
                initialNum: 0,
                singleWidths: {
                    0: 177,
                    1: 171,
                    2: 233,
                    3: 269,
                    4: 180,
                    5: 181,
                    6: 200
                },
                duration: 300,
                animateYAxis: false
            } ).addEvents( {
                'after-switch': afterLogoSwitch,
                'ready': that.readyCountdown.decrease
            } );
        } )();
        
        // Primary nav and caret
        that.primaryNav = that._initApplyPrimaryNav();
        
        // Sub nav
        that.subNav = that._initApplySubNav();
        
        // Footer nav slider
        that._initApplyFooterSlider();
        
        // Header nav slider
        that.header = that._initApplyHeaderSlider();
        
        // Init Hyphenator
        Hyphenator.config( {
            remoteloading: false,
            enablecache: true,
            onerrorhandler: function(){},
            intermediatestate: 'visible',
            safecopy: true,
            storagetype: 'local',
            orphancontrol: 1,
            defaultlanguage: 'en',
            useCSS3hyphenation: true
        } );
        
        // Initial heading font replacement
        Cufon.replace( '#main h1, .overlay-content h1, .btn, ul.buttons span.headline, .bx-nav:not(.bx-nav-small) a span.text', { trim: 'advanced' } ).now();
    },

    
    /** Initial preparation **/
    
    _initApplyPrimaryNav: function()
    {
        var navPri = $( 'nav-pri' ).grab( new Element( 'div', { 'class': 'nav-line' } ) ),
            navWrapper = new Element( 'div', { 'class': 'nav-wrapper' } ).inject( navPri ).grab( navPri.getFirst( 'ul' ) ),
            navElements = navPri.getElements( 'a' ),
            header = $$( 'header' )[0],
            navCaret = new Element( 'div', { id: 'nav-caret', styles: { opacity: 0 } } ).inject( $( 'nav-sub' ) ),
            navCaretFx = new Fx.Morph( navCaret, {
                link     : 'cancel',
                fps      : 'animationFrame',
                duration : 150
            } ),
            img = this.needsFix ? new Element( 'img', { src: '/img/main-nav.assets.png', styles: { position: 'relative' } } ) : null,
            activeElement, previousElement,
            offsetXTrack = -2,
            fxDuration = 120,
            opacityActive = 0.95,
            opacityHighlight = 0.95,
            opacityNormal = 0.5,
            opacityInitial = 0,
            highlightDuration = 500,
            highlightActive = false,
            highlightOnActivate = true,
            highlightEnabled = true,
            getFx = function( element )
            {
                var fx = element.retrieve( 'fx' );
                if( !fx )
                {
                    var parent = element.getParent(),
                        shadow = new Element( 'span', { 'class': 'nav-shadow', styles: { opacity: 0 } } ).inject( parent ),
                        hilit = new Element( 'span', { 'class': 'nav-highlight', styles: { opacity: 0 } } ).inject( parent );
                        
                    fx = new Fx.Elements( [element, shadow, hilit], {
                        link     : 'chain',
                        fps      : 'animationFrame',
                        duration : fxDuration
                    } );
                    element.store( 'fx', fx );
                }
                return fx;
            },
            focus = function( e, element, immediate, highlight, activate )
            {
                var isActive = element == activeElement,
                    fx = getFx( element );
                
                if( highlight )
                {
                    fx.setOptions( { duration: highlightDuration / 2 } );
                    element.store( 'highlight-state', 'focus' );
                }
                else resetFxOptions( fx );
                if( !activate && !highlight ) element.store( 'manual-focus', true ).store( 'highlight-state', false );
                
                fx.cancel()[immediate ? 'set' : 'start']( {
                    0: { opacity: highlight ? opacityHighlight : opacityActive },
                    1: { opacity: highlight && !isActive ? 0 : 1 },
                    2: { opacity: highlight ? 1 : 0 }
                } );
                if( isActive && !navCaretFx.isRunning() ) moveNavCaretToElement( element, immediate );
                if( !immediate ) fx.callChain();
                
                return fx;
            },
            blur = function( e, element, immediate, highlight )
            {
                var hasHighlightFocus = element.retrieve( 'highlight-state' ),
                    isRealHighlight = highlight && hasHighlightFocus,
                    stayFocused = element == activeElement || ( isRealHighlight && element.retrieve( 'manual-focus' ) ),
                    fx = getFx( element );
                
                if( isRealHighlight ) element.store( 'highlight-state', 'blur' );
                else if( highlight && !hasHighlightFocus ) return;
                else
                {
                    resetFxOptions( fx.cancel() );
                    element.store( 'manual-focus', false ).store( 'highlight-state', false );
                }
                
                fx[immediate ? 'set' : 'start']( {
                    0: { opacity: stayFocused ? opacityActive : opacityNormal },
                    1: { opacity: stayFocused ? 1 : 0 },
                    2: { opacity: 0 }
                } );
                if( isRealHighlight ) fx.chain( function()
                {
                    element.store( 'highlight-state', false );
                    fx.callChain();
                } );
                if( element.retrieve( 'reset-highlight' ) ) fx.chain( function()
                {
                    element.store( 'reset-highlight', highlightActive = false );
                    fx.callChain();
                } );
                if( !immediate ) fx.callChain();
                
                return fx;
            },
            activate = function( e, element, immediate )
            {
                if( activeElement )
                {
                    if( element == activeElement ) return;
                    previousElement = activeElement.removeClass( 'active' );
                }
                activeElement = null;
                if( previousElement && ( !highlightOnActivate || immediate || highlightActive ) ) blur( null, previousElement, immediate );
                activeElement = element.addClass( 'active' );
                if( !immediate && !highlightActive && highlightOnActivate ) highlight();
                else focus( null, element, immediate, false, true );
                moveNavCaretToElement( element, immediate );
            },
            resetFxOptions = function( fx )
            {
                return fx.setOptions( { duration: fxDuration } );
            },
            moveNavCaret = function( leftPos, immediate, noOpacityFx )
            {
                navCaretFx[immediate ? 'set' : 'start']( Object.append( {
                    left: leftPos - 7
                }, noOpacityFx ? {} : { opacity: 1 } ) );
            },
            moveNavCaretToElement = function( element, immediate, noOpacityFx )
            {
                moveNavCaret( element.getPosition( header ).x + element.getSize().x / 2, immediate, noOpacityFx );
            },
            positionNavCaret = function( doCenter, immediate )
            {
                if( doCenter ) moveNavCaret( activeElement.getParent( 'ul' ).getSize().x / 2, immediate );
                else moveNavCaretToElement( activeElement, immediate );
                return this;
            },
            highlightElement = function( inputElement, nextHighlighter, isLast )
            {
                var element = inputElement || this,
                    fx = getFx( element ),
                    isActive = activeElement == element;
                
                if( isLast ) element.store( 'reset-highlight', true ); 
                focus( null, element, false, true ).chain( blur.pass( [null, element, false, true] ) );
                if( nextHighlighter ) nextHighlighter.delay( highlightDuration / ( isActive ? 10 : 5 ) );                    
                if( !inputElement ) return this;
            },
            highlight = function()
            {
                if( highlightActive || !highlightEnabled ) return;
                highlightActive = true;

                var i, length = navElements.length - 1,
                    curHighlighter = null;
                    
                for( i = length; i >= 0; i-- ) curHighlighter = highlightElement.pass( [navElements[i], curHighlighter, i == length] );
                
                curHighlighter();
                return this;
            },
            enableHighlight = function()
            {
                highlightEnabled = true;
            },
            disableHighlight = function()
            {
                highlightEnabled = false;
            },
            activateElement = function( immediate )
            {
                activate( null, this, immediate );
                return this;
            },
            elementSetActive = function( immediate, caretNoOpacityFx )
            {
                moveNavCaretToElement( this, immediate, caretNoOpacityFx );
                activeElement = this;
                return this;
            },
            findElement = function( input )
            {
                var found;
                
                if( typeof( input ) == 'string' )
                {
                    var regex = /^\/[a-z]{2}\/([a-zA-Z0-9-_]+)\/?/i,
                        urlSubPart = input.replace( '\/json', '' ).match( regex );
                    if( urlSubPart ) navElements.each( function( element )
                    {
                        if( found ) return;
                        var matchAgainst = element.retrieve( 'main-url-part' );
                        if( !matchAgainst ) element.store( 'main-url-part', matchAgainst = element.get( 'href' ).match( regex )[1] );
                        if( matchAgainst == urlSubPart[1] ) found = element;
                    } );
                }
                else found = $$( '#nav-pri li[data-nav-num="' + input + '"] a' )[0];
                if( found ) return found;
                return navElements[0];
            };
        
        navElements.each( function( element )
        {
            getFx( element.store( 'num', element.getParent().get( 'data-nav-num' ).toInt() ).setStyle( 'opacity', opacityInitial ) );
            
            if( this.needsFix )
            {
                element
                    .grab( img.clone().setStyles( {
                        top: -74,
                        left: offsetXTrack
                    } ) ).setStyle( 'background', 'none' )
                    .getNext()
                    .grab( img.clone().setStyles( {
                        top: -37,
                        left: offsetXTrack
                    } ) ).setStyle( 'background', 'none' )
                    .getNext()
                    .grab( img.clone().setStyles( {
                        top: -111,
                        left: offsetXTrack
                    } ) ).setStyle( 'background', 'none' );
                offsetXTrack -= element.getSize().x;
            }
            
            Object.append( element, {
                setActive : elementSetActive,
                activate  : activateElement,
                highlight : highlightElement
            } );
        }, this );
        
        // Initial highlighting
        this.overlaySwitcher.addEvent( 'before-mask-hidden:once', highlight );
        
        return Object.append( navPri, {
            highlight        : highlight,
            enableHighlight  : enableHighlight,
            disableHighlight : disableHighlight,
            findElement      : findElement,
            positionNavCaret : positionNavCaret
        } ).addEvents( {
            'mouseenter:relay(a)' : focus,
            'mouseleave:relay(a)' : blur,
            'click:relay(a)'      : activate
        } );
    },
    
    _initApplySubNav: function()
    {
        var navSub = $( 'nav-sub' ),
            isMobile = Browser.isMobile,
            bx = $$( '#nav-sub nav' )[0],
            fx = new Fx.Tween( navSub, {
                link     : 'cancel',
                fps      : 'animationFrame',
                duration : 200
            } ).addEvent( 'complete', function()
            {
                if( isMobile && !subNavOpen ) window.removeEvent( 'click', immediateClose );
                forceClose = false;
            } ),
            fxTimer,
            subNavOpen = false,
            forceClose = false,
            open = function()
            {
                clearTimeout( fxTimer );
                if( forceClose ) return;
                if( !subNavOpen )
                {
                    bx.setStyles( {
                        bottom: -16,
                        height: 60
                    } );
                    fx.start( 'height', 44 );
                }
                if( isMobile && !subNavOpen ) window.addEvent( 'click', immediateClose );
                subNavOpen = true;
                
                return this;
            },
            stayOpen = function()
            {
                clearTimeout( fxTimer );
            }, 
            close = function( immediate, force )
            {
                forceClose = force || false;
                if( forceClose ) doClose();
                else
                {
                    if( !immediate ) fxTimer = doClose.delay( 1200 );
                    else doClose();
                }
                return this;
            },
            immediateClose = function()
            {
                close( true );
            },
            doClose = function()
            {
                bx.setStyles( {
                    bottom: 14,
                    height: 30
                } );
                fx.start( 'height', 14 );
                subNavOpen = false;
            };

        if( isMobile )
        {
            $$( '#nav-pri li.nav-show a' ).addEvent( 'click', open );
            new Element( 'li', { html: '<a href="/de/show"><span class="text">SHOW</span></a>' } ).inject( navSub.getElement( 'ul' ), 'top' );
        }
        else
        {
            $$( '#nav-pri li.nav-show a, #nav-sub nav' ).addEvents( {
                'mouseenter': open,
                'mouseleave': close.pass( false )
            } );
        }
        
        return Object.append( navSub, {
            open: function() { open(); return this; },
            close: function( force ) { close( false, force ); return this; }
        } ).addEvent( 'click:relay(a)', close.pass( [false, true] ) );
    },
    
    _initApplyFooterSlider: function()
    {
        var footerSlider = new Element( 'div', { id: 'footer-slider' } ).wraps( $( 'footer-nav' ) ),
            footerSliderWrapper = new Element( 'div', { id: 'footer-slider-wrapper', 'class': 'clearfix' } ).wraps( footerSlider ).grab( $( 'btn-contact' ), 'top' ),
            footerSliderSize = $( 'footer-slider' ).getSize(),
            fx = new Fx.Tween( footerSliderWrapper, {
                link     : 'cancel',
                fps      : 'animationFrame',
                duration : 250
            } ),
            isMobile = Browser.isMobile,
            fxTimer,
            footerSliderOpen = false,
            open = function()
            {
                clearTimeout( fxTimer );
                if( !footerSliderOpen ) fx.start( 'height', footerSliderSize.y );
                footerSliderOpen = true;
                if( isMobile ) close( false, 3500 );
                return this;
            },
            stayOpen = function()
            {
                clearTimeout( fxTimer );
            },
            close = function( immediate, delay )
            {
                if( !immediate ) fxTimer = doClose.delay( delay || 800 );
                else doClose();
                return this;
            },
            doClose = function()
            {
                fx.start( 'height', 60 );
                footerSliderOpen = false;
            };
        
        footerSliderWrapper.setStyles( {
            'height' : 60,
            'width'  : footerSliderSize.x
        } ).addEvent( 'click:relay(a)', close.pass( true ) );
        
        new Element( 'span', { 'class': 'more-trigger' } ).inject( $$( '#footer-right li.more' )[0] );
        
        if( isMobile )
        {
            $$( '#footer-right li.more' ).addEvent( 'click', open );
        }
        else
        {
            $$( '#footer-right li.more' ).addEvents( {
                'mouseenter' : open,
                'mouseleave' : close.pass( false )
            } );
            
            footerSliderWrapper.addEvents( {
                'mouseenter' : stayOpen,
                'mouseleave' : close.pass( false )
            } );
        }
    },
    
    _initApplyHeaderSlider: function()
    {
        var header = $$( 'header' )[0],
            primaryNav = this.primaryNav,
            subNav = this.subNav,
            mainWrapper = this.mainWrapper,
            isMobile = Browser.isMobile,
            mobileTrigger = isMobile ? new Element( 'div', { id: 'header-nav-trigger', styles: { display: 'none' } } ).inject( header ) : null,
            fx = new Fx.Elements( [primaryNav, mainWrapper], {
                link     : 'chain',
                fps      : 'animationFrame',
                duration : 150
            } ).addEvent( 'complete', ( function()
            {
                if( !noResizeGhost ) this._resizeGhost();
            } ).bind( this ) ),
            fxTimer,
            headerNavOpen = true,
            mouseOver = false,
            enabled = false,
            noResizeGhost = false,
            enableAutoHide = function( state )
            {
                enabled = state;

                if( !state || mouseOver ) open();
                else if( !mouseOver ) close();
                
                return this;
            },
            setNoResizeGhost = function( state )
            {
                noResizeGhost = state;
            },
            open = function( onComplete )
            {
                onComplete = typeOf( onComplete ) == 'function' ? onComplete : false;
                
                clearTimeout( fxTimer );
                if( !headerNavOpen )
                {
                    fx.cancel().start( {
                        0: { height: 38 },
                        1: { top: 230 }
                    } );
                    
                    if( isMobile )
                    {
                        mobileTrigger.setStyle( 'display', 'none' );
                        close();
                    }
                    if( onComplete ) fx.chain( onComplete );
                    primaryNav.positionNavCaret( false );
                }
                else if( onComplete ) onComplete();
                headerNavOpen = true;
                return this;
            },
            close = function( immediate )
            {
                if( !enabled ) open();
                else if( !immediate ) fxTimer = doClose.delay( isMobile ? 1000 : 2000 );
                else doClose();
                return this;
            },
            doClose = function()
            {
                if( isMobile ) mobileTrigger.setStyle( 'display', 'block' );
                fx.cancel().start( {
                    0: { height: 1 },
                    1: { top: 190 }
                } );
                
                primaryNav.positionNavCaret( true );
                subNav.close( true );
                headerNavOpen = false;
            },
            mouseenter = function()
            {
                mouseOver = true;
                open();
            },
            mouseleave = function()
            {
                mouseOver = false;
                close();
            };
        
        if( isMobile )
        {
            mobileTrigger.addEvent( 'click', open.pass( null ) );
        }
        else
        {
            this.headerWrapper.addEvents( {
                'mouseenter': mouseenter,
                'mouseleave': mouseleave
            } );
        }
        
        return Object.append( header, {
            open             : open,
            close            : close,
            enableAutoHide   : enableAutoHide,
            setNoResizeGhost : setNoResizeGhost
        } );
    },
    
    _initApplyMobileLayout: function()
    {
        var that = this;
    
        $( document.body ).setStyle( 'background', '#0000ff' );
    
        /*
        that.root.setStyles( {
            position: 'relative',
            width: 1000,
            overflow: 'hidden',
            top: -1000
        } );
        */
        
        /*
        that.headerWrapper.setStyle( 'position', 'static' );
        that.mainWrapper.setStyle( 'position', 'static' );
        that.footerWrapper.setStyle( 'position', 'static' );
        */
        
        /*
        new iScroll( document.body, {
            hScroll: false,
            vScroll: true,
            zoom: true
        } );
        */
        
        window.addEvent( 'orientationchange', that._orientationListener.bind( that ) );
        that._orientationListener();
        
        //that.root.store( 'pinch:threshold', 20 );
        //that.root.addEvent( 'pinch', that._pinchListener.bind( that ) );
    },
    
    _orientationListener: function()
    {
        var that = this,
            wSize = window.getSize();
            
        console.log( 'x: ' + wSize.x + ', y: ' + wSize.y );
        
        this.root.setStyles( {
            width: wSize.x,
            height: wSize.y
        } );
    },
    
    _pinchListener: function( e )
    {
        var that = this;
    
        that.testEl.set( 'html', 'pinch: ' + e.pinch + ', scale: ' + e.scale );
    },
    
    
    /** Helpers **/
    
    _checkCookie: function()
    {
        var cookieName = this._cookieName,
            introCookie = Cookie.read( cookieName );
    
        if( !introCookie || introCookie == '0' )
        {
            Cookie.write( cookieName, '1', { duration: false } );
            return false;
        }
        
        return true;
    },
    
    _handleInitialState: function( base, initPath )
    {
        var location = window.location,
            pathname = location.pathname,
            hash = location.hash.substr( 1 ),
            hasPushState = History.hasPushState();

        if( !hasPushState )
        {
            if( pathname != base )
            {
                window.location = base + '#' + pathname;
                return true;
            }
            else if( hash.length ) History.onChange( hash );
            else History.replace( initPath );
        }
        else if( hash.length )
        {
            if( hash != base ) History.push( hash );
            else location.hash = '';
        }
        else if( History.getPath() != initPath ) History.replace( initPath );

        return false;
    },
    
    _gaTrack: function()
    {
        if( _gat && !this.debug ) _gat._getTracker( this._gaKey )._trackPageview( this.currentPageInfo.a );
    },
    
    _setPrevUrl: function( url )
    {
        this.prevUrl = url;
    },
    
    
    /** Layout fixups **/
    
    _resetGhost: function()
    {
        this._resizeGhost( true );
    },
    
    _resizeGhost: ( function()
    {
        var ghost;
    
        return function( disable )
        {
            if( Browser.isMobile ) return;
        
            if( !ghost ) ghost = new Element( 'div', { id: 'ghost' } ).inject( $( document.body ) );

            var csText = this.contentSections.text,
                ghostY;
            
            if( !disable )
            {
                if( !csText || !csText.getParent() ) disable = true;
                else
                {
                    var scrollSizeY = csText.getScrollSize().y,
                        sizeY = csText.getSize().y;
                    
                    if( sizeY < scrollSizeY ) ghostY = window.getSize().y + ( scrollSizeY - sizeY );
                    else disable = true;
                }
            }
            
            if( disable )
            {
                ghost.setStyle( 'display', 'none' );
            }
            else
            {
                ghost.setStyles( {
                    display : 'block',
                    height  : ghostY
                } );
            }

            if( this.boxScroller ) this.boxScroller[disable ? 'disable' : 'enable']();
            
            window.scrollTo( 0, csText.getScroll().y );
        };
    } )(),
    
    
    /** Display certain things **/
    
    _showFullIntro: function()
    {
        var doAnimation1 = !$$( 'html' )[0].get( 'class' ).test( /($|\s)ie(6|7)(\s*|^)/ ) && !Browser.safari,
            introContainerWrapper = new Element( 'div', { id: 'intro-wrapper', 'class': 'centered' } ).inject( $( 'mask' ) ),
            introContainer = new Element( 'span' ).inject( introContainerWrapper ),
            logo = new Element( 'img', { 'class': 'logo', styles: { opacity: 0 }, src: '/img/logo.intro.gif' } ).inject( introContainer ),
            subline = new Element( 'img', { 'class': 'subline', styles: { opacity: 0 }, src: '/img/logo.sub.gif' } ).inject( introContainer ),
            spinner = new Element( 'img', { 'class': 'spinner', styles: { opacity: 0 }, src: '/img/spinner.gif' } ).inject( introContainer ),
            animation1 = ( function()
            {
                var logoEndStyles = {
                    top: 50,
                    left: 66,
                    width: 450,
                    height: 44
                };
            
                if( !doAnimation1 ) logo.setStyles( logoEndStyles );
            
                new Fx.Morph( logo, {
                    fps      : 'animationFrame',
                    duration : doAnimation1 ? 1500 : 800
                } ).addEvent( 'complete', animation2 ).start( doAnimation1 ? Object.merge( logoEndStyles, { opacity: 1 } ) : { opacity: 1 } );
            } ).bind( this ),
            animation2 = ( function()
            {
                new Fx.Elements( [subline, spinner], {
                    fps      : 'animationFrame',
                    duration : 250
                } ).addEvent( 'complete', ( function() { this.readyCountdown.decrease.delay( 1500 ); } ).bind( this ) ).start( {
                    0: { opacity: 1 },
                    1: { opacity: 1 }
                } );
            } ).bind( this );
            
        this.introContainer = introContainer;
    
        new ImagePreloader().addEvent( 'load', animation1 ).preload( [
            'spinner.gif',
            'logo.intro.gif',
            'logo.sub.gif'
        ], '/img/' );
        
        this.overlaySwitcher.addEvent( 'mask-hidden:once', function() { introContainerWrapper.destroy(); } );
    },
    
    _showLiteIntro: function()
    {
        this.spinner.show( 'intro' );
        this.readyCountdown.decrease.delay( 2000 );
    },


    /** Update internal data **/

    _updatePageInfo: function( pageInfo )
    {
        this.currentPageInfo = Object.append( {
            a: '',
            l: 'en',
            lr: ['en'],
            t: '',
            pn: 0,
            s: false,
            g: [],
            c: ''
        }, typeof( pageInfo ) == 'string' ? JSON.decode( pageInfo ) : pageInfo );
    },

    _updateContentSections: function()
    {
        var contentSections = this.contentSections = {},
            sectionClasses,
            sectionName;
        
        $$( '.content' )[0].getChildren().each( function( sectionElement )
        {
            sectionClasses = ( sectionElement.get( 'class' ) || '' ).match( /content-(.*?)-section/ );
            if( sectionClasses && sectionClasses.length == 2 )
            {
                sectionName = sectionClasses[1];
                if( !contentSections.hasOwnProperty( sectionName ) )
                {
                    contentSections[sectionName] = sectionElement;
                    return;
                }
            }
            sectionElement.destroy();
        }, this );
    },
    
    _updateGallery: function()
    {
        var pictures = [];
        this.currentPageInfo.g.each( function( src ) { pictures.push( this.currentPageInfo.ga + '/' + src ); }, this );
        this.gallery.setPictures( pictures );
    },


    /** Content preparation and manipulation **/
    
    _prepareContent: function()
    {
        this._updateContentSections();
        
        Cufon.refresh();
        
        this._applyTitle();
        this._applyHyphenation();
        this._applyNavArrows();
        this._applyButtonTrigger();
        this._applyContentLayout();
        this._applyContentButtons();
        
        if( Browser.isMobile ) this._applyMobileLayout();
        else this._applyBoxScroller();
        
        if( !this.ready ) this.primaryNav.findElement( History.getPath() ).setActive( true, true );
        else
        {
            this.primaryNav.findElement( this.currentPageInfo.pn ).activate();
            if( History.getPath() != this.currentPageInfo.a ) History.replace( this.currentPageInfo.a );
        }
    },
    
    _loadContentImages: function()
    {
        var imgElements = $$( '.content' )[0].getElements( 'img' ),
            callback = this.contentSwitcher.preparationComplete;
        
        if( imgElements.length ) new ImagePreloader().addEvent( 'load', callback ).preload( imgElements );
        else callback.delay( 1 );
    },
    
    _getGalleryNumberContainer: function()
    {
        return $$( '#main .btn-gallery-nav .number span' )[0];
    },

    _applyTitle: function()
    {
        var title = this.currentPageInfo.t;
        document.title = ( title.length ? title + ' • ' : '' ) + "SHOW D'VISION • UDO LANDOW";
    },

    _applyNavArrows: ( function()
    {
        var arrowOn = new Element( 'span', { 'class': 'arrow-on', styles: { left: 0, opacity: 0 } } ),
            arrowOff = new Element( 'span', { 'class': 'arrow-off', styles: { left: 0, opacity: 1 } } ),
            img, needsFix,
            normalColor = '#dadadc',
            hoverColor = '#f39100',
            getFx = function( element )
            {
                var fx = element.retrieve( 'fx' );
                if( !fx )
                {
                    var aOn = arrowOn.clone().inject( element ),
                        aOff = arrowOff.clone().inject( element );
                
                    if( needsFix )
                    {
                        aOn.grab( img.clone() ).setStyle( 'background', 'none' );
                        aOff.grab( img.clone() ).setStyle( 'background', 'none' );
                    }
                    
                    element.store( 'fx', fx = new Fx.Elements( [aOff, aOn], {
                        link     : 'cancel',
                        fps      : 'animationFrame',
                        duration : 100
                    } ) );
                }
                return fx;
            },
            focus = function( e, element )
            {
                if( element.retrieve( 'needs-cufon' ) ) Cufon.replace( element.getElement( 'h1' ) || element.getElement( 'span.text' ), { color: hoverColor } );
                
                var arrowFocusOffset = element.retrieve( 'arrow-focus-offset' );
                getFx( element ).start( {
                    0: {
                        left: arrowFocusOffset,
                        opacity: 0
                    },
                    1: {
                        left: arrowFocusOffset,
                        opacity: 1
                    }
                } );
            },
            blur = function( e, element )
            {
                if( element.retrieve( 'needs-cufon' ) ) Cufon.replace( element.getElement( 'h1' ) || element.getElement( 'span.text' ), { color: normalColor } );
            
                getFx( element ).start( {
                    0: {
                        left: 0,
                        opacity: 1
                    },
                    1: {
                        left: 0,
                        opacity: 0
                    }
                } );
            };
    
        return function()
        {
            needsFix = this.needsFix;
            if( !img && needsFix ) img = new Element( 'img', { src: '/img/arrow.assets.png' } );
            
            $$( '.bx-nav:not(.bx-nav-speakers)' ).each( function( element )
            {
                if( !element.retrieve( 'nav-arrows-applied' ) )
                {
                    var isSmallNavBox = element.hasClass( 'bx-nav-small' ),
                        arrowFocusOffset = isSmallNavBox ? 2 : 4,
                        needsCufon = !isSmallNavBox;
                        
                    element.getElements( 'a' ).each( getFx ).each( function( link ) { link.store( 'arrow-focus-offset', arrowFocusOffset ).store( 'needs-cufon', needsCufon ) } );
                    
                    element.store( 'nav-arrows-applied', true ).addEvents( {
                        'mouseenter:relay(a)': focus,
                        'mouseleave:relay(a)': blur
                    } );
                }
            } );
        };
    } )(),

    _applyButtonTrigger: function()
    {
        $$( '.btn' ).each( function( element )
        {
            if( !element.retrieve( 'btn-trigger-applied' ) )
            {
                var type = element.get( 'data-button-trigger' );
                if( !type ) return;

                switch( type )
                {
                    case 'page-nav':
                        ( function( element )
                        {
                            var sections = this.contentSections,
                                buttonText = element.getElement( 'span' ),
                                pathSection = sections.path,
                                navSection = sections.nav,
                                textSection = sections.text,
                                buttonSection = sections.button,
                                pictureSection = sections.picture,
                                navUrl = element.get( 'data-nav-page' ),
                                fx = new Fx.Elements( [textSection, navSection].append( pathSection ? [pathSection] : [] ).append( pictureSection ? [pictureSection] : [] ), {
                                    link     : 'cancel',
                                    fps      : 'animationFrame',
                                    duration : 200
                                } ).addEvent( 'complete', ( function()
                                {
                                    if( !navOpen )
                                    {
                                        navSection.setStyle( 'display', 'none' );
                                        this.header.setNoResizeGhost( false );
                                        this._resizeGhost();
                                    }
                                } ).bind( this ) ),
                                fxTimer,
                                normalColor = '#dadadc',
                                hoverColor = '#f39100',
                                mouseOver = false,
                                navOpen = false,
                                setMouseOverState = function( state )
                                {
                                    clearTimeout( fxTimer );
                                    mouseOver = state;
                                    if( !mouseOver ) close();
                                    else if( navOpen ) open();
                                },
                                checkOverlap = function()
                                {
                                    return ( textSection.getFirst().getComputedSize( { mode: 'vertical' } ).totalHeight + textSection.getStyle( 'top' ).toInt() + navSection.getComputedSize( { mode: 'vertical' } ).totalHeight + navSection.getStyle( 'bottom' ).toInt() ) > textSection.getParent().getSize().y;
                                },
                                checkSpace = function()
                                {
                                    return navSection.getParent().getSize().y > ( navSection.getComputedSize( { mode: 'vertical' } ).totalHeight + navSection.getStyle( 'bottom' ).toInt() );
                                },
                                click = function( e )
                                {
                                    if( Browser.isMobile || !checkSpace() )
                                    {
                                        History.push( navUrl );
                                        e.stop();
                                    }
                                },
                                open = ( function( buttonTriggered )
                                {
                                    if( buttonTriggered ) Cufon.replace( buttonText, { color: hoverColor } );

                                    if( navSection )
                                    {
                                        if( !checkSpace() ) return;

                                        this.header.setNoResizeGhost( true );
                                        this._resetGhost();

                                        clearTimeout( fxTimer );
                                        
                                        var overlap = checkOverlap();
                                        fx.start( {
                                            0: overlap ? {
                                                left: 50,
                                                opacity: 0
                                            } : {},
                                            1: {
                                                display: 'block',
                                                left: 0,
                                                opacity: 1
                                            },
                                            2: overlap ? {
                                                left: 50,
                                                opacity: 0
                                            } : {},
                                            3: overlap ? {
                                                left: 560,
                                                opacity: 0
                                            } : {}
                                        } );
                                        
                                        navOpen = true;
                                    }
                                } ).bind( this ),
                                close = function( buttonTriggered )
                                {
                                    if( buttonTriggered ) Cufon.replace( buttonText, { color: normalColor } );
                                    if( navSection ) fxTimer = doClose.delay( 100 );
                                },
                                doClose = function()
                                {
                                    fx.start( {
                                        0: {
                                            left: 0,
                                            opacity: 1
                                        },
                                        1: {
                                            left: -50,
                                            opacity: 0
                                        },
                                        2: {
                                            left: 0,
                                            opacity: 1
                                        },
                                        3: {
                                            left: 510,
                                            opacity: 1
                                        }
                                    } );
                                    navOpen = false;
                                };
                                
                            if( navSection )
                            {
                                var navSectionSize = navSection.getDimensions(),
                                    zIndex = 105;
                                    
                                [navSection, textSection, buttonSection].append( pathSection ? [pathSection] : [] ).append( pictureSection ? [pictureSection] : [] ).each( function( element ) { element.setStyle( 'z-index', zIndex-- ); } );
                                [
                                    navSection,
                                    buttonSection,
                                    new Element( 'div', { styles: {
                                        position: 'absolute',
                                        bottom: 0,
                                        left: 0,
                                        width: navSectionSize.x,
                                        height: buttonSection.getSize().y + navSectionSize.y + 150,
                                        'z-index': 100
                                    } } ).inject( buttonSection.getParent() )
                                ].each( function( element )
                                {
                                    element.addEvents( {
                                        'mouseenter': setMouseOverState.pass( true ),
                                        'mouseleave': setMouseOverState.pass( false )
                                    } );
                                } );
                            }
                            
                            element.addEvents( {
                                'mouseenter': open.pass( true ),
                                'mouseleave': close.pass( true ),
                                'click': click
                            } );
                        } ).call( this, element );
                        break;
                        
                    case 'overlay-nav':
                        ( function( element, overlay )
                        {
                            var normalColor = '#dadadc',
                                hoverColor = '#f39100',
                                buttonText = element.getElement( 'span' ),
                                overlayUrl = element.get( 'data-overlay' ),
                                focus = function()
                                {
                                    Cufon.replace( buttonText, { color: hoverColor } );
                                },
                                blur = function()
                                {
                                    Cufon.replace( buttonText, { color: normalColor } );
                                };
                                
                            element.addEvents( {
                                'mouseenter': focus,
                                'mouseleave': blur
                                
                                // Note: click is globally handled by _clickListener()
                            } );
                        } ).apply( this, [element, this.overlay] );
                    
                        break;
                        
                    case 'gallery-nav':
                        ( function( element, gallery )
                        {
                            var prevSlide = function() { gallery.prev(); },
                                nextSlide = function() { gallery.next(); };
                            
                            element.addEvents( {
                                'click:relay(a.prev)': prevSlide,
                                'click:relay(a.next)': nextSlide                                
                            } );
                        } ).apply( this, [element, this.gallery] );
                        break;
                }
                
                element.store( 'btn-trigger-applied', true );
            }
        }, this );
    },

    _applyContentLayout: function()
    {
        var cs = this.contentSections,
            csBtnY = cs.button ? cs.button.getSize().y : 0;
        
        cs.text.setStyles( {
            top: cs.path ? cs.path.getSize().y : 0,
            bottom: cs.text.getSize().x < 640 ? ( csBtnY || 10 ) : ( csBtnY || 60 )
        } );
        
        if( cs.nav ) cs.nav.setStyles( {
            bottom: csBtnY,
            left: -50,
            display: 'none',
            opacity: 0
        } );
        
        if( cs.picture ) cs.picture.setStyle( 'top', cs.path ? cs.path.getSize().y : 0 );
        
        var navBox = $$( '.content' )[0].getElement( '.bx-nav' );
        if( navBox )
        {
            var navElements = navBox.getElements( 'li' ),
                origNavElementsContainer = navBox.getElement( 'ul' ),
                navConfig,
                columnCount = 0,
                curColContainer = null,
                curColElementCount = 0,
                normalColor = '#dadadc',
                hoverColor = '#f39100',
                getLinkHeadline = function( link )
                {
                    var headline = link.retrieve( 'headline' );
                    if( !headline )
                    {
                        headline = link.getElement( 'h1' );
                        link.store( 'headline', headline );
                    }
                    return headline;
                },
                focusLink = function( e, link )
                {
                    Cufon.replace( getLinkHeadline( link ), { color: hoverColor } );
                },
                blurLink = function( e, link )
                {
                    Cufon.replace( getLinkHeadline( link ), { color: normalColor } );
                };
            
            if( !navElements.length )
            {
                navBox.destroy();
                return;
            }
            else if( navConfig = navBox.get( 'data-nav-config' ) )
            {
                navConfig = navConfig.match( /(\d+)(\-|$)/g ).map( function( num ) { return num.replace( /\-/g, '' ).toInt(); } );
                navElements.each( function( navElement )
                {
                    if( navConfig.length || curColElementCount )
                    {
                        if( !curColContainer || !curColElementCount )
                        {
                            curColContainer = new Element( 'ul' ).inject( navBox );
                            curColElementCount = navConfig.shift();
                            columnCount++;
                        }
                        curColElementCount--;
                    }
                    
                    navElement.inject( curColContainer );
                } );
            }
            else if( navElements.length > minElementsPerColumn )
            {
                var minElementsPerColumn = 4,
                    elementsPerColumn = Math.max( minElementsPerColumn, Math.ceil( navElements.length / navBox.get( 'data-nav-columns' ).toInt() ) );
                
                navElements.each( function( navElement )
                {
                    if( !curColContainer || curColElementCount >= elementsPerColumn )
                    {
                        curColContainer = new Element( 'ul' ).inject( navBox );
                        curColElementCount = 0;
                        columnCount++;
                    }
                    navElement.inject( curColContainer );
                    curColElementCount++;
                } );
            }
            
            origNavElementsContainer.destroy();
            
            if( navBox.hasClass( 'bx-nav-speakers' ) )
            {
                origDisplayNone = navBox.getParent().getStyle( 'display' ) == 'none';
                if( origDisplayNone ) navBox.getParent().setStyle( 'display', 'block' );
            
                var rowsMaxY = {},
                    columns = navBox.getElements( 'ul' ),
                    curRow = 0,
                    curCol = 0;
            
                navBox.getChildren( 'ul' ).each( function( column )
                {
                    curRow = 0;
                    column.getChildren( 'li' ).each( function( el )
                    {
                        if( el.get( 'data-no-height-check' ) ) return;
                    
                        var elY = el.getSize().y;
                        if( !rowsMaxY[curRow] ) rowsMaxY[curRow] = elY;
                        else rowsMaxY[curRow] = Math.max( rowsMaxY[curRow], elY );
                        
                        curRow++;
                    } );
                    curCol++;
                } ).each( function( column )
                {
                    curRow = 0;
                    column.getElements( 'li' ).each( function( el )
                    {
                        if( el.get( 'data-no-height-check' ) ) return;
                        el.setStyle( 'height', rowsMaxY[curRow] );
                        el.getFirst( 'a' ).setStyle( 'height', rowsMaxY[curRow] );
                        curRow++;
                    } );
                } );
                
                navBox.addEvents( {
                    'mouseenter:relay(a)': focusLink,
                    'mouseleave:relay(a)': blurLink
                } );
                
                if( origDisplayNone ) navBox.getParent().setStyle( 'display', 'none' );
            }
        }
    },
    
    _applyBoxScroller: function()
    {
        if( this.boxScroller && this.contentSections.text ) this.boxScroller.disable().lock( true ).attach( this.contentSections.text, window );
    },
    
    _applyContentButtons: function()
    {
        var normalColor = '#dadadc',
            hoverColor = '#f39100',
            focus = function( e, link )
            {
                Cufon.replace( link.getElement( 'span.headline' ), { color: hoverColor } );
            },
            blur = function( e, link )
            {
                Cufon.replace( link.getElement( 'span.headline' ), { color: normalColor } );
            };
    
        $$( '.content ul.buttons' ).each( function( list )
        {
            if( !list.retrieve( 'button-actions-applied' ) )
            {
                list.addEvents( {
                    'mouseenter:relay(a)': focus,
                    'mouseleave:relay(a)': blur
                } );
            
                list.store( 'button-actions-applied', true );
            }
        } );
    },
    
    _applyHyphenation: function()
    {
        var language = this.currentPageInfo.l;
        this.contentSections.text.getElements( 'p' ).each( function( txtElement )
        {
            Hyphenator.hyphenate( txtElement, language );
            
            // Words can be wrapped with <span class="donthyphenate">...</span>.
            // Normally Hyphenator can deal with this, but not when used with Hyphenator.hyphenate().
            // So we try to find all words that are wrapped and remove all soft hyphens now after
            // Hyphenator previously added them.
            txtElement.getElements( 'span.donthyphenate' ).each( function( element ) { element.set( 'html', element.get( 'html' ).replace( /\u00AD/g, '' ) ); } );    
        } );
    },
    
    _applyMobileLayout: function()
    {
        
    },

    
    /* Event listeners for overlays */
    
    _prepareOverlay: function( element, data )
    {
        if( data.av )
        {
            if( Browser.Plugins.Flash.version )
            {
                this.jwplayer = jwplayer( 'video_container' ).setup( {
                    'width': 600,
                    'height': data.vy / data .vx * 600,
                    'file': '/files/video/' + data.av + '/video.flv',
                    'skin': '/lib/jwplayer/bekle.zip',
                    'controlbar': 'over',
                    'autostart': 'true',
                    'stretching': 'full',
                    'flashplayer': '/lib/jwplayer/player.swf'
                } );
            }
            else
            {
                this.jwplayer = jwplayer( 'video_container' ).setup( {
                    'width': 600,
                    'height': data.vy / data .vx * 600,
                    'file': '/files/video/' + data.av + '/video.mp4',
                    'skin': '/lib/jwplayer/bekle.zip',
                    'controlbar': 'over',
                    'autostart': 'true',
                    'stretching': 'full',
                    'modes': [{type: 'html5'}]
                } );
            }
        }
        
        if( $( 'overlay-content-contact' ) )
        {
            var form = $( $$( '#overlay-content-contact form' )[0] ),
                submitListener = ( function( e )
                {
                    e.stop();
                    this.overlaySwitcher.setCloseLock( true );
                    this.overlaySwitcher.open( form.get( 'action' ), true, form.toQueryString() );
                    form.getElement( 'input[type=submit]' ).set( 'disabled', 'disabled' ).setStyle( 'opacity', 0.5 );
                } ).bind( this );
                
            form.addEvent( 'submit', submitListener );
            form.getElement( 'input[type=submit]' ).addEvent( 'click', submitListener ).addEvent( 'touch', submitListener );
        }
    
        Cufon.refresh();
        
        var bSize = element.getFirst().getSize();
        
        element.setStyles( {
            width: bSize.x,
            height: bSize.y,
            left: -bSize.x / 2,
            top: -bSize.y / 2
        } );
    },
    
    _overlayOpenListener: function()
    {
        if( this.boxScroller ) this.boxScroller.lock( true );
        this._resetGhost();
    },
    
    _overlayCloseListener: function()
    {
        if( this.jwplayer )
        {
            this.jwplayer.remove();
            var video = $$( 'video' )[0];
            if( video ) video.destroy();
        }
    },
    
    _overlayFailureListener: function()
    {
        this.overlaySwitcher.setCloseLock( false ).close();
    },
    
    _overlaySwitchComplete: function()
    {
        this.overlaySwitcher.setCloseLock( false );
    }, 
    
    
    /** Event listeners for the content **/
    
    _contentSwitchStartListener: function()
    {
        if( this.ready )
        {
            this.header.enableAutoHide( false );
            this.gallery.stopSlideshow();
        }
    },
    
    _contentArrivedListener: function( uid, url, data )
    {
        this._setPrevUrl( data.a );
        this._updatePageInfo( data );
    },
    
    _dataFailureListener: function()
    {
        this._reInitPage();
    },
    
    _preContentSwitchCompleteListener: function()
    {
        if( this._getGalleryNumberContainer() ) this._galleryBeforeSlideListener( null, 0, this.gallery.vars.length );
    
        this.logoSlider.switchTo( this.currentPageInfo.pn, !this.ready );
    
        if( this.ready ) this.gallery.next();
        
        if( this.ready && this.currentPageInfo.s ) this.gallery.startSlideshow();
    },
    
    _contentSwitchCompleteListener: function()
    {    
        if( this.ready )
        {
            if( this.boxScroller && this.overlaySwitcher.isClosed() ) this.boxScroller.lock( false );
            this._resizeGhost();
            
            this.header.enableAutoHide.delay( 500, this.header, true );
            
            window.scrollTo( 0, 0 );
        }
        
        this._gaTrack();
    },
    
    
    /** Event listeners for the gallery **/
    
    _galleryBeforeSlideListener: function( slideElement, index, length )
    {
        var numContainer = this._getGalleryNumberContainer();
        if( numContainer )
        {
            numContainer.set( 'html', length - index );
            Cufon.replace( numContainer.getParent() );
        }
    },
    
    
    /** Some other event listeners **/
    
    _maskHiddenListener: function()
    {
        if( this.ready )
        {
            if( this.boxScroller ) this.boxScroller.lock( false );
            this._resizeGhost();
        }
    },
    
    _readyListener: function()
    {
        this.ready = true;
        
        this.contentSwitcher.enableFx();
        if( this.boxScroller ) this.boxScroller.lock( false ).enable();
        
        // Initially hide the mask
        this.overlaySwitcher.initFadeOutMask();
        this.header.enableAutoHide( false ).enableAutoHide.delay( 2000, this.header, true );
        if( this.currentPageInfo.s ) this.gallery.startSlideshow.delay( 200 );
        
        this.spinner.hide( 'intro' );
        
        // Listen on viewport resize
        window.addEvent( 'resize', this._resizeGhost );
    },
    
    
    /** Content switch trigger **/
    
    _reInitPage: function()
    {
        this.contentSwitcher.goTo( this.prevUrl, false, null, {
            prepareData: function() { return $$( '.content' )[0]; }
        } );
    },
    
    _historyChangeListener: function( url, force )
    {
        // Never allow hash signs in urls when the browser supports pushState
        if( !force && ( this.currentPageInfo.a == url || !url.length || ( url.indexOf( '#' ) != -1 && History.hasPushState() ) ) ) return;
        
        if( this.ready )
        {
            // Make sure that the primary navigation is always visible during content switches
            this.header.open( ( function() { this.primaryNav.findElement( url ).activate(); } ).bind( this ) );
            this.subNav.close( true );
            
            if( this.boxScroller ) this.boxScroller.detach().lock( true );
            this._resetGhost();
        }
        
        // We are in the middle of initialization and the initial state check
        // fired an history change. So load this new content prior the mask to be faded out
        else this.contentSwitchOnInit = true;
        
        this.contentSwitcher.goTo( url );
    },
    
    _clickListener: function( e, link )
    {
        var newLanguage = link.get( 'data-language-switch' ),
            overlayUrl = link.get( 'data-overlay' ),
            href = link.get( 'href' ),
            target = link.get( 'target' ),
            isExternalHref = ( function() { return href.match( /\.pdf$/ ) || ( href.match( /^http:\/\// ) && !href.match( '^http:\/\/' + location.host + '(\/|$)' ) ); } )(),
            isMailTo = href.match( /^mailto:/ );
            
        // External page to in new window/tab?
        if( ( target && target == '_blank' ) || isExternalHref || isMailTo ) return;
        
        // Never let the browser redirect on its own!
        e.preventDefault();
        
        // Language change?
        if( newLanguage )
        {
            // Do a straight redirect here!
            if( newLanguage != this.currentPageInfo.l )
            {
                var abort = true;
                if( this.currentPageInfo.lr ) this.currentPageInfo.lr.each( function( lang ) { if( lang == newLanguage ) abort = false; } );
            
                if( !abort ) top.window.location = History.getPath().replace( /^\/(de|en)\//i, '/' + newLanguage + '/' );
                return;
            }
        }
        
        // Open the overlay?
        else if( overlayUrl ) this.overlaySwitcher.open( overlayUrl );
        
        // Empty href?
        else if( !href || !href.length ) return;
        
        // Content load
        else
        {
            var regexp = /^\/+|[\/#]+$/g,
                href = href.replace( /^(http:\/\/.*?\/)/, '' ).replace( regexp, '' );
            
            if( href.length )
            {
                // Open another page within an open overlay?
                if( this.overlaySwitcher.isOpen() ) this.overlaySwitcher.open( href );
                
                // Just initiate an History push. The History object will then fire
                // a "change" event, which is caught by _historyChangeListener to load
                // and switch the content.
                else if( History.getPath().replace( regexp, '' ) != href ) History.push( '/' + href );
            }
        }
    }
} );


/*!
 * Request.JSON ContentLoader Extension
 *
 * Version : 1.0
 * Author  : Felix Zandanel <fz (at) onwebworx (dot) net>
 * Company : zandanel webmedia
 * License : MIT-style license
 */

Request.JSON.UID = ( function()
{
    var nextUID = 1;

    return new Class( {
    
        Extends: Request.JSON,
    
        initialize: function( options )
        {
            this.parent( options );
            this._uid = 'request-' + nextUID++;
        },
        
        getUID: function()
        {
            return this._uid;
        },
        
        onFailure: function()
        {
            this.fireEvent( 'complete', this._uid ).fireEvent( 'failure', this._uid );
        },
        
        success: function( text )
        {
            var json = null;
            try { json = this.response.json = JSON.decode( text, this.options.secure ); }
            catch( error ) {}
            if( json == null ) this.onFailure();
            else this.onSuccess( this._uid, json );
        }
        
    } );
    
} )();


/*!
 * Spinner
 *
 * Version : 1.0
 * Author  : Felix Zandanel <fz (at) onwebworx (dot) net>
 * Company : zandanel webmedia
 * License : MIT-style license
 */

Spinner = new Class( {
    Implements: [Options, Events, Stack],
    
    Binds: [
        'show',
        'hide',
        
        '_fxCompleteListener'
    ],
    
    options: {
        modes: [],
        imgSrc: 'spinner.gif',
        containerId: 'spinner-wrapper',
        containerClass: '',
        fxDuration: 150,
        spinnerMinVisibleTime: 800
    },
    
    initialize: function( container, options )
    {
        this.setOptions( options );
        
        this.registerSyncMethods( [
            'releaseMode',
            
            'show',
            'hide',
            
            '_fxCompleteListener'
        ] );
    
        var container = this.container = new Element( 'div', { id: this.options.containerId, styles: { opacity: 0, display: 'none' }, 'class': this.options.containerClass } ).inject( container )
                .set( 'html', '<span><img src="' + this.options.imgSrc + '" /></span>' ),
            modes = Array.from( this.options.modes );
        
        this.fx = new Fx.Tween( container, {
            link     : 'cancel',
            fps      : 'animationFrame',
            duration : this.options.fxDuration
        } ).addEvent( 'complete', this._fxCompleteListener );
        
        var priorityNumbers = [];
        for( var i = 0, j = modes.length; i < j; i++ ) priorityNumbers.push( i );

        this.visible = false;
        this.modes = modes;
        this.priorities = priorityNumbers.associate( this.modes );
        this.modeVisibility = {};
        this.modeRetain = {};
        this.modeStack = [];
        
        this.showCompleteTime = this.hideTimer = this.fxMode = null;
        
        modes.each( function( mode ) { this.modeVisibility[mode] = this.modeRetain[mode] = false; }, this );
    },
    
    getMode: function()
    {
        return this.mode;
    },
    
    releaseMode: function( mode )
    {
        if( !this._checkMode( mode ) || !this.modeStack.contains( mode ) ) return;
        
        var i, j;
        
        this.modeRetain[mode] = false;
        
        for( i = 0, j = this.modeStack.length; i < j; i++ )
        {
            if( this.modeStack[i] == mode )
            {
                this.modeStack = this.modeStack.slice( 0, i ).append( this.modeStack.slice( i + 1 ) );
                return;
            }
        }
    },
    
    show: function( mode )
    {
        if( !this._checkMode( mode ) ) return;

        clearTimeout( this.hideTimer );

        this.modeVisibility[mode] = true;
        
        this._pushMode( mode );

        var doShow = true,
            newPriority = this.priorities[mode],
            highestMode = this.modeStack[this.modeStack.length - 1],
            highestPriority = this.priorities[highestMode];
        
        if( this.fxMode != null && this.fxMode != mode ) this._fireCompleted( this.fxMode );
        
        if( newPriority >= highestPriority && !this.visible )
        {
            this.showCompleteTime = null;
            this.visible = true;
            this.container.setStyle( 'display', 'block' );
            this.fxMode = mode;
            this.fx.start( 'opacity', 1 );
        }
        else this._fireCompleted( mode );
    },
    
    hide: function( mode, retain, immediate )
    {
        if( !this._checkMode( mode ) || !this.modeStack.contains( mode ) ) return;

        this.modeVisibility[mode] = false;
        
        var fireCompletedMode = null,
            highestMode = this.modeStack[this.modeStack.length - 1];

        if( this.priorities[mode] < this.priorities[highestMode] || ( mode == highestMode && !this.visible && !this.fx.isRunning() ) ) fireCompletedMode = mode;
        else if( mode != highestMode ) fireCompletedMode = highestMode;
        
        if( fireCompletedMode != null ) this._fireCompleted( fireCompletedMode );
        else
        {
            this.fxMode = mode;
            
            if( !immediate && this.showCompleteTime )
            {
                var delay = this.showCompleteTime + this.options.spinnerMinVisibleTime - Date.now();
    
                if( delay > 0 )
                {
                    this.hideTimer = this.hide.delay( delay, this, [mode, retain] );
                    return;
                }
            }

            this.visible = false;
            this.modeRetain[mode] = !!retain;
            this.fx.start( 'opacity', 0 );
        }
    },
    
    
    /* Private methods */
    
    _pushMode: function( mode )
    {
        if( !this._checkMode( mode ) || this.modeStack.contains( mode ) ) return;

        if( this.modeStack.length )
        {
            var newPriority = this.priorities[mode],
                insertBefore = null,
                i, j;
            
            for( i = 0, j = this.modeStack.length; i < j; i++ )
            {
                if( this.priorities[this.modeStack[i]] > newPriority )
                {
                    var temp = this.modeStack.slice( 0, i );
                    temp.push( mode );
                    this.modeStack = temp.append( this.modeStack.slice( i ) );
                    return;
                }
            }
        }
        
        this.modeStack.push( mode );
    },
    
    _checkMode: function( mode )
    {
        return this.modes.contains( mode );
    },
    
    _fireCompleted: function( mode, firedByFx )
    {
        if( this.visible && firedByFx ) this.showCompleteTime = Date.now();
        
        var action = this.modeVisibility[mode] ? 'show' : 'hide';
        this.fireEvent.delay( 1, this, action + '-' + mode + '-complete' );

        if( this.fxMode == mode ) this.fxMode = null;
        if( action == 'hide' && !this.modeRetain[mode] ) this.releaseMode( mode );
    },

    _fxCompleteListener: function()
    {
        var mode = this.modeStack[this.modeStack.length - 1];
        
        this._fireCompleted( mode, true );
        
        if( !this.visible ) this.container.setStyle( 'display', 'none' );
    }
} );


/*!
 * Box Scroller
 *
 * Version : 1.3
 * Author  : Felix Zandanel <fz (at) onwebworx (dot) net>
 * Company : zandanel webmedia
 * License : MIT-style license
 */

BoxScroller = new Class( {
    Implements: [Events],

    Binds: [
        'enable',
        'disable',
        'update',
    
        '_scrollListener',
        '_dragStartListener',
        '_dragMoveListener',
        '_dragEndListener'
    ],
    
    initialize: function( scrollContainerWrapper, positioningCallback )
    {
        this.factor = 1;
        this.enabled = this.attached = this.locked = this.inDrag = false;
        
        var container = this.container = new Element( 'div', { 'class': 'scroll-handle-wrapper', styles: { display: 'none' } } ).inject( scrollContainerWrapper ),
            handle = this.handle = new Element( 'span', { 'class': 'scroll-handle' } )
            .set( 'html', '<span class="edge-top"></span><span class="fill"></span><span class="edge-bottom"></span>' )
            .inject( container ).addEvent( 'mousedown', this._dragStartListener );
        
        this.positioningCallback = typeOf( positioningCallback ) == 'function' ? positioningCallback : ( function()
        {
            return {
                x: this.scrollTarget.getSize().x + 10,
                y: 0
            };
        } ).bind( this );
    },
    
    getScrollTarget: function()
    {
        return this.scrollTarget;
    },
    
    attach: function( scrollTarget )
    {
        if( this.attached || !scrollTarget ) return;
        
        this.detach();
        
        this.scrollTarget = scrollTarget;
        
        this.dragValues = {
            start: 0,
            cur:   0
        };
        
        this.attached = true;
        this.enabled = false;
        
        this.update();        
        
        this._doScroll( 0, 0 );
        
        window.addEvent( 'scroll', this._scrollListener );
        
        this.handle.addEvent( 'mousedown', this._dragStartListener );

        return this;
    },
    
    detach: function()
    {
        this.attached = this.enabled = false;
        this.scrollTarget = null;
        
        window.removeEvent( 'scroll', this._scrollListener );
        
        this.handle.removeEvent( 'mousedown', this._dragStartListener );
        
        this._showContainer( false );
        
        return this;
    },
    
    lock: function( state )
    {
        this.locked = state;
        
        return this;
    },
    
    enable: function()
    {
        if( this.attached && !this.locked )
        {
            this.enabled = true;
            this.update();
        }
        
        return this;
    }, 
    
    disable: function()
    {
        this.enabled = false;
        this._showContainer( false );
        
        return this;
    },
    
    isAttached: function()
    {
        return this.attached;
    },
    
    isEnabled: function()
    {
        return this.attached && this.enabled;
    },
    
    isLocked: function()
    {
        return this.locked;
    },
    
    update: function()
    {
        if( !this.isEnabled() ) return;
        
        var target              = this.scrollTarget,
            container           = this.container,
            handle              = this.handle,
            targetSizeY         = target.getSize().y,
            targetScrollSizeY   = target.getScrollSize().y,
            factor              = ( targetSizeY + 8 ) / targetScrollSizeY,
            handleY             = Math.round( targetSizeY * factor );
        
        this.factor             = factor;
        this.maxHandleTop       = Math.round( targetScrollSizeY * factor ) - handleY;
        this.maxScroll          = targetScrollSizeY - targetSizeY;
        
        if( targetSizeY >= targetScrollSizeY || handleY < 16 ) this._showContainer( false );
        else
        {
            var containerPositioning = this.positioningCallback();
            
            handle.setStyle( 'height', handleY );
            container.setStyles( {
                top     : containerPositioning.y - 4,
                left    : containerPositioning.x,
                height  : targetSizeY + 8
            } );
            
            this._showContainer( true );
            
            this._scrollListener();
        }
        
        return this;
    },
    
    
    /* Private methods */
    
    _showContainer: function( visibility )
    {
        if( !this.isEnabled() ) visibility = false;        
        
        this.container.setStyle( 'display', visibility ? 'block' : 'none' );
        
        this.fireEvent( 'scrollbar-' + ( visibility ? 'visible' : 'hidden' ) );
    },

    _scrollListener: function()
    {
        if( !this.isEnabled() ) return;
        
        if( !this.inDrag )
        {
            var scrollY = window.getScroll().y;

            this._doScroll( scrollY, Math.round( scrollY * this.factor ).limit( 0, this.maxHandleTop ) );
        }
    },
    
    _dragStartListener: function( e )
    {
        e.stop();
    
        if( this.inDrag ) return;

        this.fireEvent( 'before-drag-start' );

        window.addEvents( {
            mousemove : this._dragMoveListener,
            mouseup   : this._dragEndListener
        } );

        this.dragInit = {
            mouseY    : e.page.y,
            handleTop : this.handle.getStyle( 'top' ).toInt()
        };
        
        this.inDrag = true;
    },
    
    _dragMoveListener: function( e )
    {
        if( !this.inDrag || !this.isEnabled() ) return;
        
        e.stop();
        
        var handleTop = ( this.dragInit.handleTop + e.page.y - this.dragInit.mouseY ).limit( 0, this.maxHandleTop ),
            scrollY = ( handleTop * ( 1 / this.factor ) ).limit( 0, this.maxScroll );

        this._doScroll( scrollY, handleTop );
    },
    
    _dragEndListener: function( e )
    {
        e.stop();
    
        if( this.inDrag )
        {
            window.removeEvents( {
                mousemove : this._dragMoveListener,
                mouseup   : this._dragEndListener
            } );
    
            window.scrollTo( 0, this.scrollTarget.getScroll().y );
        }
        
        this.fireEvent( 'drag-end' );
        
        this.inDrag = false;
    },
    
    _doScroll: function( scrollY, handleTop )
    {
        this.handle.setStyle( 'top', handleTop );
        this.scrollTarget.scrollTo( 0, scrollY );
    }
} );


/*!
 * Image Preloader
 * Working replacement for Asset.images from Mootools More (which is over-optimized)
 *
 * Version : 1.2
 * Author  : Felix Zandanel <fz (at) onwebworx (dot) net>
 * Company : zandanel webmedia
 * License : MIT-style license
 */

ImagePreloader = new Class( {
    Implements: [Events, Stack],
    
    Binds: ['_imageListener'],
    
    initialize: function()
    {
        this.loadCounter = 0;
        this.loadedImages = [];
        this.imgContainer = null;
        
        this._imageListenerLoad = ( function( img ) { this._imageListener.pass( ['load', img] ).delay( 1 ) } ).bind( this );
        this._imageListenerAbort = ( function( img ) { this._imageListener.pass( ['abort', img] ).delay( 1 ) } ).bind( this );
        
        this.registerSyncMethods( [
            'preload',
            
            '_imageListener'
        ] );
    },
    
    preload: function( imgs, baseSrc, noValidate )
    {
        imgs = Array.from( imgs ).flatten();
        this.loadedImages = [];
        
        var base = baseSrc,
            img, src,
            queue = [],
            container = this._getContainer();
            
        imgs.each( function( input )
        {
            if( typeOf( input ) == 'element' && input.get( 'tag' ) == 'img' ) src = input.get( 'src' );
            else if( typeOf( input ).toLowerCase() == 'string' ) src = ( base ? base : '' ) + input;
            if( !src || !src.length ) return;
            
            this.loadCounter++;
            
            img = new Element( 'img' ).store( 'img-src', src ).inject( container );
            img.onload = this._imageListenerLoad.pass( img );
            img.onabort = img.onerror = this._imageListenerAbort.pass( img );
            img.validate = noValidate ? 'never' : 'always';

            queue.push( img );
        }, this );
        
        queue.each( function( img ) { img.set( 'src', img.retrieve( 'img-src' ) ); } );
        
        return this;
    },
    
    
    /** Private methods **/
    
    _imageListener: function( evt, img )
    {
        if( evt == 'load' ) this.loadedImages.push( img );
        if( !--this.loadCounter )
        {
            this.fireEvent( 'load', [this.loadedImages] );
            if( this.imgContainer )
            {
                this.imgContainer.destroy();
                this.imgContainer = null;
            }
        }
    },
    
    _getContainer: function()
    {
        if( !this.imgContainer ) this.imgContainer = new Element( 'div', { styles: { display: 'none' } } ).inject( $( document.body ) );
        return this.imgContainer;
    }
} );


/*!
 * Countdown Manager
 *
 * Version : 1.1
 * Author  : Felix Zandanel <fz (at) onwebworx (dot) net>
 * Company : zandanel webmedia
 * License : MIT-style license
 */

CountdownManager = new Class( {
    Implements: [Events],
    
    Binds: [
        'reset',
        'lock',
        'increase',
        'decrease'
    ],
    
    initialize: function( stages, reFire, initLock )
    {
        this.currentStage = stages || 0;
        this.locked = false;
        this.reFire = !!reFire || true;
        this.locked = !!initLock;
    },

    reset: function( stages )
    {
        this.currentStage = Math.max( 0, stages );
        
        return this;
    },
    
    lock: function( state )
    {
        this.locked = state;
        
        return this;
    },
    
    increase: function( stages )
    {
        if( this.locked ) return false;
        
        this.currentStage += Math.max( 1, stages );
        
        return true;
    },
    
    decrease: function()
    {
        if( this.locked ) return false;
        
        if( this.currentStage || this.reFire )
        {
            this.currentStage = Math.max( 0, this.currentStage - 1 );
            if( !this.currentStage ) this.fireEvent( 'trigger' );
        }
        
        return true;
    }
} );


/*!
 * Logo Offset Slider
 *
 * Version : 1.1
 * Author  : Felix Zandanel <fz (at) onwebworx (dot) net>
 * Company : zandanel webmedia
 * License : MIT-style license
 */

OffsetSlider = new Class( {
    Implements: [Options, Events, Stack],
    
    options: {
        totalWidth: 100,
        offsetTop: 0,
        singleHeight: 50,
        singleWidths: {},
        
        initialNum: 0,
        tag: 'span',
        duration: 500,
        transition: null,
        centerContainer: true,
        animateYAxis: true,
        
        imgSrc: null
    },
    
    initialize: function( outerContainer, options )
    {
        this.setOptions( options );
        
        this.registerSyncMethods( [
            'switchTo',
            '_slide',
            '_fxComplete'
        ] );
        
        this.currentNum = -1;
        this.nextNum = this.options.initialNum;
        this.nextNoFx = true;
        this.ready = false;
        
        var outerContainer = $( outerContainer ).setStyle( 'visibility', 'hidden' ),
            innerContainer = new Element( this.options.tag, { 'class': 'slide-container' } ).inject( outerContainer, 'top' );
        this.activeSlide = new Element( this.options.tag, { 'class': 'slide-active' } ).inject( innerContainer, 'top' );
        this.nextSlide = new Element( this.options.tag, { 'class': 'slide-next', styles: { opacity: 0 } } ).inject( innerContainer, 'top' );

        this.fx = new Fx.Elements( [this.activeSlide, this.nextSlide, outerContainer], {
            fps        : 'animationFrame',
            duration   : this.options.duration,
            transition : this.options.transition
        } ).addEvent( 'complete', this._fxComplete.pass( false, this ) );

        this.cSizeX = innerContainer.getSize().x;

        new ImagePreloader().addEvent( 'load', ( function( imgs )
        {
            var img = imgs[0].inject( this.activeSlide ),
                img2 = img.clone().inject( this.nextSlide );
            
            this.ready = true;
            this._slide();
            outerContainer.setStyle( 'visibility', 'visible' );
            this.fireEvent( 'ready' );
        } ).bind( this ) ).preload( this.options.imgSrc );
    },
    
    switchTo: function( num, noFx )
    {
        if( num < 0 || num == this.currentNum ) return this;

        this.nextNum = num;
        this.nextNoFx = noFx;
        this._slide();
        
        return this;
    },
    
    
    /** Private methods **/
    
    _slide: function()
    {
        if( !this.ready || this.inSlide ) return;
    
        this.inSlide = true;
        this.currentNum = this.nextNum;
        
        var noFx = this.nextNoFx,
            nextSizeX = this.options.singleWidths[this.nextNum] || this.options.totalWidth,
            nextSizeY = this.options.offsetTop + this.nextNum * this.options.singleHeight,
            nextOffsetX = this.options.totalWidth - nextSizeX,
            fxArgs = noFx ? {
                0: {
                    left: this.options.totalWidth,
                    top: -nextSizeY,
                    opacity: 1
                },
                1: { opacity: 0 }
            } : {
                0: {
                    left: nextOffsetX,
                    opacity: 0
                },
                1: {
                    left: [this.cSizeX - nextOffsetX, this.options.totalWidth],
                    top: this.options.animateYAxis ? -nextSizeY : [-nextSizeY, -nextSizeY],
                    opacity: 1
                }
            };
        
        if( this.options.centerContainer ) fxArgs = Object.merge( fxArgs, { 2: { left: ( nextSizeX - this.options.totalWidth ) / 2 } } );
        
        this.fx[noFx ? 'set' : 'start']( fxArgs );
        if( noFx ) this._fxComplete( true );
    },
    
    _fxComplete: function( noSwitch )
    {
        if( !noSwitch )
        {
            var fxArgs = {},
                i = 0;
            [this.nextSlide, this.activeSlide].each( function( item )
            {
                fxArgs[i] = {};
                ['top', 'left', 'opacity'].each( function( prop ) { fxArgs[i][prop] = item.getStyle( prop ); } );
                i++;
            } );
            this.fx.set( fxArgs );
        }
    
        this.fireEvent( 'after-switch' );
    
        this.inSlide = false;
        if( this.currentNum != this.nextNum ) this._slide();
    }
} );


/*!
 * Content Loader
 *
 * Version : 1.3
 * Author  : Felix Zandanel <fz (at) onwebworx (dot) net>
 * Company : zandanel webmedia
 * License : MIT-style license
 */

( function()
{
    var cache = {},
        cacheSize = 30,
        nextUID = 1,
        cacheGC = function()
        {
            if( Object.getLength( cache ) > cacheSize )
            {
                var oldestKey,
                    oldestKeyTime = Date.now();
                Object.each( cache, function( data, key )
                {
                    if( data.time < oldestKeyTime && !data.lock )
                    {
                        oldestKey = key;
                        oldestKeyTime = data.time;
                    }
                } );
                if( cache[oldestKey] ) delete cache[oldestKey];
            }
        };

    this.ContentLoader = new Class( {
        Implements: [Events, Stack],
        
        Binds: [
            '_loadSuccessListener',
            '_loadAbortListener'
        ],
        
        initialize: function()
        {
            var that = this;

            that.registerSyncMethods( [
                'load',
                
                '_loadSuccessListener',
                '_loadAbortListener'
            ] );
        
            that.urlUIDMap = {};
            that.requestMap = {};
        },
        
        setCacheSize: function( size )
        {
            cacheSize = Math.max( 1, size );
        },
        
        load: function( url, doReload, sendData, lock )
        {
            var that = this,
                cacheData, request, uid,
                isPost = typeOf( sendData ) == 'string' && sendData != '';
            
            url = that._normalizeUrl( url );
            uid = that.urlUIDMap[url] || null;
            cacheData = !doReload && uid != null ? that._doRetrieve( url ) : null;
            doReload = doReload || ( sendData ? true : false );
            
            if( cacheData )
            {
                if( lock ) that.lock( url );
                doReload = true;
                that._loadEvent.delay( 1, that, ['cache', uid, cacheData, url] );
            }
            else if( uid == null )
            {
                isPost = typeOf( sendData ) == 'string' && sendData != '';
            
                request = new Request.JSON.UID( {
                    method    : isPost ? 'post' : 'get',
                    url       : url,
                    noCache   : isPost ? true : false,
                    data      : isPost ? sendData : ''
                } ).addEvents( {
                    success : that._loadSuccessListener,
                    failure : that._loadAbortListener,
                    cancel  : that._loadAbortListener
                } ).send();
            
                uid = request.getUID();
                
                that.urlUIDMap[url] = uid;
                
                that.requestMap[uid] = {
                    url     : url,
                    uid     : uid,
                    noStore : doReload,
                    lock    : !!lock
                };
            }

            return uid;
        },
        
        lock: function( url )
        {
            if( cache[url] ) cache[url].lock = true;
            
            return this;
        },
        
        unlock: function( url )
        {
            if( cache[url] ) cache[url].lock = false;
            
            return this;
        },
        
        store: function( url, data, lock )
        {
            var that = this;
                uid = that.urlUIDMap[url] = that._doStore( that._normalizeUrl( url ), data, { lock: !!lock } );
            
            return this;
        },
        
        retrieve: function( url )
        {
            url = this._normalizeUrl( url );
            
            return this._doRetrieve( url );
        },
        
        
        /** Private methods **/
        
        _doStore: function( url, data, meta )
        {
            cacheGC();
            
            meta = meta || {};
            
            cache[url] = {
                time  : Date.now(),
                url   : url,
                data  : data,
                lock  : !!meta.lock,
                uid   : meta.uid || 'stored-' + nextUID++
            };
            
            return cache[url].uid;
        },
        
        _doRetrieve: function( url, raw )
        {
            return cache[url] ? ( raw ? cache[url] : cache[url].data ) : null;
        },
        
        _normalizeUrl: function( url )
        {
            return '/' + url.replace( /^\/*|\/*$/g, '' ).replace( /\/+/g, '/' ).toLowerCase();
        },
        
        _loadEvent: function( evt, uid, data, url )
        {
            var that = this;
            
            if( evt != 'cache' && !that.requestMap[uid] ) return;
            
            switch( evt )
            {
                case 'success':
                    meta = that.requestMap[uid];
                    url = meta.url;
                    if( !meta.noStore ) that._doStore( url, data, meta );
                    // no break
        
                case 'cache':
                    that.fireEvent( 'complete', [uid, url, data] );
                    break;
                
                case 'abort':
                    delete that.urlUIDMap[url];
                    this.fireEvent( 'abort', uid );
            }
            
            delete this.requestMap[uid];
        },
        
        _loadSuccessListener: function( uid, data )
        {    
            this._loadEvent( 'success', uid, data );
        },
        
        _loadAbortListener: function( uid )
        {
            this._loadEvent( 'abort', uid );
        }
    } );
} )();


/*!
 * Content Switcher Base Class
 *
 * Version : 1.7
 * Author  : Felix Zandanel <fz (at) onwebworx (dot) net>
 * Company : zandanel webmedia
 * License : MIT-style license
 */

( function()
{
    var defaultCheckCallback = function( value ) { return value; };

    this.ContentSwitcher = new Class( {
    
        Implements: [Options, Events, Stack],
        
        Binds: [
            'goTo',
            'preparationComplete',
            'phaseComplete',
            
            '_dataReadyListener',
            '_loadCompleteListener',
            '_loadAbortListener'
        ],
        
        options: {
            initPhase  : 2,
            phaseCount : 2
        },
        
        vars: {
            callbacks        : {},
            defaultCallbacks : {},
        
            preparationTasks           : [],
            preparationTasksAsyncCount : 0,
            
            currentPhase        : null,
            currentPhaseRunning : false,
            
            data        : null,
            dataUID     : null,
            dataUrl     : null,
            dataElement : null,
            
            dataArrived  : false,
            dataPrepared : false,
            dataReady    : false,
            
            idle      : true,
            fxEnabled : true
        },
        
        initialize: function( contentLoader, callbacks, options )
        {
            this.setOptions( options );
        
            var that = this,
                vars = that.vars,
                opts = that.options,
                callbackError,
                phaseCount,
                preparationCountdown;
            
            phaseCount = opts.phaseCount;
            if( phaseCount < 2 || phaseCount % 2 != 0 ) throw new Error( 'ContentSwitcher: Option "phaseCount" must be at least 2 and must be an even number.' );
            if( opts.initPhase > opts.phaseCount ) throw new Error( 'ContentSwitcher: Option "initPhase" is greater than option "phaseCount".' );
        
            opts.waitForDataPhase = phaseCount / 2;
        
            callbacks = Object.merge( {
                checkUrl  : defaultCheckCallback,
                checkData : defaultCheckCallback 
            }, callbacks );

            callbackError = ['prepareData'].append( ( function()
            {
                var temp = [],
                    i, j;
                
                for( i = 1, j = opts.phaseCount; i <= j; i++ ) { temp.push( 'onPhase' + i ); }
                
                return temp;
            } )() ).some( function( value ) { if( typeOf( callbacks[value] ) != 'function' ) return true; } );
            
            if( callbackError ) throw new Error( 'ContentSwitcher: Please provide all "onPhaseX" methods and the "prepareData" method as callbacks.' );
        
            vars.defaultCallbacks = callbacks;

            that.preparationCountdown = preparationCountdown = new CountdownManager( 0, false, true ).addEvent( 'trigger', that._dataReadyListener );
            that.preparationComplete = preparationCountdown.decrease;
                        
            that.registerSyncMethods( [
                'goTo',
                'phaseComplete',
                'preparationComplete',
                
                '_startPhase',
                '_prepareData',
                
                '_dataReadyListener',
                '_loadCompleteListener',
                '_loadAbortListener'
            ] );
        
            that.contentLoader = contentLoader.addEvents( {
                complete : that._loadCompleteListener,
                abort    : that._loadAbortListener
            } );
            
            vars.currentPhase = opts.initPhase;
        },
        
        goTo: function( url, doReload, sendData, overloadCallbacks )
        {
            var that = this,
                opts = that.options,
                vars = that.vars,
                defaultCallbacks = vars.defaultCallbacks,
                checkUrlCallback = overloadCallbacks && overloadCallbacks.checkUrl ? overloadCallbacks.checkUrl : defaultCallbacks.checkUrl,
                uid, phase,
                currentPhase = vars.currentPhase;
                
            url = checkUrlCallback( url );

            if( !url || ( vars.dataUrl == url && !doReload ) ) return;

            that.clearStack();
            that.preparationCountdown.lock( true );
            
            Object.merge( vars, {
                callbacks: Object.merge( {}, vars.defaultCallbacks, overloadCallbacks || {} ),
                
                data         : null,
                dataUID      : that.contentLoader.load( url, doReload, sendData ),
                dataUrl      : url,
                dataElement  : null,
                
                dataArrived  : false,
                dataPrepared : false,
                dataReady    : false,
                
                idle : false
            } );
            
            that.fireEvent( 'goto', [url, uid] );
            phase = currentPhase > opts.waitForDataPhase ? opts.phaseCount - ( currentPhase - 1 ) : null;
            if( phase != null ) that._startPhase( phase );
            
            return vars.uid;
        },
        
        registerPreparationTasks: function( fns, async )
        {
            var that = this;
        
            if( that.vars.currentPhase == that.options.waitForDataPhase ) return false;
            
            Array.from( fns ).each( function( fn ) { that.registerPreparationTask( fn, async ) }, that );
            
            return true;
        },
        
        registerPreparationTask: function( fn, async )
        {
            var that = this,
                vars = that.vars;
        
            if( vars.currentPhase == that.options.waitForDataPhase ) return false;
            
            vars.preparationTasks.push( fn );
            if( async ) vars.preparationTasksAsyncCount++
            
            return true;
        },
        
        enableFx: function()
        {
            this.vars.fxEnabled = true;
            return this;
        },
        
        disableFx: function()
        {
            this.vars.fxEnabled = false;
            return this;
        },
        
        phaseComplete: function( phase )
        {
            var that = this,
                vars = that.vars,
                opts = that.options,
                waitForDataPhase = opts.waitForDataPhase,
                phaseCount = opts.phaseCount,
                nextPhase = null;
            
            if( vars.currentPhase != phase ) return;

            vars.currentPhaseRunning = false;
            
            that.fireEvent( 'phase' + phase + '-complete' );
            
            if( phase == waitForDataPhase ) that._prepareData();
            
            if( ( phase == waitForDataPhase && vars.dataReady ) || ( phase != waitForDataPhase && phase != phaseCount ) ) nextPhase = phase + 1;
            else if( phase == phaseCount ) this.idle = true;
            
            if( nextPhase !== null ) that._startPhase( nextPhase );
        },
        
        
        /* Private methods */   
        
        _startPhase: function( phase )
        {
            var that = this,
                vars = that.vars,
                value;
        
            if( vars.currentPhase == phase ) return;
            
            vars.currentPhase = phase;
            vars.currentPhaseRunning = true;
            
            that.fireEvent( 'phase' + phase + '-start' );
            
            value = vars.callbacks['onPhase' + phase]( !vars.fxEnabled );
            if( value ) that.phaseComplete( phase );
        },
        
        _reset: function()
        {
            var that = this,
                vars = that.vars,
                opts = that.options;
        
            this.clearStack();
            
            Object.merge( vars, {
                currentPhase        : opts.initPhase,
                currentPhaseRunning : false,
            
                data         : null,
                dataUID      : null,
                dataUrl      : null,
                dataElement  : null,
                
                dataArrived  : false,
                dataPrepared : false,
                dataReady    : false,
                
                idle : true
            } );
        },
        
        _prepareData: function()
        {
            var that = this,
                vars = that.vars,
                opts = that.options,
                data = vars.data,
                element;
        
            if( vars.idle || !vars.data || vars.currentPhase != opts.waitForDataPhase || vars.currentPhaseRunning || !vars.dataArrived || vars.dataPrepared ) return;
            
            vars.dataPrepared = true;

            element = vars.dataElement = vars.callbacks.prepareData( data );
            
            that.fireEvent( 'data-prepare', [element, data] );
            
            if( !vars.preparationTasks.length ) that._dataReadyListener();
            else
            {
                if( vars.preparationTasksAsyncCount ) that.preparationCountdown.reset( vars.preparationTasksAsyncCount ).lock( false );
                vars.preparationTasks.each( function( fn ) { fn.call( null, element, data ); }, that );
            }
        },
        
        _dataReadyListener: function()
        {
            var that = this,
                vars = that.vars;
            
            that.preparationCountdown.lock( true );
            
            if( vars.idle ) return;
            
            vars.dataReady = true;
            that._startPhase( vars.currentPhase + 1 );
        },
        
        _loadCompleteListener: function( uid, url, data )
        {
            var that = this,
                vars = that.vars;
        
            if( vars.dataUID != uid ) return;

            data = vars.callbacks.checkData( data, uid, url );
            if( data )
            {
                vars.dataArrived = true;
                vars.data = data;
                
                that.fireEvent( 'data-arrived', [uid, url, data] );
                
                that._prepareData();
            }
        },
        
        _loadAbortListener: function( uid )
        {
            var that = this,
                vars = that.vars;
        
            if( vars.dataUID == uid )
            {
                vars.idle = true;
                that.fireEvent( 'data-failure', vars.dataUrl );
            }
        }
        
    } );
} )();


/*!
 * Main Content Switcher
 *
 * Version : 1.6
 * Author  : Felix Zandanel <fz (at) onwebworx (dot) net>
 * Company : zandanel webmedia
 * License : MIT-style license
 */

MainContentSwitcher = new Class( {
    Extends: ContentSwitcher,
    
    Binds: [
        '_inject',
        
        '_slideCompleteListener'
    ],
    
    options: {
        slideFxDuration  : 300,
        contentClass     : 'content',
        spinnerMode      : 'content',
        urlCheckCallback : null,
        slideOffsetLeft  : 0
    },
    
    initialize: function( container, contentLoader, spinner, options )
    {
        this.setOptions( Object.merge( options || {}, {
            initPhase        : 4,
            phaseCount       : 4,
            waitForDataPhase : 2
        } ) );
        
        container = this.container = $( container );
        this.containerSizeX = container.getSize().x;
        
        this.slideElement = container.getFirst( 'div.' + this.options.contentClass ).getParent();
        
        this.slideState = 'out';
        this.slideFx = new Fx.Tween( container, {
            link: 'cancel',
            duration: this.options.slideFxDuration
        } ).addEvent( 'complete', this._slideCompleteListener );
    
        var spinnerMode = this.options.spinnerMode,
            spinnerEvents = {};
        
        spinnerEvents['show-' + spinnerMode + '-complete' ] = this.phaseComplete.pass( 2 );
        spinnerEvents['hide-' + spinnerMode + '-complete' ] = this.phaseComplete.pass( 3 );
        this.spinner = spinner.addEvents( spinnerEvents );
        
        var callbacks = {
            prepareData: this._inject,
            
            // Content slide out
            onPhase1 : ( function( noFx ) { this._slide( 'out', noFx ); } ).bind( this ),
            
            // Spinner fade in
            onPhase2 : ( function( noFx ) { this._fadeSpinner( true, noFx ); } ).bind( this ),
            
            // Spinner fade out
            onPhase3 : ( function( noFx ) { this._fadeSpinner( false, noFx ); } ).bind( this ),
            
            // Content slide in
            onPhase4 : ( function( noFx ) { this._slide( 'in', noFx ); } ).bind( this )
        };
        
        if( this.options.urlCheckCallback != null ) callbacks.checkUrl = this.options.urlCheckCallback;
        
        this.parent( contentLoader, callbacks );
    },
    
    
    /* Private methods */
    
    _inject: function( data )
    {
        return new Element( 'div', { 'class': this.options.contentClass } ).inject( this.container.empty() ).set( 'html', data.c );
    },
    
    _fadeSpinner: function( visibility, noFx )
    {
        if( noFx ) this.phaseComplete.delay( 1, this, visibility ? 2 : 3 );
        else this.spinner[visibility ? 'show' : 'hide']( this.options.spinnerMode );
    },
    
    _slide: function( direction, noFx )
    {
        this.slideState = direction;
    
        var fx = this.slideFx,
            fxArgs,
            cSizeX = this.containerSizeX,
            slideOffsetLeft = this.options.slideOffsetLeft;
        
        if( noFx )
        {
            fx.set( 'left', direction == 'in' ? slideOffsetLeft : -cSizeX );
            this._slideCompleteListener.delay( 1 );
        }
        else
        {
            var contentX = 0;
            this.slideElement.getChildren().each( function( element ) { contentX = Math.max( contentX, element.getSize().x ); } );
        
            if( direction == 'out' ) fxArgs = ['left', -contentX];
            else fxArgs = ['left', -contentX, slideOffsetLeft];
            
            fx.start.delay( 1, fx, fxArgs );
        }
    },
    
    _slideCompleteListener: function()
    {
        if( this.slideState == 'out' ) this.container.setStyle( 'left', -this.containerSizeX );
        
        this.phaseComplete( this.slideState == 'out' ? 1 : 4 );
    }
    
} );


/*!
 * Overlay Content Switcher
 *
 * Version : 1.3
 * Author  : Felix Zandanel <fz (at) onwebworx (dot) net>
 * Company : zandanel webmedia
 * License : MIT-style license
 */

OverlayContentSwitcher = new Class( {
    Extends: ContentSwitcher,
    
    Binds: [
        'open',
        'close',
        'initFadeOutMask',
    
        '_inject',
        '_overlayFade',
        
        '_fadeInitCompleteListener',
        '_closeCompleteListener',
        '_windowEscapeListener',
        '_maskFadeCompleteListener',
        '_containerFadeCompleteListener'
    ],
    
    options: {
        maskOpacity: 0.85,
        maskFxDuration: 300,
        containerFxDuration: 300,
        contentClass: 'overlay-content',
        spinnerMode: 'overlay',
        urlCheckCallback: null
    },
    
    initialize: function( container, mask, contentLoader, spinner, options )
    {
        this.setOptions( Object.merge( options || {}, {
            initPhase        : 4,
            phaseCount       : 4,
            waitForDataPhase : 2
        } ) );
        
        var container = this.container = $( container ).setStyles( {
                'opacity': 0,
                'display': 'none'
            } ),
            mask = this.mask = $( mask ).setStyle( 'opacity', 1 );
        
        this.closed = true;
        this.closeLock = false;
        
        this.maskClickApplied = this.maskReady = false;
        this.maskState = 'visible';
        this.maskFx = new Fx.Tween( mask, {
            link: 'cancel',
            duration: this.options.maskFxDuration
        } ).addEvent( 'complete', this._maskFadeCompleteListener );
        
        this.containerState = 'hidden';
        this.containerFx = new Fx.Tween( container, {
            link: 'cancel',
            duration: this.options.containerFxDuration
        } ).addEvent( 'complete', this._containerFadeCompleteListener );
        
        this.spinner = spinner;
    
        var spinnerMode = this.options.spinnerMode,
            spinnerEvents = {};
        
        spinnerEvents['show-' + spinnerMode + '-complete' ] = this.phaseComplete.pass( 2 );
        spinnerEvents['hide-' + spinnerMode + '-complete' ] = this.phaseComplete.pass( 3 );
        this.spinner = spinner.addEvents( spinnerEvents );
        
        var callbacks = {
            prepareData : this._inject,
            
            // Content fade out (if any)
            onPhase1 : this._overlayFade.pass( 'init' ),
            
            // Spinner fade in
            onPhase2 : spinner.show.pass( spinnerMode ),
            
            // Spinner fade out
            onPhase3 : spinner.hide.pass( [spinnerMode, true] ),
            
            // Content fade in
            onPhase4 : this._overlayFade.pass( 'container' )
        };
        
        if( this.options.urlCheckCallback != null ) callbacks.checkUrl = this.options.urlCheckCallback;
        
        this._goTo = this.goTo;
        delete this.goTo;
        
        this.fadeInitCountdown = new CountdownManager( 0, false, true ).addEvent( 'trigger', this._fadeInitCompleteListener );
        this.closeCompleteCountdown = new CountdownManager( 0, false, true ).addEvent( 'trigger', this._closeCompleteListener );
        
        this.registerSyncMethods( [
            'open',
            'close',
            
            '_fadeInitCompleteListener',
            '_closeCompleteListener',
            '_maskFadeCompleteListener',
            '_containerFadeCompleteListener'
        ] );
        
        this.parent( contentLoader, callbacks );
    },
    
    open: function( url, doReload, sendData )
    {
        if( !this.maskReady ) return false;
    
        this.closed = false;
        
        this._applyMaskClick( true );
        
        this._goTo( url, doReload, sendData );
        
        this.fireEvent( 'open', [url, this.nextDataUID] );

        return true;
    },
    
    close: function()
    {
        if( this.closed || this.closeLock ) return false;
        
        this.closed = true;
        
        this.fireEvent( 'close' );
        
        this._reset();
        
        this._applyMaskClick( false );
        
        this.closeCompleteCountdown.reset( 2 ).lock( false );
        
        this.spinner.hide( this.options.spinnerMode, false, true );
        
        this._fadeMask( false );
        this._fadeContainer( false );
        
        return true;
    },
    
    isOpen: function()
    {
        return !this.closed && !this.maskFx.isRunning();
    },
    
    isClosed: function()
    {
        return this.closed && !this.maskFx.isRunning();
    },
    
    setCloseLock: function( state )
    {
        this.closeLock = state;
        
        return this;
    },
    
    initFadeOutMask: function()
    {
        if( !this.maskReady ) this._fadeMask( false );
        
        return this;
    },
    

    /* Private methods */
    
    _inject: function( data )
    {
        this.container.setStyle( 'display', 'block' ).empty();
        
        var element = new Element( 'div', { 'class': this.options.contentClass } )
            .inject( this.container )
            .set( 'html', data.c )
            .grab( new Element( 'a', { 'class': 'overlay-close', href: '' } ).addEvent( 'click', this.close ) );
        
        return element;
    },
    
    _overlayFade: function( mode )
    {
        if( mode == 'init' )
        {
            this.fadeInitCountdown.reset( 2 ).lock( false );
            this._fadeMask( true );
            this._fadeContainer( false );
        }
        else this._fadeContainer( true );
    },
    
    _fadeMask: function( visibility )
    {
        if( ( this.maskState == 'visible' && visibility ) || ( this.maskState == 'hidden' && !visibility ) ) this._maskFadeCompleteListener();
        else if( this.maskState == 'visible' && !visibility )
        {
            this.fireEvent( 'before-mask-hidden' );
            this.maskState = 'hidden';
            this.maskFx.start( 'opacity', 0 );
        }
        else
        {
            this.fireEvent( 'before-mask-visible' );
            this.maskState = 'visible';
            this.maskFx.set( 'display', 'block' ).start( 'opacity', this.options.maskOpacity );
        }
    },
    
    _fadeContainer: function( visibility )
    {
        if( ( this.containerState == 'visible' && visibility ) || ( this.containerState == 'hidden' && !visibility ) ) this._containerFadeCompleteListener();
        else if( this.containerState == 'visible' && !visibility )
        {
            this.containerState = 'hidden';
            this.containerFx.start( 'opacity', 0 );
        }
        else if( this.containerState == 'hidden' && visibility )
        {
            this.containerState = 'visible';
            this.containerFx.set( 'display', 'block' ).start( 'opacity', 1 );
        }
    },
    
    _applyMaskClick: function( enable )
    {
        if( enable && this.maskClickApplied ) return;
        this.mask[enable ? 'addEvent' : 'removeEvent']( 'click', this.close );
        window[enable ? 'addEvent' : 'removeEvent']( 'keydown', this._windowEscapeListener );
        this.maskClickApplied = enable;
    },
    
    _fadeInitCompleteListener: function()
    {
        this.fadeInitCountdown.lock( true );
        this.phaseComplete( 1 );
    },
    
    _closeCompleteListener: function()
    {
        this.closeCompleteCountdown.lock( true );
        this.spinner.releaseMode( this.options.spinnerMode );
        this._reset();
        this.fireEvent( 'close-complete' );
    },
    
    _windowEscapeListener: function( e )
    {
        if( e.key && e.key == 'esc' ) this.close();
    },
    
    _maskFadeCompleteListener: function()
    {
        switch( this.maskState )
        {
            case 'hidden':
                this.mask.setStyle( 'display', 'none' );
                if( !this.maskReady ) this.maskReady = true;
                this.fireEvent( 'mask-hidden' );
                this.closeCompleteCountdown.decrease();
                break;
                
            case 'visible':
                this.fireEvent( 'mask-visible' );
                this.fadeInitCountdown.decrease();
                break;
        }
    },

    _containerFadeCompleteListener: function()
    {
        switch( this.containerState )
        {
            case 'hidden':
                this.container.setStyle( 'display', 'none' );
                if( this.closed ) this.closeCompleteCountdown.decrease();
                else this.fadeInitCountdown.decrease();
                break;
                
            case 'visible':
                this.phaseComplete( 4 );
                break;
        } 
    }
    
} );


/*!
 * SlideGallery
 * Fullscreen Gallery & Slideshow plugin for MooTools
 *
 * Version : 1.6
 * Author  : Felix Zandanel <fz (at) onwebworx (dot) net>
 * Company : zandanel webmedia
 * License : MIT-style license
 */

SlideGallery = new Class( {
    Implements: [Options, Events, Stack],
    
    Binds: [
        'setPictures',
        'slideTo',
        'next',
        'prev',
        'startSlideshow',
        'stopSlideshow',
    
        '_imageListener',
        '_slideCompleteListener',
        '_resizeListener'
    ],
    
    options: {
        containerElementId : 'gallery',
    
        minWidth         : 0,
        minHeight        : 0,
        horizontalCenter : true,
        verticalCenter   : true,

        fadeOnReady      : true,
        protect          : true,
        performance      : 'optimize',
        performanceDelay : 50,
        
        basePath : '',
        
        endFxDuration     : 250,
        readyFadeDuration : 500,
        slideshowInterval : 5000,
        
        remainOffsetPercent : 8
    },
    
    vars: {
        list      : null,
        container : null,
    
        curIndex : -1,
        length   : 0,
        
        loadCounter : 0,
        
        ready       : false,
        sliderReady : false,
        
        activeSlide   : null,
        foreignSlides : new Elements(),
        
        nextSlide     : null,
        nextSlideMode : null,
        nextSlideNoFx : false,
        
        inSlide     : false,
        inSlideshow : false
    },
    
    initialize: function( container, options )
    {
        var that = this.setOptions( options ),
            vars = that.vars,
            opts = that.options,
            galleryContainer;

        vars.mSize = {
            x : Math.max( 0, opts.minWidth ),
            y : Math.max( 0, opts.minHeight )
        };
        
        that.registerSyncMethods( [
            'setPictures',
            'slideTo',
            
            '_load',
            '_imageListener'
        ] );

        galleryContainer = vars.container = new Element( 'div', { id: opts.containerElementId, styles: { visibility: 'hidden' } } ).inject( $( container || document.body ) );
        vars.list = new Element( 'ul' ).inject( galleryContainer );
        
        ['resize', 'orientationchange'].each( function( evt ) { window.addListener( evt, this._resizeListener.pass([]) ); }, that );
    },
    
    setPictures: function( srcs )
    {
        var that = this,
            vars = that.vars,
            loadFirstSlide,
            num = 0,
            reorderSlides = {},
            checkSrc = ( function( slide )
            {
                var slideImgSrc = slide.retrieve( 'img-src' );
                
                return srcs.some( function( src )
                {
                    // Do not destroy this slide if it contains an image that is also included in the new picture set.
                    if( src == slideImgSrc )
                    {
                        reorderSlides[src] = slide;
                        return true;
                    }
                } );
            } ).bind( that );
            
        srcs = Array.from( srcs );
    
        if( !srcs.length ) return that;
    
        // All following invocations on this gallery must be made using the new picture set!
        that.stopSlideshow();
        that.clearStack();
        
        vars.sliderReady = false;
        
        vars.list.getChildren( 'li' ).each( function( slide )
        {
            // Do not destroy this slide if it contains an image that is also included in the new picture set.
            if( checkSrc( slide ) ) return;
            
            // Otherwise destroy the slide if it's not active (visible)
            else if( !slide.hasClass( 'slide-active' ) ) slide.destroy();
            
            // Mark the active (visible) slide to be destroyed after the launch of the new picture set
            else vars.foreignSlides.push( slide.addClass( 'slide-foreign' ).removeClass( 'slide-' + slide.retrieve( 'slide-num' ) ) );
        } );
        
        srcs.each( function( src )
        {
            var exists = !!reorderSlides[src],
                slide = exists
                    ? reorderSlides[src].removeClass( 'slide-' + reorderSlides[src].retrieve( 'slide-num' ) )
                    : new Element( 'li', { 'class': 'slide-pending', styles: { visibility: 'hidden' } } ).store( 'img-src', src );
            
            slide.inject( vars.list ).addClass( 'slide-' + num ).store( 'slide-num', num++ );
            
            if( vars.activeSlide == slide && num == 1 ) vars.omitNextNext = true;
            
            // Set the first slide to load
            if( !exists && !loadFirstSlide ) loadFirstSlide = slide;
        } );
        
        vars.curIndex = -1;
        vars.length = srcs.length;
        
        if( loadFirstSlide ) that._load( loadFirstSlide, true );
        else that._launch();
        
        return that;
    },
    
    slideTo: function( num, direction, noFx )
    {
        var that = this,
            vars = that.vars,
            slide;
    
        if( !vars.sliderReady || num < 0 || num >= vars.length ) return false;

        slide = vars.list.getChildren( 'li.slide-' + num )[0];
        
        if( direction == 'next' && vars.omitNextNext )
        {
            vars.omitNextNext = false;
            vars.curIndex = slide.retrieve( 'slide-num' );
            return that;
        }
        
        vars.nextSlide = slide;
        vars.nextSlideMode = ( direction && ['next', 'prev'].contains( direction ) ) ? direction : 'next';
        vars.nextSlideNoFx = !!noFx;
        
        if( slide.hasClass( 'slide-pending' ) ) that._load( slide, true );
        else if( !slide.hasClass( 'slide-loading' ) ) that._slide( slide );
        
        return that;
    },
    
    next: function( noFx, slideshowTrigger )
    {
        var that = this,
            vars = that.vars;
    
        if( !vars.sliderReady ) return this;
        
        if( vars.inSlideshow && !slideshowTrigger ) that.stopSlideshow();
        
        return that.slideTo( ( vars.curIndex == vars.length - 1 || vars.curIndex == -1 ) ? 0 : vars.curIndex + 1, 'next', noFx );
    },
    
    prev: function( noFx )
    {
        var that = this,
            vars = that.vars;
    
        if( !vars.sliderReady ) return this;
        
        that.stopSlideshow();
        
        return that.slideTo( ( vars.curIndex == 0 || vars.curIndex == -1 ) ? vars.length - 1 : vars.curIndex - 1, 'prev', noFx );
    },
    
    startSlideshow: function()
    {
        var that = this,
            vars = that.vars;
    
        if( !vars.inSlideshow )
        {
            vars.slideshowTimer = that.next.periodical( that.options.slideshowInterval, that, [false, true] );
            vars.inSlideshow = true;
        }
        return that;
    },
    
    stopSlideshow: function()
    {
        var that = this,
            vars = that.vars;
    
        clearInterval( vars.slideshowTimer );
        vars.inSlideshow = false;
        
        return that;
    },
    
    
    /** Private methods **/
    
    _launch: function( slide )
    {
        var that = this,
            vars = that.vars,
            opts = that.options;
    
        if( vars.ready && vars.sliderReady ) return;
    
        if( !vars.ready )
        {
            vars.ready = true;
            vars.activeSlide = slide.addClass( 'slide-active' ).setStyle( 'visibility', 'visible' );
            that._resizeListener();
            vars.curIndex = 0;
            that.fireEvent( 'ready' );
            
            if( opts.fadeOnReady && opts.readyFadeDuration > 0 ) ( new Fx.Tween( vars.container, {
                fps      : 'animationFrame',
                duration : opts.readyFadeDuration
            } ) ).start( 'opacity', 0, 1 );
            else vars.container.setStyle( 'visibility', 'visible' );
        }

        vars.sliderReady = true;
        that.fireEvent( 'picture-set-ready' );
    },
    
    _load: function( slide, immediate )
    {
        var that = this,
            vars = that.vars,
            img,
            alreadyLoading = false;
    
        if( !immediate && vars.loadCounter ) return;
        
        if( slide.hasClass( 'slide-loading' ) ) alreadyLoading = true;
        else slide.removeClass( 'slide-pending' ).addClass( 'slide-loading' );
        
        if( immediate && !slide.retrieve( 'announced' ) ) that._announce( slide );
        
        if( !alreadyLoading )
        {
            vars.loadCounter++;
            img = new Element( 'img' ).inject( slide );
            img.onload = that._imageListener.pass( ['load', slide] );
            img.onabort = that._imageListener.pass( ['abort', slide] );
            img.onerror = that._imageListener.pass( ['error', slide] );
            img.set( 'src', that.options.basePath + slide.retrieve( 'img-src' ) );
        }
    },
    
    _imageListener: function( evt, slide )
    {
        var that = this,
            vars = that.vars,
            isNext, isAnnounced,
            slideNum, num = 0,
            nextSlide,
            img;
    
        if( !slide || !slide.getParent() ) return;

        switch( evt )
        {
            case 'abort':
            case 'error':
                isNext = vars.nextSlide == slide,
                isAnnounced = slide.retrieve( 'announced' ),
                slideNum = slide.retrieve( 'slide-num' );
                slide.destroy();
                
                vars.list.getElements( 'li' ).each( function( slide )
                {
                    if( slide.hasClass( 'slide-foreign' ) ) return;
                    slide.removeClass( 'slide-' + slide.retrieve( 'slide-num' ) ).addClass( 'slide-' + num ).store( 'slide-num', num++ );
                } );
                
                vars.length--;
                
                if( isNext )
                {
                    if( vars.nextSlideMode == 'next' ) nextSlide = vars.list.getElement( 'li.slide-' + ( slideNum >= ( vars.length - 1 ) ? '0' : vars.activeSlide.retrieve( 'slide-num' ) + 1 ) );
                    else nextSlide = vars.list.getElement( 'li.slide-' + ( slideNum <= 0 ? vars.length - 1 : vars.activeSlide.retrieve( 'slide-num' ) - 1 ) );
                    
                    if( nextSlide == vars.activeSlide ) nextSlide = null;
                    else if( isAnnounced ) that._announce( nextSlide );
                    
                    vars.nextSlide = nextSlide;
                }
                
                if( vars.curIndex != -1 && vars.curIndex > slideNum ) vars.curIndex--;
                
                break;
        
            case 'load':
                img = slide.getFirst( 'img' );
                if( that.options.protect ) img.enableProtection();
                slide.removeClass( 'slide-loading' );
                
                if( !vars.ready && slide.retrieve( 'slide-num' ) == 0 ) that._launch( slide );
                else if( vars.ready && !vars.sliderReady ) that._launch();
                else if( vars.ready && vars.sliderReady && vars.nextSlide == slide ) that._slide( slide, true );
        }
        
        vars.loadCounter--;
        if( !vars.loadCounter )
        {
            slide = vars.list.getElement( 'li.slide-pending' );
            if( slide ) that._load( slide );
            else that.fireEvent( 'load-complete' );
        }
    },
    
    _performance: function( speed )
    {
        var that = this,
            vars = that.vars,
            opts = that.options;
        
        if( opts.performance != 'optimize' ) return;
        clearTimeout( vars.performanceTimer );
        
        if( speed ) that._setPerformance( true );
        else vars.performanceTimer = that._setPerformance.delay( opts.performanceDelay, that, false );
    },
    
    _setPerformance: function( speed )
    {
        this.vars.list[(speed ? 'add' : 'remove') + 'Class']( 'speed' );
    },
    
    _announce: function( slide )
    {
        if( !slide.retrieve( 'announced' ) ) this.fireEvent( 'before-slide-load', [slide, slide.store( 'announced', true ).retrieve( 'slide-num' )] );
    },
    
    _slide: function( newSlide, afterLoad )
    {
        var that = this,
            vars = that.vars,
            opts = that.options,
            prevSlide, currentSlide,
            remainOffsetPixels,
            startX,
            noFx;
    
        if( !newSlide || !vars.nextSlide || !newSlide.getParent() || !vars.nextSlide.getParent() || vars.nextSlide != newSlide || vars.activeSlide == newSlide || newSlide.hasClass( 'slide-pending' ) || !that.checkStack( arguments ) ) return;
        
        prevSlide = vars.list.getChildren( 'li.slide-prev' )[0],
        currentSlide = vars.list.getChildren( 'li.slide-active' )[0],
        remainOffsetPixels = vars.container.getSize().x * ( opts.remainOffsetPercent / 100 ),
        startX = vars.nextSlideMode == 'prev' ? -remainOffsetPixels : remainOffsetPixels,
        noFx = vars.nextSlideNoFx;
        
        vars.nextSlide = vars.nextSlideMode = vars.nextSlideNoFx = null;
        vars.activeSlide = newSlide;
        vars.curIndex = newSlide.retrieve( 'slide-num' );
        vars.inSlide = true;
        
        if( afterLoad && newSlide.retrieve( 'announced' ) ) that.fireEvent( 'after-slide-load', [newSlide, vars.curIndex] );
        
        prevSlide && prevSlide.removeClass( 'slide-prev' );
        currentSlide.removeClass( 'slide-active' ).addClass( 'slide-prev' );
        newSlide.setStyles( {
            left       : startX,
            visibility : 'hidden'
        } ).addClass( 'slide-active' );
        
        that._performance( true );
        that._resizeListener( newSlide.getFirst( 'img' ), true );
        that.fireEvent( 'before-slide', [newSlide, vars.curIndex, vars.length] );
        
        if( noFx )
        {
            newSlide.setStyles( {
                visibility : 'visible',
                left       : 0
            } );
            that._slideCompleteListener();
        }
        else
        {
            var fx = newSlide.retrieve( 'fx' );
            if( !fx )
            {
                fx = new Fx.Tween( newSlide, {
                    fps        : 'animationFrame',
                    duration   : opts.endFxDuration,
                    transition : Fx.Transitions.Quart.easeOut
                } ).addEvent( 'complete', that._slideCompleteListener );
                
                newSlide.store( 'fx', fx );
            }
            
            fx.set( 'visibility', 'visible' ).start( 'left', startX, 0 );
        }
    },
    
    _slideCompleteListener: function( noReset )
    {
        var that = this,
            vars = that.vars;
    
        vars.list.getElement( '.slide-prev' ).setStyle( 'visibility', 'hidden' );
        
        that._performance();
        vars.inSlide = false;
        vars.foreignSlides.destroy().empty();
        
        that.fireEvent( 'after-slide', [vars.activeSlide, vars.curIndex, vars.length] );
        
        // Release lock set in _slide()
       that.callStack();
    },
    
    _resizeListener: ( function()
    {
        var img, iSize, iRatio, cSize, mSize, imgX, imgY,
            resizeX = function( checkMin )
            {
                if( checkMin )
                {
                    var checkY = mSize.x * iRatio;
                    if( cSize.y <= checkY ) setSize( mSize.x, checkY );
                    else resizeY();
                }
                else setSize( cSize.x, cSize.x * iRatio );
            },
            resizeY = function( checkMin )
            {
                if( checkMin )
                {
                    var checkX = mSize.y / iRatio;
                    if( cSize.x <= checkX ) setSize( checkX, mSize.y );
                    else resizeX();
                }
                else setSize( cSize.y / iRatio, cSize.y );
            },
            setSize = function( x, y )
            {
                imgX = x;
                imgY = y;
                img.setStyles( { 'width': x, 'height': y } );
            };
    
        return function( element, noPerformance )
        {
            var that = this,
                vars = that.vars,
                opts = that.options;
        
            if( !noPerformance ) that._performance( true );

            cSize = vars.container.getSize();
            if( !mSize ) mSize = vars.mSize;
        
            ( element ? [element] : vars.list.getElements( 'li.slide-active img' + ( vars.inSlide ? ', li.slide-prev img' : '' ) ) ).each( function( element )
            {
                img = element;
                iSize = img.retrieve( 'orig-size' ) || ( function()
                {
                    var obj = { x: img.width, y: img.height };
                    img.store( 'orig-size', obj );
                    return obj;
                } )();
                iRatio = img.retrieve( 'ratio' ) || ( function()
                {
                    var r = iSize.y / iSize.x;
                    img.store( 'ratio', r );
                    return r;
                } )();
                
                if( mSize.x > cSize.x ) resizeX( true );
                else if( mSize.y > cSize.y ) resizeY( true );
                else if( ( cSize.y / cSize.x ) > iRatio ) resizeY();
                else resizeX();
                
                opts.horizontalCenter && img.setStyle( 'left', ( cSize.x - imgX ) / 2 );
                opts.verticalCenter && img.setStyle( 'top', ( cSize.y - imgY ) / 2 )
            }, that );
            
            if( !noPerformance ) that._performance();
        };
    } )()
} );

// Protects elements from being dragged (includes dragging out of the browser)
// and disables the context menu.
if( !Element.enableProtection )
{
    ( function()
    {
        var protector = function( e ) { e.preventDefault(); },
            protectEvents = { 'contextmenu': protector, 'mousedown': protector };
        
        Element.implement( {
            enableProtection: function() { this.addEvents( protectEvents ); },
            disableProtection: function() { this.removeEvents( protectEvents ); }
        } );
    } )();
}

