/**
 * ==========================================================================
 * Copyright © 2009 Jörn P. Meier
 * All rights reserved.
 * webmaster@ionflux.org
 * --------------------------------------------------------------------------
 * ionflux.js          Ionflux.org scripts.
 * ==========================================================================
 */

/**
 * String: pad (left).
 *
 * width   -- Target string width.
 * padChar -- Padding character.
 *
 * Pad a string to the specified width by adding the specified padding 
 * character to the left.
 */
String.prototype.lpad = function(width, padChar)
{
    if (typeof(padChar) == 'undefined')
        padChar = ' ';
    var result = "";
    for (var i = 0; i < (width - this.length); i++)
        result += padChar;
    result += this;
    return result;
};

// Ionflux namespace.
Ionflux = {
    
    // Debug mode.
    DEBUG: false,
    
    // Message level: debug.
    MSG_DEBUG: 0, 
    // Message level: info.
    MSG_INFO: 10, 
    // Message level: warning.
    MSG_WARN: 20, 
    // Message level: error.
    MSG_ERR: 30, 
    
    // Message reporting level.
    logLevel: 10, 
    
    // Length for unique IDs.
    UID_LENGTH: 20, 
    
    // Source for unique IDs.
    uidSource: null, 
    
    // Position: free.
    POSITION_FREE: 0, 
    // Position: centered.
    POSITION_CENTERED: 1, 
    // Position: expand.
    POSITION_EXPAND: 2, 
    
    // Page locations, relative to the base URL.
    PAGE_LOCATIONS: {
        index: 'index.html', 
        gallery: 'gallery.html', 
        blog: 'blog/', 
        contact: 'contact/', 
        projects: 'projects.html', 
        links: 'links.html', 
        impressum: 'impressum.html', 
        datenschutz: 'datenschutz.html'
    },
    
    // Image path.
    IMAGE_PATH: '/images',
    
    /**
     * Log a message.
     *
     * This function does not do anything by default. An implementation 
     * should be provided by the application.
     *
     * msg     -- The message.
     * source  -- Message source.
     * level   -- Message level.
     */
     log: function(msg, source, level)
     {
         // Does nothing by default.
     }
};

// Undefined check.
Ionflux.isUndefined = function(v)
{
    if (typeof(v) == 'undefined')
        return true;
    return false;
};

/**
 * Default argument support.
 *
 * v             -- Value.
 * defaultValue  -- Default value.
 *
 * Returns the default value if the argument is not defined.
 */
Ionflux.defaultArg = function(v, defaultValue)
{
    if (typeof(v) == 'undefined')
        return defaultValue;
    return v;
};

/**
 * Get random character.
 * 
 * useChars  -- Characters to be used.
 * 
 * Get a random character from the specified range. If no range is specified, 
 * a random alphanumeric character or underscore will be returned.
 */
Ionflux.randomChar = function(useChars)
{
    var c0 = Ionflux.defaultArg(useChars, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        + "abcdefghijklmnopqrstuvwxyz_0123456789");
    var i = Math.floor(Math.random() * c0.length);
    return c0.charAt(i);
};

/**
 * Get random string.
 * 
 * l         -- Length.
 * useChars  -- Characters to be used.
 * 
 * Get a random string of the specified length from the specified character 
 * range. If no range is specified, alphanumeric characters and the underscore 
 * will be used.
 */
Ionflux.randomString = function(l, useChars)
{
    var l0 = Ionflux.defaultArg(l, 20);
    var c0 = Ionflux.defaultArg(useChars, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        + "abcdefghijklmnopqrstuvwxyz_0123456789");
    var r = "";
    for (var i = 0; i < l; i++)
        r += Ionflux.randomChar(c0);
    return r;
};

/**
 * Timer.
 * 
 * A timer which invokes a callback at regular intervals.
 */
Ionflux.Timer = Class.create({
    
    // Constructor.
    initialize: function(callback, period, maxIterations)
    {
        // Callback function.
        this.callback = Ionflux.defaultArg(callback, null);
        // Timer period (in ms).
        this.period = Ionflux.defaultArg(period, 1000);
        /* Maximum number of iterations.
           If this is 0, the timer runs until it is stopped. */
        this.maxIterations = Ionflux.defaultArg(maxIterations, 0);
        // Interval ID.
        this.intervalID = null;
        // Iteration count.
        this.iterationCount = 0;
        // Restart period.
        this.restartPeriod = this.period;
        // Schedule restart.
        this.scheduleRestart = false;
        // Handler active.
        this.handlerActive = false;
        // Running.
        this.running = false;
    },
    
    // Handle timer event.
    onTimer: function()
    {
        this.handlerActive = true;
        if (this.callback !== null)
            this.callback(this);
        this.iterationCount++;
        if ((this.maxIterations > 0)
            && (this.iterationCount >= this.maxIterations))
            this.stop();
        this.handlerActive = false;
        if (this.scheduleRestart)
            this.start(this.restartPeriod);
    },
    
    // Start the timer.
    start: function(period)
    {
        if (this.running)
            return;
        /* <---- DEBUG ----- //
        Ionflux.log("Timer started.", "Timer.start");
        // ----- DEBUG ----> */
        var p0 = Ionflux.defaultArg(period, this.period);
        if (this.intervalID !== null)
            return;
        this.intervalID = window.setInterval(
            this.onTimer.bind(this), p0);
        this.scheduleRestart = false;
        this.running = true;
    },
    
    // Stop the timer.
    stop: function()
    {
        if (this.intervalID === null)
            return;
        window.clearInterval(this.intervalID);
        this.intervalID = null;
        this.iterationCount = 0;
        this.running = false;
    },
    
    // Restart the timer.
    restart: function(period)
    {
        var p0 = Ionflux.defaultArg(period, this.period);
        if (this.handlerActive)
        {
            this.scheduleRestart = true;
            this.restartPeriod = p0;
            return;
        }
        this.stop();
        this.start();
    }
});

/**
 * UID source.
 *
 * A source for unique IDs.
 */
Ionflux.UIDSource = Class.create({
    
    // Constructor.
    initialize: function(uidLength)
    {
        this.uidLength = Ionflux.defaultArg(uidLength, Ionflux.UID_LENGTH);
        this.uids = new Hash();
    }, 
    
    getUID: function()
    {
        var uid = Ionflux.randomString(this.uidLength);
        var maxTries = 100;
        var i = 0;
        while (!Ionflux.isUndefined(this.uids.get(uid))
            && (i < maxTries))
        {
            uid = Ionflux.randomString(this.uidLength);
            i++;
        }
        if (i >= maxTries)
            throw new Error("[Ionflux.UIDSource.getUID] " 
                + "Maximum number of attempts to generate a UID exceeded!");
        this.uids.set(uid, true);
        return uid;
    }
    
});

// Get UID from the default source.
Ionflux.getUID = function()
{
    if (Ionflux.uidSource === null)
        Ionflux.uidSource = new Ionflux.UIDSource();
    return Ionflux.uidSource.getUID();
};

/**
 * Identifiable object.
 *
 * An object that can be uniquely identified.
 */
Ionflux.Identifiable = Class.create({
    
    // Constructor.
    initialize: function()
    {
        this.uniqueID = Ionflux.getUID();
    },
    
    toString: function()
    {
        return "Identifiable[" + this.uniqueID + "]";
    }
});

/**
 * Set.
 *
 * A set of objects. Objects which are added to the set need to have an 
 * uniqueID property that uniquely identifies the object.
 */
Ionflux.Set = Class.create({
    
    // Constructor.
    initialize: function()
    {
        this.entries = new Hash();
    },
    
    // Add object.
    add: function(obj)
    {
        if (Ionflux.isUndefined(obj.uniqueID))
            throw Error("[Ionflux.Set.add] "
                + "Object does not have a unique ID!");
        this.entries.set(obj.uniqueID, obj);
    },
    
    // Remove object.
    remove: function(obj)
    {
        if (Ionflux.isUndefined(obj.uniqueID))
            throw Error("[Ionflux.Set.remove] "
                + "Object does not have a unique ID!");
        this.entries.unset(obj.uniqueID);
    },
    
    // Pop object.
    pop: function()
    {
        var keys = this.entries.keys();
        if (keys.length === 0)
            return null;
        return this.entries.unset(keys[0]);
    },
    
    // Check whether the set contains an object.
    contains: function(obj)
    {
        if (Ionflux.isUndefined(obj.uniqueID))
            return false;
        /*
            throw Error("[Ionflux.Set.contains] "
                + "Object does not have a unique ID!");
         */
        if (Ionflux.isUndefined(this.entries.get(obj.uniqueID)))
            return false;
        return true;
    },
    
    // Update set with another set.
    update: function(other)
    {
        var e0 = other.getEntries();
        for (var i = 0; i < e0.length; i++)
        {
            var it = e0[i];
            this.add(it);
        }
    },
    
    // Get number of entries.
    getNumEntries: function()
    {
        return this.entries.keys().length;
    },
    
    // Get entries.
    getEntries: function()
    {
        return this.entries.values();
    },
    
    // Clear entries.
    clear: function()
    {
        this.entries = new Hash();
    },
    
    toString: function()
    {
        var r = "";
        var keys = this.entries.keys();
        for (var i = 0; i < keys.length; i++)
        {
            var k = keys[i];
            if (i > 0)
                r += ', ';
            var it = this.entries.get(k);
            r += it;
        }
        return "Set[" + r + "]";
    }
});

/**
 * Widget.
 * 
 * An element of the user interface.
 */
Ionflux.Widget = Class.create(Ionflux.Identifiable, {
    
    // Constructor.
    initialize: function($super, width, height, x, y, viewport, parent, 
        position)
    {
        $super.call(this);
        
        // Width.
        this.width = Ionflux.defaultArg(width, 0);
        // Height.
        this.height = Ionflux.defaultArg(height, 0);
        // X position.
        this.x = Ionflux.defaultArg(x, 0);
        // Y position.
        this.y = Ionflux.defaultArg(y, 0);
        // Viewport element.
        this.viewport = Ionflux.defaultArg(viewport, null);
        // Parent widget.
        this.parent = Ionflux.defaultArg(parent, null);
        // Position.
        this.position = Ionflux.defaultArg(position, Ionflux.POSITION_FREE);
        // Child widgets.
        this.children = new Ionflux.Set();
        
        this.setup();
    },
    
    // Center widget inside parent.
    center: function()
    {
        if ((this.parent === null)
            || (this.viewport === null))
            return;
        var pw = this.parent.getWidth();
        var ph = this.parent.getHeight();
        this.x = Math.round(0.5 * (pw - this.width));
        this.y = Math.round(0.5 * (ph - this.height));
        /* <---- DEBUG ----- //
        Ionflux.log("x = " + this.x + ", y = " + this.y 
            + ", parent.width = " + pw + ", parent.height = " + ph, 
            "Widget.center");
        // ----- DEBUG ----> */
    },
    
    // Expand widget to parent viewport size.
    expand: function()
    {
        if ((this.parent === null)
            || (this.viewport === null))
            return;
        this.x = 0;
        this.y = 0;
        this.width = this.parent.getWidth();
        this.height = this.parent.getHeight();
    },
    
    // Set absolute geometry.
    setGeometry: function(x, y, width, height)
    {
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
    },
    
    // Resize UI.
    resizeUI: function()
    {
        if (this.viewport === null)
            return;
        if (this.position == Ionflux.POSITION_CENTERED)
            this.center();
        else
        if (this.position == Ionflux.POSITION_EXPAND)
            this.expand();
        /* <---- DEBUG ----- //
        Ionflux.log("x = " + this.x + ", y = " + this.y + ", width = " 
            + this.width + ", height = " + this.height, "Widget.resizeUI");
        // ----- DEBUG ----> */
        this.viewport.setStyle({
            top: "" + this.y + "px", 
            left: "" + this.x + "px", 
            width: "" + this.width + "px", 
            height: "" + this.height + "px"
        });
        // Resize the children.
        var e0 = this.children.getEntries();
        for (var i = 0; i < e0.length; i++)
        {
            var it = e0[i];
            it.resizeUI();
        }
    },
    
    // Update the widget.
    update: function()
    {
        this.resizeUI();
    },
    
    // Setup user interface.
    setupUI: function()
    {
        this.update();
    },
    
    // Setup event handlers.
    setupEventHandlers: function()
    {
        // To be implemented by derived classes.
    },
    
    // Setup.
    setup: function()
    {
        this.setupUI();
        this.setupEventHandlers();
    },
    
    /**
     * Add child widget.
     * 
     * w  -- Widget to be added.
     * 
     * Add a widget as a child of this widget. If both the widget and the new 
     * child have a viewport, the child viwport will be added to the viewport 
     * of this widget.
     */
    add: function(w)
    {
        this.children.add(w);
        w.parent = this;
        var v0 = w.getViewport();
        if ((v0 === null)
            || (this.viewport === null))
            return;
        this.viewport.insert(v0);
    },
    
    /**
     * Remove child widget.
     * 
     * w  -- Widget to be removed.
     * 
     * Remove a widget from this widget. This also removes the viewport, if 
     * the child widget has one.
     */
    remove: function(w)
    {
        if (!this.children.contains(w))
            return;
        var v0 = w.getViewport();
        w.parent = null;
        this.children.remove(w);
        if (v0 === null)
            return;
        v0.remove();
    },
    
    // Get viewport.
    getViewport: function()
    {
        return this.viewport;
    },
    
    // Set width.
    setWidth: function(width)
    {
        this.width = width;
    },
    
    // Get width.
    getWidth: function()
    {
        return this.width;
    },
    
    // Set height.
    setHeight: function(height)
    {
        this.height = height;
    },
    
    // Get height.
    getHeight: function()
    {
        return this.height;
    },
    
    // Set X position.
    setX: function(x)
    {
        this.x = x;
    },
    
    // Get X position.
    getX: function()
    {
        return this.x;
    },
    
    // Set Y position.
    setY: function(y)
    {
        this.y = y;
    },
    
    // Get Y position.
    getY: function()
    {
        return this.y;
    },
    
    // Show viewport.
    show: function()
    {
        if (this.viewport === null)
            return;
        this.viewport.show();
    },
    
    // Hide viewport.
    hide: function()
    {
        if (this.viewport === null)
            return;
        this.viewport.hide();
    },
    
    // Fade in viewport.
    fadeIn: function(duration)
    {
        if (this.viewport === null)
            return;
        var d0 = Ionflux.defaultArg(duration, 1);
        Effect.Appear(this.viewport, { 
            duration: d0,
            queue: { 
                position: 'end', 
                scope: this.uniqueID
            }
        });
    },
    
    // Fade out viewport.
    fadeOut: function(duration)
    {
        if (this.viewport === null)
            return;
        var d0 = Ionflux.defaultArg(duration, 1);
        Effect.Fade(this.viewport, {
            duration: d0,
            queue: { 
                position: 'end', 
                scope: this.uniqueID
            }
        });
    },
    
    // Set position.
    setPosition: function(p)
    {
        this.position = p;
        this.resizeUI();
    },
    
    // Get position.
    getPosition: function()
    {
        return this.position;
    }
});

/**
 * Content pane.
 * 
 * A pane on the page where content can be inserted and positioned.
 */
Ionflux.ContentPane = Class.create(Ionflux.Widget, {
    
    // Constructor.
    initialize: function($super, width, height, x, y, parent)
    {
        var e0 = new Element('div', {
            id: 'contentPane_' + this.uniqueID
        });
        e0.setStyle({
            // border: '1px solid #00ff00', 
            position: 'absolute'
        });
        
        $super.call(this, width, height, x, y, e0, parent);
    }
});

/**
 * Image.
 * 
 * A static image.
 */
Ionflux.Image = Class.create(Ionflux.Widget, {
    
    // Constructor.
    initialize: function($super, source, width, height, x, y, parent)
    {
        var s0 = Ionflux.defaultArg(source, null);
        
        var e0 = new Element('div', {
            id: 'image_' + this.uniqueID
        });
        e0.setStyle({
            // border: '1px solid #00ff00', 
            position: 'absolute'
        });
        
        $super.call(this, width, height, x, y, e0, parent);
        
        if (source !== null)
            this.setSource(s0);
    },
    
    // Set source URI.
    setSource: function(source)
    {
        this.source = source;
        this.viewport.setStyle({
            backgroundImage: 'url(' + this.source + ')', 
            backgroundPosition: 'top left', 
            backgroundRepeat: 'no-repeat'
        });
    },
    
    // Get source.
    getSource: function()
    {
        return this.source;
    }
});

/**
 * Image link.
 * 
 * An image link. The image link supports a base image and a highlight image, 
 * which is shown as a hover effect with an optional fade-in and fade-out 
 * behaviour.
 */
Ionflux.ImageLink = Class.create(Ionflux.Widget, {
    
    // Constructor.
    initialize: function($super, baseSource, hlSource, width, height, x, y, 
        fadeInTime, fadeOutTime, parent, linkID)
    {
        var p0 = Ionflux.defaultArg(parent, null);
        
        // Fade in time.
        this.fadeInTime = Ionflux.defaultArg(fadeInTime, 0);
        // Fade out time.
        this.fadeOutTime = Ionflux.defaultArg(fadeOutTime, 0);
        // Highlight state.
        this.highlighted = false;
        // Activate handler.
        this.activateHandler = null;
        // Mouse over handler.
        this.mouseOverHandler = null;
        // Mouse out handler.
        this.mouseOutHandler = null;
        // Active flag.
        this.active = true;
        // Enable highlighting effect.
        this.enableHighlight = true;
        // Link ID.
        this.linkID = Ionflux.defaultArg(linkID, null);
        
        var e0 = new Element('div', {
            id: 'imageLink_' + this.uniqueID
        });
        e0.setStyle({
            // border: '1px solid #00ff00', 
            position: 'absolute'
        });
        
        // Base image.
        this.baseImage = new Ionflux.Image(baseSource, 
            width, height, 0, 0);
        // Highlight image.
        this.highlightImage = new Ionflux.Image(hlSource, 
            width, height, 0, 0);
        // Event target.
        this.eventTarget = new Ionflux.ContentPane(width, height, 0, 0);
        
        if (baseSource == hlSource)
            this.enableHighlight = false;
        
        $super.call(this, width, height, x, y, e0, p0);
        
        this.add(this.baseImage);
        this.highlightImage.hide();
        this.add(this.highlightImage);
        this.add(this.eventTarget);
        
        this.eventTarget.getViewport().setStyle({
            cursor: 'pointer', 
            zIndex: 1000
        });
    },
    
    // Event handler: mouse over.
    onMouseOver: function()
    {
        if (this.highlighted 
            || !this.active
            || !this.enableHighlight)
            return;
        this.highlighted = true;
        if (this.fadeInTime > 0)
            this.highlightImage.fadeIn(this.fadeInTime);
        else
            this.highlightImage.show();
        if (this.mouseOverHandler !== null)
            this.mouseOverHandler.onMouseOver(this);
    },
    
    // Event handler: mouse out.
    onMouseOut: function()
    {
        if (!this.highlighted 
            || !this.enableHighlight)
            return;
        this.highlighted = false;
        if (this.fadeOutTime > 0)
            this.highlightImage.fadeOut(this.fadeOutTime);
        else
            this.highlightImage.hide();
        if (this.mouseOutHandler !== null)
            this.mouseOutHandler.onMouseOut(this);
    },
    
    // Event handler: mouse up.
    onMouseUp: function(event)
    {
        if ((this.activateHandler === null)
            || !this.active)
            return;
        if (event.isLeftClick())
            this.activateHandler.onActivate(this);
    },
    
    // Setup event handlers.
    setupEventHandlers: function()
    {
        Event.observe(this.eventTarget.getViewport(), 'mouseover', 
            this.onMouseOver.bindAsEventListener(this));
        Event.observe(this.eventTarget.getViewport(), 'mouseout', 
            this.onMouseOut.bindAsEventListener(this));
        Event.observe(this.eventTarget.getViewport(), 'mouseup', 
            this.onMouseUp.bindAsEventListener(this));
    },
    
    // Flash the highlight image.
    flash: function()
    {
        this.highlightImage.fadeIn(0.1);
        this.highlightImage.fadeOut(0.6);
    },
    
    // Get base image.
    getBaseImage: function()
    {
        return this.baseImage;
    },
    
    // Get highlight image.
    getHighlightImage: function()
    {
        return this.highlightImage;
    },
    
    // Set fade in time.
    setFadeInTime: function(fadeInTime)
    {
        this.fadeInTime = fadeInTime;
    },
    
    // Set fade out time.
    setFadeOutTime: function(fadeOutTime)
    {
        this.fadeOutTime = fadeOutTime;
    },
    
    // Set mouse over handler callback.
    setMouseOverHandler: function(handler)
    {
        this.mouseOverHandler = handler;
    },
    
    // Set mouse over handler callback.
    getMouseOverHandler: function()
    {
        return this.mouseOverHandler;
    },
    
    // Set mouse out handler callback.
    setMouseOutHandler: function(handler)
    {
        this.mouseOutHandler = handler;
    },
    
    // Set mouse out handler callback.
    getMouseOutHandler: function()
    {
        return this.mouseOutHandler;
    },
    
    // Set activate handler callback.
    setActivateHandler: function(handler)
    {
        this.activateHandler = handler;
    },
    
    // Set activate handler callback.
    getActivateHandler: function()
    {
        return this.activateHandler;
    },
    
    // Get active flag.
    getActive: function()
    {
        return this.active;
    },
    
    // Set active flag.
    setActive: function(active)
    {
        this.active = active;
        if (this.active)
        {
            this.eventTarget.getViewport().setStyle({
                cursor: 'pointer'
            });
        } else
        {
            this.eventTarget.getViewport().setStyle({
                cursor: 'default'
            });
        }
    },
    
    // Set link ID.
    setLinkID: function(linkID)
    {
        this.linkID = linkID;
    },
    
    // Get link ID.
    getLinkID: function()
    {
        return this.linkID;
    },
    
    // Set highlight.
    setHighlight: function(highlight)
    {
        if (highlight && !this.highlighted)
        {
            this.highlightImage.show();
            this.highlighted = true;
        } else
        if (!highlight && this.highlighted)
        {
            this.highlightImage.hide();
            this.highlighted = false;
        }
    }
});

/**
 * Main box.
 * 
 * Main content box widget which occupies all of the viewport.
 */
Ionflux.MainBox = Class.create(Ionflux.Widget, {
    
    // Constructor.
    initialize: function($super, parent)
    {
        var p0 = Ionflux.defaultArg(parent, null);
        
        var main = $('main');
        if (main === null)
            throw new Error("[MainPage.setupUI] Main content box is missing!");
        
        $super.call(this, 0, 0, 0, 0, main, p0);
    },
    
    /**
     * Resize UI.
     * 
     * Resize the UI. By default, this sets the size of the viewport element 
     * to the size of the top-level viewport.
     */
    resizeUI: function($super)
    {
        this.x = 0;
        this.y = 0;
        this.width = document.viewport.getWidth();
        this.height = document.viewport.getHeight();
        
        $super.call(this);
    }
    
});

/**
 * Image gallery.
 * 
 * A widget displaying an image gallery.
 */
Ionflux.ImageGallery = Class.create(Ionflux.Widget, {
    
    // Constructor.
    initialize: function($super, x, y, fromIndex, toIndex, thumbWidth, 
        thumbHeight, spacingX, spacingY, thumbBaseURL, numColumns, reverse, 
        paddingTop, paddingRight, paddingBottom, paddingLeft, 
        copyrightNotice)
    {
        var x0 = Ionflux.defaultArg(x, 0);
        var y0 = Ionflux.defaultArg(y, 0);
        
        // First image index.
        this.fromIndex = Ionflux.defaultArg(fromIndex, 1);
        // Last image index.
        this.toIndex = Ionflux.defaultArg(toIndex, 1);
        // Thumbnail image width.
        this.thumbWidth = Ionflux.defaultArg(thumbWidth, 100);
        // Thumbnail image height.
        this.thumbHeight = Ionflux.defaultArg(thumbHeight, 100);
        // Thumbnail spacing (X).
        this.spacingX = Ionflux.defaultArg(spacingX, 10);
        // Thumbnail spacing (Y).
        this.spacingY = Ionflux.defaultArg(spacingY, 10);
        // Thumbnail image base URL.
        this.thumbBaseURL = Ionflux.defaultArg(thumbBaseURL, 
            Ionflux.IMAGE_PATH + '/gallery/thumbs');
        // Number of columns.
        this.numColumns = Ionflux.defaultArg(numColumns, 3);
        // Reverse order.
        this.reverse = Ionflux.defaultArg(reverse, true);
        // Padding: top.
        this.paddingTop = Ionflux.defaultArg(paddingTop, 0);
        // Padding: right.
        this.paddingRight = Ionflux.defaultArg(paddingRight, 0);
        // Padding: bottom.
        this.paddingBottom = Ionflux.defaultArg(paddingBottom, 0);
        // Padding: left.
        this.paddingLeft = Ionflux.defaultArg(paddingLeft, 0);
        // Copyright notice.
        this.copyrightNotice = Ionflux.defaultArg(copyrightNotice, "");
        
        // Number of images.
        this.numImages = this.toIndex - this.fromIndex + 1;
        // Number of rows.
        this.numRows = Math.ceil(this.numImages / this.numColumns);
        // Thumbnail images.
        this.thumbs = [];
        // Select image handler.
        this.selectImageHandler = null;
        // Active flag.
        this.active = true;
        // Copyright notice box.
        this.copyrightBox = null;
        
        var e0 = new Element('div', {
            id: 'imageGallery_' + this.uniqueID
        });
        e0.setStyle({
            // border: '1px solid #00ff00', 
            position: 'absolute'
        });
        
        // Calculate widget size.
        var width = (this.thumbWidth + this.spacingX) 
            * this.numColumns - this.spacingX 
            + this.paddingRight + this.paddingLeft;
        var height = (this.thumbHeight + this.spacingY) 
            * this.numRows - this.spacingY 
            + this.paddingTop + this.paddingBottom;
        
        $super.call(this, width, height, x0, y0, e0, parent);
    },
    
    // Setup the user interface.
    setupUI: function($super)
    {
        // Setup thumbnail images.
        for (var i = 0; i < this.numImages; i++)
        {
            var j = 0;
            if (!this.reverse)
                j = this.fromIndex + i;
            else
                j = this.toIndex - i;
            var col = i % this.numColumns;
            var row = Math.floor(i / this.numColumns);
            var ti = new Ionflux.ImageLink(
                this.thumbBaseURL + '/' + String(j).lpad(4, '0') 
                    + '_s.jpg', 
                this.thumbBaseURL + '/' + String(j).lpad(4, '0') 
                    + '_s_hl.jpg', 
                this.thumbWidth, this.thumbHeight, 
                col * (this.thumbWidth + this.spacingX) + this.paddingLeft, 
                row * (this.thumbHeight + this.spacingY) + this.paddingTop, 
                0.8, 0.8
            );
            ti.setLinkID(j);
            ti.setActivateHandler(this);
            this.thumbs.push(ti);
            this.add(ti);
        }
        
        // Copyright notice.
        this.copyrightBox = new Ionflux.ContentPane(
            this.getWidth(), 20, 
            0, this.numRows * (this.thumbHeight + this.spacingY) 
                + this.spacingY);
        this.copyrightBox.getViewport().addClassName('footer');
        this.copyrightBox.getViewport().insert(this.copyrightNotice);
        this.add(this.copyrightBox);
        
        $super.call(this);
    },
    
    // Event handler: Activate.
    onActivate: function(source)
    {
        this.selectImageHandler.onSelectImage(this, source.getLinkID());
    },
    
    // Set select image handler callback.
    setSelectImageHandler: function(handler)
    {
        this.selectImageHandler = handler;
    },
    
    // Get select image handler callback.
    getSelectImageHandler: function()
    {
        return this.selectImageHandler;
    },
    
    // Get active flag.
    getActive: function()
    {
        return this.active;
    },
    
    // Set active flag.
    setActive: function(active)
    {
        this.active = active;
        var i = 0;
        if (this.active)
        {
            for (i = 0; i < this.thumbs.length; i++)
                this.thumbs[i].setActive(true);
        } else
        {
            for (i = 0; i < this.thumbs.length; i++)
                this.thumbs[i].setActive(false);
        }
    }
});

/**
 * Image view.
 * 
 * A widget displaying a single work of art. The widget also features buttons 
 * to browse to other images.
 */
Ionflux.ImageView = Class.create(Ionflux.Widget, {
    
    // Constructor.
    initialize: function($super, backgroundColor, backgroundOpacity, parent)
    {
        // Background color.
        this.backgroundColor = Ionflux.defaultArg(backgroundColor, '#101010');
        // Background opacity.
        this.backgroundOpacity = Ionflux.defaultArg(backgroundOpacity, 0.9);
        
        // Background.
        this.background = null;
        // Button: previous image.
        this.prevButton = null;
        // Button: next image.
        this.nextButton = null;
        // Caption box.
        this.captionBox = null;
        // Close handler.
        this.closeHandler = null;
        // Previous image handler.
        this.prevImageHandler = null;
        // Next image handler.
        this.nextImageHandler = null;
        // Default image caption.
        this.defaultCaption = 'Untitled';
        // Default image caption.
        this.copyrightNotice = '© 2006-2009 Jörn P. Meier';
        
        var e0 = new Element('div', {
            id: 'imageView_' + this.uniqueID
        });
        e0.setStyle({
            // border: '1px solid #00ff00', 
            position: 'absolute'
        });
        
        $super.call(this, 100, 100, 0, 0, e0, parent);
        
        this.position = Ionflux.POSITION_EXPAND;
    },
    
    // Setup the user interface.
    setupUI: function($super)
    {
        // The background.
        this.background = new Ionflux.ContentPane();
        this.background.setPosition(Ionflux.POSITION_EXPAND);
        this.background.getViewport().setStyle({
            backgroundColor: this.backgroundColor, 
            cursor: 'pointer', 
            zIndex: 1000
        });
        this.background.getViewport().setOpacity(this.backgroundOpacity);
        this.add(this.background);
        
        // The image.
        this.image = new Ionflux.Image(null, 100, 100);
        this.image.getViewport().setStyle({
            cursor: 'pointer', 
            zIndex: 2000
        });
        this.image.setPosition(Ionflux.POSITION_CENTERED);
        this.add(this.image);
        
        // Browse buttons.
        this.prevButton = new Ionflux.ImageLink(
            Ionflux.IMAGE_PATH + '/arrow_l01.png', 
            Ionflux.IMAGE_PATH + '/arrow_l01_hl.png', 
            26, 26, 0, 0, 0.8, 0.8
        );
        this.prevButton.getViewport().setStyle({
            cursor: 'pointer', 
            zIndex: 2000
        });
        this.nextButton = new Ionflux.ImageLink(
            Ionflux.IMAGE_PATH + '/arrow_r01.png', 
            Ionflux.IMAGE_PATH + '/arrow_r01_hl.png', 
            26, 26, 0, 0, 0.8, 0.8
        );
        this.nextButton.getViewport().setStyle({
            cursor: 'pointer', 
            zIndex: 2000
        });
        this.add(this.prevButton);
        this.add(this.nextButton);
        
        // Caption box.
        this.captionBox = new Ionflux.ContentPane(100, 20, 0, 0);
        this.captionBox.getViewport().setStyle({
            zIndex: 2000, 
            textAlign: 'center'
        });
        this.add(this.captionBox);
        
        $super.call(this);
    },
    
    // Update button positions.
    updateButtonPositions: function()
    {
        var spacing = 14;
        var yp = this.image.getY() + 0.5 * (this.image.getHeight() 
            - this.prevButton.getHeight());
        var yn = this.image.getY() + 0.5 * (this.image.getHeight() 
            - this.nextButton.getHeight());
        var xp = this.image.getX() - spacing - this.prevButton.getWidth();
        var xn = this.image.getX() + this.image.getWidth() + spacing;
        this.prevButton.setX(xp);
        this.prevButton.setY(yp);
        this.nextButton.setX(xn);
        this.nextButton.setY(yn);
        this.prevButton.resizeUI();
        this.nextButton.resizeUI();
    },
    
    // Update caption position.
    updateCaptionPosition: function()
    {
        var spacing = 14;
        var y = this.image.getY() + this.image.getHeight() + spacing;
        var x = this.image.getX();
        this.captionBox.setX(x);
        this.captionBox.setY(y);
        this.captionBox.setWidth(this.image.getWidth());
        this.captionBox.resizeUI();
    },
    
    // Resize UI.
    resizeUI: function($super)
    {
        $super.call(this);
        
        /* Some sort of hack to get the view to center even if the parent 
           has been scrolled. */
        if (this.parent !== null)
        {
            var pst = this.parent.getViewport().scrollTop;
            if (pst > 0)
                this.viewport.setStyle({
                    top: '' + pst + 'px'
                });
        }
        
        this.updateButtonPositions();
        this.updateCaptionPosition();
    },
    
    // Setup event handlers.
    setupEventHandlers: function()
    {
        Event.observe(this.background.getViewport(), 'mouseup', 
            this.onClose.bindAsEventListener(this));
        Event.observe(this.image.getViewport(), 'mouseup', 
            this.onClose.bindAsEventListener(this));
        Event.observe(this.prevButton.getViewport(), 'mouseup', 
            this.onPrevImage.bindAsEventListener(this));
        Event.observe(this.nextButton.getViewport(), 'mouseup', 
            this.onNextImage.bindAsEventListener(this));
    },
    
    // Event handler: close.
    onClose: function()
    {
        this.closeHandler.onClose(this);
    },
    
    // Event handler: previous image.
    onPrevImage: function()
    {
        this.prevImageHandler.onPrevImage(this);
    },
    
    // Event handler: next image.
    onNextImage: function()
    {
        this.nextImageHandler.onNextImage(this);
    },
    
    // Set close handler callback.
    setCloseHandler: function(handler)
    {
        this.closeHandler = handler;
    },
    
    // Get close handler callback.
    getCloseHandler: function()
    {
        return this.closeHandler;
    },
    
    // Set next image handler callback.
    setNextImageHandler: function(handler)
    {
        this.nextImageHandler = handler;
    },
    
    // Get next image handler callback.
    getNextImageHandler: function()
    {
        return this.nextImageHandler;
    },
    
    // Set prev image handler callback.
    setPrevImageHandler: function(handler)
    {
        this.prevImageHandler = handler;
    },
    
    // Get prev image handler callback.
    getPrevImageHandler: function()
    {
        return this.prevImageHandler;
    },
    
    setImage: function(source, width, height, caption)
    {
        var c0 = Ionflux.defaultArg(caption, this.defaultCaption);
        this.image.setWidth(width);
        this.image.setHeight(height);
        this.image.setSource(source);
        this.updateButtonPositions();
        this.updateCaptionPosition();
        this.captionBox.getViewport().update(
            '<p class="imageViewCaption">' + c0 
            + '</p><p class="imageViewCopyrightNotice">' 
            + this.copyrightNotice + '</p>');
        this.resizeUI();
    }
});

// Main page.
Ionflux.MainPage = Class.create({
    
    // Constructor.
    initialize: function()
    {
        // Main content box.
        this.mainBox = null;
        // Title box (contains the logo and main menu).
        this.titleBox = null;
        // Title image 01.
        this.titleImage01 = null;
        // Title image 02.
        this.titleImage02 = null;
        // Segment 01.
        this.seg01 = null;
        // Segment 02.
        this.seg02 = null;
        // Segment 03.
        this.seg03 = null;
        // Main menu items.
        this.mainMenuItems = [];
        // Main menu item set.
        this.mainMenuItemSet = new Ionflux.Set();
        // Number of main menu items.
        this.numMainMenuItems = 7;
        // Main menu item coordinates.
        this.mainMenuItemCoords = [
            [ 0, 0 ], 
            [ 0, 17 ], 
            [ 0, 34 ], 
            [ 0, 51 ], 
            [ 0, 67 ], 
            [ 0, 85 ], 
            [ 0, 102 ]
        ];
        // Main menu offset.
        this.mainMenuOffset = [ 275, 177 ];
        // Main menu visible.
        this.mainMenuVisible = false;
        // Main menu timer.
        this.mainMenuTimer = new Ionflux.Timer(
            this.onTimer.bind(this), 3000, 1);
        // Flash timer.
        this.flashTimer = new Ionflux.Timer(
            this.onTimer.bind(this), 2000, 1);
        // Flash index.
        this.flashIndex = 0;
    },
    
    // Setup UI.
    setupUI: function()
    {
        // Remove 'no javascript stuff'.
        var nojs01 = $('noJSInfo');
        var nojs02 = $('noJSMainContent');
        if (nojs01 !== null)
            nojs01.remove();
        if (nojs02 !== null)
            nojs02.remove();
        
        // Wrap the main box so it can be used as a widget.
        this.mainBox = new Ionflux.MainBox();
        
        this.titleBox = new Ionflux.ContentPane(380, 300);
        this.mainBox.add(this.titleBox);
        this.titleBox.setPosition(Ionflux.POSITION_CENTERED);
        this.titleImage01 = new Ionflux.Image(Ionflux.IMAGE_PATH 
            + '/fluxwheel01.jpg', 
            241, 254, 11, 8);
        this.titleBox.add(this.titleImage01);
        this.titleImage02 = new Ionflux.Image(Ionflux.IMAGE_PATH 
            + '/flux01.jpg', 
            126, 55, 236, 101);
        
        this.seg01 = new Ionflux.ImageLink(Ionflux.IMAGE_PATH + '/fluxsegment01.jpg', 
            Ionflux.IMAGE_PATH + '/fluxsegment01hl.jpg', 91, 97, 12, 39, 0.8, 0.8);
        this.seg01.setActivateHandler(this);
        this.seg01.setMouseOverHandler(this);
        this.seg01.setMouseOutHandler(this);
        this.titleBox.add(this.seg01);
        this.seg02 = new Ionflux.ImageLink(Ionflux.IMAGE_PATH + '/fluxsegment02.jpg', 
            Ionflux.IMAGE_PATH + '/fluxsegment02hl.jpg', 102, 99, 150, 12, 0.8, 0.8);
        this.seg02.setActivateHandler(this);
        this.seg02.setMouseOverHandler(this);
        this.seg02.setMouseOutHandler(this);
        this.titleBox.add(this.seg02);
        this.seg03 = new Ionflux.ImageLink(Ionflux.IMAGE_PATH + '/fluxsegment03.jpg', 
            Ionflux.IMAGE_PATH + '/fluxsegment03hl.jpg', 102, 99, 150, 160, 0.8, 0.8);
        this.seg03.setActivateHandler(this);
        this.seg03.setMouseOverHandler(this);
        this.seg03.setMouseOutHandler(this);
        this.titleBox.add(this.seg03);
        this.titleBox.add(this.titleImage02);
        
        // Main menu items.
        for (var i = 0; i < this.numMainMenuItems; i++)
        {
            var num = ('' + (i + 1)).lpad(2, '0');
            var xi = this.mainMenuItemCoords[i][0] + this.mainMenuOffset[0];
            var yi = this.mainMenuItemCoords[i][1] + this.mainMenuOffset[1];
            var it = new Ionflux.ImageLink(
                Ionflux.IMAGE_PATH + '/fluxmenuitem' + num + '.jpg', 
                Ionflux.IMAGE_PATH + '/fluxmenuitem' + num + 'hl.jpg', 
                92, 16, xi, yi, 0.4, 0.1);
            it.hide();
            it.setActivateHandler(this);
            it.setMouseOverHandler(this);
            it.setMouseOutHandler(this);
            this.mainMenuItems.push(it);
            this.mainMenuItemSet.add(it);
            this.titleBox.add(it);
        }
        this.mainMenuItems[5].show();
        this.mainMenuItems[6].show();
        
        this.flashTimer.start();
    },
    
    // Resize the UI.
    resizeUI: function()
    {
        this.mainBox.resizeUI();
    },
    
    // Setup.
    setup: function()
    {
        this.setupUI();
    },
    
    showMainMenu: function()
    {
        if (this.mainMenuVisible)
            return;
        for (var i = 0; i < this.numMainMenuItems - 2; i++)
        {
            this.mainMenuItems[i].setHighlight(false);
            this.mainMenuItems[i].fadeIn(2);
        }
        this.mainMenuVisible = true;
        this.seg03.setActive(false);
    },
    
    hideMainMenu: function()
    {
        if (!this.mainMenuVisible)
            return;
        for (var i = 0; i < this.numMainMenuItems - 2; i++)
        {
            var it = this.mainMenuItems[i];
            if ((i != 0) 
                || (!this.seg01.highlighted))
                // Don't fade out gallery if highlighted.
                it.fadeOut(2);
        }
        this.mainMenuVisible = false;
        this.seg03.setActive(true);
        this.mainMenuTimer.stop();
    },
    
    // Event handler: Mouse over.
    onMouseOver: function(source)
    {
        if (source == this.seg01)
        {
            /* <---- DEBUG ----- //
            Ionflux.log("Mouse over: gallery", 
                "MainPage.onMouseOver");
            // ----- DEBUG ----> */
            if (!this.mainMenuVisible)
            {
                this.mainMenuItems[0].setHighlight(true);
                this.mainMenuItems[0].fadeIn(0.8);
            } else
                this.mainMenuItems[0].onMouseOver();
            this.flashTimer.stop();
        } else
        if (source == this.seg02)
        {
            /* <---- DEBUG ----- //
            Ionflux.log("Mouse over: blog", 
                "MainPage.onMouseOver");
            // ----- DEBUG ----> */
            this.flashTimer.stop();
        } else
        if (source == this.seg03)
        {
            /* <---- DEBUG ----- //
            Ionflux.log("Mouse over: menu", 
                "MainPage.onMouseOver");
            // ----- DEBUG ----> */
            this.showMainMenu();
            this.flashTimer.stop();
        } else
        if (this.mainMenuItemSet.contains(source))
        {
            if (!this.mainMenuVisible)
            {
                this.showMainMenu();
                this.mainMenuTimer.start();
            } else
                this.mainMenuTimer.restart();
            this.flashTimer.stop();
            if (source == this.mainMenuItems[0])
                // gallery
                this.seg01.onMouseOver();
        }
    },
    
    // Event handler: Mouse out.
    onMouseOut: function(source)
    {
        if (source == this.seg01)
        {
            /* <---- DEBUG ----- //
            Ionflux.log("Mouse out: gallery", 
                "MainPage.onMouseOver");
            // ----- DEBUG ----> */
            if (!this.mainMenuVisible)
                this.mainMenuItems[0].fadeOut(0.4);
             else
                this.mainMenuItems[0].onMouseOut();
        } else
        if (source == this.seg03)
            this.mainMenuTimer.start();
        else
        if (source == this.mainMenuItems[0])
            // gallery
            this.seg01.onMouseOut();
    },
    
    // Event handler: Activate.
    onActivate: function(source)
    {
        if (source == this.seg01)
        {
            /* <---- DEBUG ----- //
            Ionflux.log("Link activated: gallery", 
                "MainPage.onActivate");
            // ----- DEBUG ----> */
            window.location = Ionflux.PAGE_LOCATIONS.gallery;
        } else
        if (source == this.seg02)
        {
            window.location = Ionflux.PAGE_LOCATIONS.blog;
        } else
        if (source == this.seg03)
        {
            /* <---- DEBUG ----- //
            Ionflux.log("Link activated: menu", 
                "MainPage.onActivate");
            // ----- DEBUG ----> */
        } else
        if (source == this.mainMenuItems[0])
            // gallery
            window.location = Ionflux.PAGE_LOCATIONS.gallery;
        else
        if (source == this.mainMenuItems[1])
            // blog
            window.location = Ionflux.PAGE_LOCATIONS.blog;
        else
        if (source == this.mainMenuItems[2])
            // contact
            window.location = Ionflux.PAGE_LOCATIONS.contact;
        else
        if (source == this.mainMenuItems[3])
            // projects
            window.location = Ionflux.PAGE_LOCATIONS.projects;
        else
        if (source == this.mainMenuItems[4])
            // links
            window.location = Ionflux.PAGE_LOCATIONS.links;
        else
        if (source == this.mainMenuItems[5])
            // impressum
            window.location = Ionflux.PAGE_LOCATIONS.impressum;
        else
        if (source == this.mainMenuItems[6])
            // datenschutz
            window.location = Ionflux.PAGE_LOCATIONS.datenschutz;
    },
    
    // Event handler: timer.
    onTimer: function(source)
    {
        /* <---- DEBUG ----- //
        Ionflux.log("Timer handler invoked (source = " + source 
            + ", this.mainMenuTimer = " + this.mainMenuTimer + ", " 
            + (source == this.mainMenuTimer) + ").", 
            "MainPage.onTimer");
        // ----- DEBUG ----> */
        if (source == this.mainMenuTimer)
            this.hideMainMenu();
        else
        if (source == this.flashTimer)
        {
            if (this.flashIndex == 0)
            {
                this.seg03.flash();
                this.flashIndex++;
                this.flashTimer.restart(150);
            } else
            if (this.flashIndex == 1)
            {
                this.seg02.flash();
                this.flashIndex++;
                this.flashTimer.restart(150);
            } else
            if (this.flashIndex == 2)
                this.seg01.flash();
        }
    }
});

// Gallery page.
Ionflux.GalleryPage = Class.create({
    
    // Constructor.
    initialize: function(thumbBaseURL, imageBaseURL, imageData, 
        imageCaptions, imageIndexFrom, imageIndexTo, reverse, 
        imageViewFrameSize, footer)
    {
        // Main content box.
        this.mainBox = null;
        // Main menu item coordinates.
        this.mainMenuItemCoords = [
            [ 0, 0 ], 
            [ 0, 17 ], 
            [ 0, 34 ], 
            [ 0, 51 ], 
            [ 0, 67 ], 
            [ 0, 85 ], 
            [ 0, 102 ]
        ];
        // Header image.
        this.fluxHeader = null;
        // Main menu items.
        this.mainMenuItems = [];
        // Main menu item set.
        this.mainMenuItemSet = new Ionflux.Set();
        // Number of main menu items.
        this.numMainMenuItems = 7;
        // Main menu offset.
        this.mainMenuOffset = [ 50, 108 ];
        // Sub-page links.
        this.subPageLinks = [];
        // Image gallery widget.
        this.gallery = null;
        // Thumbnail base URL.
        this.thumbBaseURL = Ionflux.defaultArg(thumbBaseURL, 
            Ionflux.IMAGE_PATH + '/gallery/thumbs');
        // Image base URL.
        this.imageBaseURL = Ionflux.defaultArg(imageBaseURL, 
            Ionflux.IMAGE_PATH + '/gallery');
        // Image data.
        this.imageData = Ionflux.defaultArg(imageData, []);
        // Image captions.
        this.imageCaptions = Ionflux.defaultArg(imageCaptions, []);
        // First image index.
        this.imageIndexFrom = Ionflux.defaultArg(imageIndexFrom, 1);
        // Last image index.
        this.imageIndexTo = Ionflux.defaultArg(imageIndexTo, 
            this.imageData.length);
        // Current image.
        this.currentImage = 1;
        // Reverse image order.
        this.reverse = Ionflux.defaultArg(reverse, true);
        // Frame size for the image view.
        this.imageViewFrameSize = Ionflux.defaultArg(imageViewFrameSize, 60);
        // Footer.
        this.footer = Ionflux.defaultArg(footer, '© 2006-2009 Jörn P. Meier');
    },
    
    // Setup static images.
    setupStaticImages: function()
    {
        var img01 = new Ionflux.Image(Ionflux.IMAGE_PATH + '/flux02.jpg', 443, 59, 153, 14);
        var img02 = new Ionflux.Image(Ionflux.IMAGE_PATH + '/lineV01.jpg', 1, 343, 152, 14);
        this.mainBox.add(img01);
        this.mainBox.add(img02);
    },
    
    // Setup sub-page links.
    setupSubPageLinks: function()
    {
        for (var i = 0; i < this.subPageLinks.length; i++)
        {
            var it = this.subPageLinks[i];
            it.setActivateHandler(this);
            this.mainBox.add(it);
        }
    },
    
    // Setup UI.
    setupUI: function()
    {
        // Remove 'no javascript stuff'.
        var nojs01 = $('noJSInfoPage');
        var nojs02 = $('noJSPageContent');
        if (nojs01 !== null)
            nojs01.remove();
        if (nojs02 !== null)
            nojs02.remove();
        
        // Wrap the main box so it can be used as a widget.
        this.mainBox = new Ionflux.MainBox();
        
        // Static images.
        this.setupStaticImages();
        
        // Header link.
        this.fluxHeader = new Ionflux.ImageLink(
            Ionflux.IMAGE_PATH + '/flux01.jpg', 
            Ionflux.IMAGE_PATH + '/flux01.jpg', 
            126, 55, 11, 11);
        this.fluxHeader.setActivateHandler(this);
        this.mainBox.add(this.fluxHeader);
        
        // Sub-page links.
        this.setupSubPageLinks();
        
        // Main menu items.
        for (i = 0; i < this.numMainMenuItems; i++)
        {
            var num = ('' + (i + 1)).lpad(2, '0');
            var xi = this.mainMenuItemCoords[i][0] + this.mainMenuOffset[0];
            var yi = this.mainMenuItemCoords[i][1] + this.mainMenuOffset[1];
            it = null;
            if (i == 0)
            {
                // Active menu item.
                it = new Ionflux.Image(Ionflux.IMAGE_PATH + '/fluxmenuitem01hl.jpg', 
                    92, 16, xi, yi);
            } else
            {
                // Menu item links.
                it = new Ionflux.ImageLink(
                    Ionflux.IMAGE_PATH + '/fluxmenuitem' + num + '.jpg', 
                    Ionflux.IMAGE_PATH + '/fluxmenuitem' + num + 'hl.jpg', 
                    92, 16, xi, yi, 0.4, 0.1);
                it.setActivateHandler(this);
                it.setMouseOverHandler(this);
                it.setMouseOutHandler(this);
            }
            this.mainMenuItems.push(it);
            this.mainMenuItemSet.add(it);
            this.mainBox.add(it);
        }
        
        // Image gallery.
        this.gallery = new Ionflux.ImageGallery(191, 137, 
            this.imageIndexFrom, this.imageIndexTo, 169, 71, 
            14, 14, this.thumbBaseURL, 3, true, 
            0, 0, 80, 0, this.footer);
        this.gallery.setSelectImageHandler(this);
        this.mainBox.add(this.gallery);
        
        // Image view.
        this.imageView = new Ionflux.ImageView();
        this.mainBox.add(this.imageView);
        this.imageView.setNextImageHandler(this);
        this.imageView.setPrevImageHandler(this);
        this.imageView.setCloseHandler(this);
        this.imageView.hide();
        
        this.mainBox.resizeUI();
        /* Ugly hack: Recalculate child size again since the previous call 
           might have removed scrollbars. */
        this.mainBox.resizeUI();
    },
    
    // Resize the UI.
    resizeUI: function()
    {
        this.mainBox.resizeUI();
        /* Ugly hack: Recalculate child size again since the previous call 
           might have removed scrollbars. */
        this.mainBox.resizeUI();
        this.setImage();
    },
    
    // Setup.
    setup: function()
    {
        this.setupUI();
    },
    
    // Event handler: Mouse over.
    onMouseOver: function(source)
    {
        // Nothing here.
    },
    
    // Event handler: Mouse out.
    onMouseOut: function(source)
    {
        // Nothing here.
    },
    
    // Event handler: activate (sub-page links).
    onActivateSubPages: function(source)
    {
        // To be implemented by derived classes.
    },
    
    // Event handler: Activate.
    onActivate: function(source)
    {
        if (source == this.fluxHeader)
            // index
            window.location = Ionflux.PAGE_LOCATIONS.index;
        else
        if (source == this.mainMenuItems[1])
            // blog
            window.location = Ionflux.PAGE_LOCATIONS.blog;
        else
        if (source == this.mainMenuItems[2])
            // contact
            window.location = Ionflux.PAGE_LOCATIONS.contact;
        else
        if (source == this.mainMenuItems[3])
            // projects
            window.location = Ionflux.PAGE_LOCATIONS.projects;
        else
        if (source == this.mainMenuItems[4])
            // links
            window.location = Ionflux.PAGE_LOCATIONS.links;
        else
        if (source == this.mainMenuItems[5])
            // impressum
            window.location = Ionflux.PAGE_LOCATIONS.impressum;
        else
        if (source == this.mainMenuItems[6])
            // datenschutz
            window.location = Ionflux.PAGE_LOCATIONS.datenschutz;
        else
            this.onActivateSubPages(source);
    },
    
    // Get image data for the current viewport size.
    getImageData: function(imageID)
    {
        if ((imageID - 1) >= this.imageData.length)
            return null;
        var imageData = this.imageData[imageID - 1];
        var i = 0;
        var found = false;
        var vw = this.mainBox.getWidth() - 2 * this.imageViewFrameSize;
        var vh = this.mainBox.getHeight() - 2 * this.imageViewFrameSize;
        while ((i < imageData.length) 
            && !found)
        {
            var it = imageData[i];
            if ((it[1] <= vw)
                && (it[2] <= vh))
                found = true;
            else
                i++;
        }
        if (found)
            return imageData[i];
        // Return the smallest possible image.
        return imageData[imageData.length - 1];
    },
    
    // Set image.
    setImage: function(imageID)
    {
        var i0 = Ionflux.defaultArg(imageID, this.currentImage);
        this.currentImage = i0;
        if (this.currentImage > this.imageIndexTo)
            this.currentImage = this.imageIndexFrom;
        else
        if (this.currentImage < this.imageIndexFrom)
            this.currentImage = this.imageIndexTo;
        var imageData = this.getImageData(this.currentImage);
        var imageURL = this.imageBaseURL + '/' + imageData[0];
        this.imageView.setImage(imageURL, imageData[1], imageData[2], 
            this.imageCaptions[this.currentImage - 1]);
    },
    
    // Event handler: Select image.
    onSelectImage: function(source, imageID)
    {
        var imageURL = this.imageBaseURL + '/' + String(imageID).lpad(4, '0') 
            + '.jpg';
        this.setImage(imageID);
        this.gallery.setActive(false);
        this.imageView.fadeIn(0.8);
        this.mainBox.getViewport().setStyle({
            overflow: 'hidden'
        });
        // Bad hack.
        // this.mainBox.getViewport().scrollTop = 0;
    },
    
    // Event handler: Previous image.
    onPrevImage: function(source)
    {
        if (this.reverse)
            this.currentImage++;
        else
            this.currentImage--;
        this.setImage();
    },
    
    // Event handler: Next image.
    onNextImage: function(source)
    {
        if (this.reverse)
            this.currentImage--;
        else
            this.currentImage++;
        this.setImage();
    },
    
    onClose: function(source)
    {
        this.imageView.fadeOut(0.8);
        this.gallery.setActive(true);
        this.mainBox.getViewport().setStyle({
            overflow: 'auto'
        });
        this.mainBox.resizeUI();
    }
});

// Blog page.
Ionflux.BlogPage = Class.create({
    
    // Constructor.
    initialize: function()
    {
        // Main content box.
        this.mainBox = null;
        // Main menu item coordinates.
        this.mainMenuItemCoords = [
            [ 0, 0 ], 
            [ 0, 17 ], 
            [ 0, 34 ], 
            [ 0, 51 ], 
            [ 0, 67 ], 
            [ 0, 85 ], 
            [ 0, 102 ]
        ];
        // Header image.
        this.fluxHeader = null;
        // Main menu items.
        this.mainMenuItems = [];
        // Main menu item set.
        this.mainMenuItemSet = new Ionflux.Set();
        // Number of main menu items.
        this.numMainMenuItems = 7;
        // Main menu offset.
        this.mainMenuOffset = [ 15, 108 ];
    },
    
    // Setup UI.
    setupUI: function()
    {
        // Remove 'no javascript stuff'.
        var nojs01 = $('noJSInfoPage');
        var nojs02 = $('noJSMainMenuBlog');
        var nojs03 = $('noJSComment');
        if (nojs01 !== null)
            nojs01.remove();
        if (nojs02 !== null)
            nojs02.remove();
        if (nojs03 !== null)
            nojs03.remove();
        
        // Wrap the main box so it can be used as a widget.
        this.mainBox = new Ionflux.MainBox();
        
        // Main menu items.
        for (i = 0; i < this.numMainMenuItems; i++)
        {
            var num = ('' + (i + 1)).lpad(2, '0');
            var xi = this.mainMenuItemCoords[i][0] + this.mainMenuOffset[0];
            var yi = this.mainMenuItemCoords[i][1] + this.mainMenuOffset[1];
            it = null;
            if (i == 1)
            {
                // Active menu item.
                it = new Ionflux.Image(Ionflux.IMAGE_PATH 
                    + '/fluxmenuitem02hl.jpg', 
                    92, 16, xi, yi);
            } else
            {
                // Menu item links.
                it = new Ionflux.ImageLink(
                    Ionflux.IMAGE_PATH 
                        + '/fluxmenuitem' + num + '.jpg', 
                    Ionflux.IMAGE_PATH 
                        + '/fluxmenuitem' + num + 'hl.jpg', 
                    92, 16, xi, yi, 0.4, 0.1);
                it.setActivateHandler(this);
                it.setMouseOverHandler(this);
                it.setMouseOutHandler(this);
            }
            this.mainMenuItems.push(it);
            this.mainMenuItemSet.add(it);
            this.mainBox.add(it);
        }
        
        this.mainBox.resizeUI();
    },
    
    // Resize the UI.
    resizeUI: function()
    {
        this.mainBox.resizeUI();
    },
    
    // Setup.
    setup: function()
    {
        this.setupUI();
    },
    
    // Event handler: Mouse over.
    onMouseOver: function(source)
    {
        // Nothing here.
    },
    
    // Event handler: Mouse out.
    onMouseOut: function(source)
    {
        // Nothing here.
    },
    
    // Event handler: activate (sub-page links).
    onActivateSubPages: function(source)
    {
        // To be implemented by derived classes.
    },
    
    // Event handler: Activate.
    onActivate: function(source)
    {
        if (source == this.fluxHeader)
            // index
            window.location = '../' + Ionflux.PAGE_LOCATIONS.index;
        else
        if (source == this.mainMenuItems[0])
            // gallery
            window.location = '../' + Ionflux.PAGE_LOCATIONS.gallery;
        else
        if (source == this.mainMenuItems[2])
            // contact
            window.location = '../' + Ionflux.PAGE_LOCATIONS.contact;
        else
        if (source == this.mainMenuItems[3])
            // projects
            window.location = '../' + Ionflux.PAGE_LOCATIONS.projects;
        else
        if (source == this.mainMenuItems[4])
            // links
            window.location = '../' + Ionflux.PAGE_LOCATIONS.links;
        else
        if (source == this.mainMenuItems[5])
            // impressum
            window.location = '../' + Ionflux.PAGE_LOCATIONS.impressum;
        else
        if (source == this.mainMenuItems[6])
            // datenschutz
            window.location = '../' + Ionflux.PAGE_LOCATIONS.datenschutz;
    }
});

// Contact page.
Ionflux.ContactPage = Class.create({
    
    // Constructor.
    initialize: function()
    {
        // Main content box.
        this.mainBox = null;
        // Main menu item coordinates.
        this.mainMenuItemCoords = [
            [ 0, 0 ], 
            [ 0, 17 ], 
            [ 0, 34 ], 
            [ 0, 51 ], 
            [ 0, 67 ], 
            [ 0, 85 ], 
            [ 0, 102 ]
        ];
        // Header image.
        this.fluxHeader = null;
        // Main menu items.
        this.mainMenuItems = [];
        // Main menu item set.
        this.mainMenuItemSet = new Ionflux.Set();
        // Number of main menu items.
        this.numMainMenuItems = 7;
        // Main menu offset.
        this.mainMenuOffset = [ 50, 108 ];
    },
    
    // Setup UI.
    setupUI: function()
    {
        // Remove 'no javascript stuff'.
        var nojs01 = $('noJSInfoPage');
        var nojs02 = $('noJSMainMenuPage');
        if (nojs01 !== null)
            nojs01.remove();
        if (nojs02 !== null)
            nojs02.remove();
        
        // Wrap the main box so it can be used as a widget.
        this.mainBox = new Ionflux.MainBox();
        
        // Main menu items.
        for (i = 0; i < this.numMainMenuItems; i++)
        {
            var num = ('' + (i + 1)).lpad(2, '0');
            var xi = this.mainMenuItemCoords[i][0] + this.mainMenuOffset[0];
            var yi = this.mainMenuItemCoords[i][1] + this.mainMenuOffset[1];
            it = null;
            if (i == 2)
            {
                // Active menu item.
                it = new Ionflux.Image(Ionflux.IMAGE_PATH 
                    + '/fluxmenuitem03hl.jpg', 
                    92, 16, xi, yi);
            } else
            {
                // Menu item links.
                it = new Ionflux.ImageLink(
                    Ionflux.IMAGE_PATH 
                        + '/fluxmenuitem' + num + '.jpg', 
                    Ionflux.IMAGE_PATH 
                        + '/fluxmenuitem' + num + 'hl.jpg', 
                    92, 16, xi, yi, 0.4, 0.1);
                it.setActivateHandler(this);
                it.setMouseOverHandler(this);
                it.setMouseOutHandler(this);
            }
            this.mainMenuItems.push(it);
            this.mainMenuItemSet.add(it);
            this.mainBox.add(it);
        }
        
        this.mainBox.resizeUI();
    },
    
    // Resize the UI.
    resizeUI: function()
    {
        this.mainBox.resizeUI();
    },
    
    // Setup.
    setup: function()
    {
        this.setupUI();
    },
    
    // Event handler: Mouse over.
    onMouseOver: function(source)
    {
        // Nothing here.
    },
    
    // Event handler: Mouse out.
    onMouseOut: function(source)
    {
        // Nothing here.
    },
    
    // Event handler: activate (sub-page links).
    onActivateSubPages: function(source)
    {
        // To be implemented by derived classes.
    },
    
    // Event handler: Activate.
    onActivate: function(source)
    {
        if (source == this.fluxHeader)
            // index
            window.location = '../' + Ionflux.PAGE_LOCATIONS.index;
        else
        if (source == this.mainMenuItems[0])
            // gallery
            window.location = '../' + Ionflux.PAGE_LOCATIONS.gallery;
        else
        if (source == this.mainMenuItems[1])
            // blog
            window.location = '../' + Ionflux.PAGE_LOCATIONS.blog;
        else
        if (source == this.mainMenuItems[3])
            // projects
            window.location = '../' + Ionflux.PAGE_LOCATIONS.projects;
        else
        if (source == this.mainMenuItems[4])
            // links
            window.location = '../' + Ionflux.PAGE_LOCATIONS.links;
        else
        if (source == this.mainMenuItems[5])
            // impressum
            window.location = '../' + Ionflux.PAGE_LOCATIONS.impressum;
        else
        if (source == this.mainMenuItems[6])
            // datenschutz
            window.location = '../' + Ionflux.PAGE_LOCATIONS.datenschutz;
    }
});

// Projects page.
Ionflux.ProjectsPage = Class.create({
    
    // Constructor.
    initialize: function()
    {
        // Main content box.
        this.mainBox = null;
        // Main menu item coordinates.
        this.mainMenuItemCoords = [
            [ 0, 0 ], 
            [ 0, 17 ], 
            [ 0, 34 ], 
            [ 0, 51 ], 
            [ 0, 67 ], 
            [ 0, 85 ], 
            [ 0, 102 ]
        ];
        // Header image.
        this.fluxHeader = null;
        // Main menu items.
        this.mainMenuItems = [];
        // Main menu item set.
        this.mainMenuItemSet = new Ionflux.Set();
        // Number of main menu items.
        this.numMainMenuItems = 7;
        // Main menu offset.
        this.mainMenuOffset = [ 50, 108 ];
    },
    
    // Setup UI.
    setupUI: function()
    {
        // Remove 'no javascript stuff'.
        var nojs01 = $('noJSInfoPage');
        var nojs02 = $('noJSMainMenuPage');
        if (nojs01 !== null)
            nojs01.remove();
        if (nojs02 !== null)
            nojs02.remove();
        
        // Wrap the main box so it can be used as a widget.
        this.mainBox = new Ionflux.MainBox();
        
        // Main menu items.
        for (i = 0; i < this.numMainMenuItems; i++)
        {
            var num = ('' + (i + 1)).lpad(2, '0');
            var xi = this.mainMenuItemCoords[i][0] + this.mainMenuOffset[0];
            var yi = this.mainMenuItemCoords[i][1] + this.mainMenuOffset[1];
            it = null;
            if (i == 3)
            {
                // Active menu item.
                it = new Ionflux.Image(Ionflux.IMAGE_PATH + '/fluxmenuitem04hl.jpg', 
                    92, 16, xi, yi);
            } else
            {
                // Menu item links.
                it = new Ionflux.ImageLink(
                    Ionflux.IMAGE_PATH + '/fluxmenuitem' + num + '.jpg', 
                    Ionflux.IMAGE_PATH + '/fluxmenuitem' + num + 'hl.jpg', 
                    92, 16, xi, yi, 0.4, 0.1);
                it.setActivateHandler(this);
                it.setMouseOverHandler(this);
                it.setMouseOutHandler(this);
            }
            this.mainMenuItems.push(it);
            this.mainMenuItemSet.add(it);
            this.mainBox.add(it);
        }
        
        this.mainBox.resizeUI();
    },
    
    // Resize the UI.
    resizeUI: function()
    {
        this.mainBox.resizeUI();
    },
    
    // Setup.
    setup: function()
    {
        this.setupUI();
    },
    
    // Event handler: Mouse over.
    onMouseOver: function(source)
    {
        // Nothing here.
    },
    
    // Event handler: Mouse out.
    onMouseOut: function(source)
    {
        // Nothing here.
    },
    
    // Event handler: activate (sub-page links).
    onActivateSubPages: function(source)
    {
        // To be implemented by derived classes.
    },
    
    // Event handler: Activate.
    onActivate: function(source)
    {
        if (source == this.fluxHeader)
            // index
            window.location = Ionflux.PAGE_LOCATIONS.index;
        else
        if (source == this.mainMenuItems[0])
            // gallery
            window.location = Ionflux.PAGE_LOCATIONS.gallery;
        else
        if (source == this.mainMenuItems[1])
            // blog
            window.location = Ionflux.PAGE_LOCATIONS.blog;
        else
        if (source == this.mainMenuItems[2])
            // contact
            window.location = Ionflux.PAGE_LOCATIONS.contact;
        else
        if (source == this.mainMenuItems[4])
            // links
            window.location = Ionflux.PAGE_LOCATIONS.links;
        else
        if (source == this.mainMenuItems[5])
            // impressum
            window.location = Ionflux.PAGE_LOCATIONS.impressum;
        else
        if (source == this.mainMenuItems[6])
            // datenschutz
            window.location = Ionflux.PAGE_LOCATIONS.datenschutz;
    }
});

// Links page.
Ionflux.LinksPage = Class.create({
    
    // Constructor.
    initialize: function()
    {
        // Main content box.
        this.mainBox = null;
        // Main menu item coordinates.
        this.mainMenuItemCoords = [
            [ 0, 0 ], 
            [ 0, 17 ], 
            [ 0, 34 ], 
            [ 0, 51 ], 
            [ 0, 67 ], 
            [ 0, 85 ], 
            [ 0, 102 ]
        ];
        // Header image.
        this.fluxHeader = null;
        // Main menu items.
        this.mainMenuItems = [];
        // Main menu item set.
        this.mainMenuItemSet = new Ionflux.Set();
        // Number of main menu items.
        this.numMainMenuItems = 7;
        // Main menu offset.
        this.mainMenuOffset = [ 50, 108 ];
    },
    
    // Setup UI.
    setupUI: function()
    {
        // Remove 'no javascript stuff'.
        var nojs01 = $('noJSInfoPage');
        var nojs02 = $('noJSMainMenuPage');
        if (nojs01 !== null)
            nojs01.remove();
        if (nojs02 !== null)
            nojs02.remove();
        
        // Wrap the main box so it can be used as a widget.
        this.mainBox = new Ionflux.MainBox();
        
        // Main menu items.
        for (i = 0; i < this.numMainMenuItems; i++)
        {
            var num = ('' + (i + 1)).lpad(2, '0');
            var xi = this.mainMenuItemCoords[i][0] + this.mainMenuOffset[0];
            var yi = this.mainMenuItemCoords[i][1] + this.mainMenuOffset[1];
            it = null;
            if (i == 4)
            {
                // Active menu item.
                it = new Ionflux.Image(Ionflux.IMAGE_PATH + '/fluxmenuitem05hl.jpg', 
                    92, 16, xi, yi);
            } else
            {
                // Menu item links.
                it = new Ionflux.ImageLink(
                    Ionflux.IMAGE_PATH + '/fluxmenuitem' + num + '.jpg', 
                    Ionflux.IMAGE_PATH + '/fluxmenuitem' + num + 'hl.jpg', 
                    92, 16, xi, yi, 0.4, 0.1);
                it.setActivateHandler(this);
                it.setMouseOverHandler(this);
                it.setMouseOutHandler(this);
            }
            this.mainMenuItems.push(it);
            this.mainMenuItemSet.add(it);
            this.mainBox.add(it);
        }
        
        this.mainBox.resizeUI();
    },
    
    // Resize the UI.
    resizeUI: function()
    {
        this.mainBox.resizeUI();
    },
    
    // Setup.
    setup: function()
    {
        this.setupUI();
    },
    
    // Event handler: Mouse over.
    onMouseOver: function(source)
    {
        // Nothing here.
    },
    
    // Event handler: Mouse out.
    onMouseOut: function(source)
    {
        // Nothing here.
    },
    
    // Event handler: activate (sub-page links).
    onActivateSubPages: function(source)
    {
        // To be implemented by derived classes.
    },
    
    // Event handler: Activate.
    onActivate: function(source)
    {
        if (source == this.fluxHeader)
            // index
            window.location = Ionflux.PAGE_LOCATIONS.index;
        else
        if (source == this.mainMenuItems[0])
            // gallery
            window.location = Ionflux.PAGE_LOCATIONS.gallery;
        else
        if (source == this.mainMenuItems[1])
            // blog
            window.location = Ionflux.PAGE_LOCATIONS.blog;
        else
        if (source == this.mainMenuItems[2])
            // contact
            window.location = Ionflux.PAGE_LOCATIONS.contact;
        else
        if (source == this.mainMenuItems[3])
            // projects
            window.location = Ionflux.PAGE_LOCATIONS.projects;
        else
        if (source == this.mainMenuItems[5])
            // impressum
            window.location = Ionflux.PAGE_LOCATIONS.impressum;
        else
        if (source == this.mainMenuItems[6])
            // datenschutz
            window.location = Ionflux.PAGE_LOCATIONS.datenschutz;
    }
});

// Impressum page.
Ionflux.ImpressumPage = Class.create({
    
    // Constructor.
    initialize: function()
    {
        // Main content box.
        this.mainBox = null;
        // Main menu item coordinates.
        this.mainMenuItemCoords = [
            [ 0, 0 ], 
            [ 0, 17 ], 
            [ 0, 34 ], 
            [ 0, 51 ], 
            [ 0, 67 ], 
            [ 0, 85 ], 
            [ 0, 102 ]
        ];
        // Header image.
        this.fluxHeader = null;
        // Main menu items.
        this.mainMenuItems = [];
        // Main menu item set.
        this.mainMenuItemSet = new Ionflux.Set();
        // Number of main menu items.
        this.numMainMenuItems = 7;
        // Main menu offset.
        this.mainMenuOffset = [ 50, 108 ];
    },
    
    // Setup UI.
    setupUI: function()
    {
        // Remove 'no javascript stuff'.
        var nojs01 = $('noJSInfoPage');
        var nojs02 = $('noJSMainMenuPage');
        if (nojs01 !== null)
            nojs01.remove();
        if (nojs02 !== null)
            nojs02.remove();
        
        // Wrap the main box so it can be used as a widget.
        this.mainBox = new Ionflux.MainBox();
        
        // Main menu items.
        for (i = 0; i < this.numMainMenuItems; i++)
        {
            var num = ('' + (i + 1)).lpad(2, '0');
            var xi = this.mainMenuItemCoords[i][0] + this.mainMenuOffset[0];
            var yi = this.mainMenuItemCoords[i][1] + this.mainMenuOffset[1];
            it = null;
            if (i == 5)
            {
                // Active menu item.
                it = new Ionflux.Image(Ionflux.IMAGE_PATH + '/fluxmenuitem06hl.jpg', 
                    92, 16, xi, yi);
            } else
            {
                // Menu item links.
                it = new Ionflux.ImageLink(
                    Ionflux.IMAGE_PATH + '/fluxmenuitem' + num + '.jpg', 
                    Ionflux.IMAGE_PATH + '/fluxmenuitem' + num + 'hl.jpg', 
                    92, 16, xi, yi, 0.4, 0.1);
                it.setActivateHandler(this);
                it.setMouseOverHandler(this);
                it.setMouseOutHandler(this);
            }
            this.mainMenuItems.push(it);
            this.mainMenuItemSet.add(it);
            this.mainBox.add(it);
        }
        
        this.mainBox.resizeUI();
    },
    
    // Resize the UI.
    resizeUI: function()
    {
        this.mainBox.resizeUI();
    },
    
    // Setup.
    setup: function()
    {
        this.setupUI();
    },
    
    // Event handler: Mouse over.
    onMouseOver: function(source)
    {
        // Nothing here.
    },
    
    // Event handler: Mouse out.
    onMouseOut: function(source)
    {
        // Nothing here.
    },
    
    // Event handler: activate (sub-page links).
    onActivateSubPages: function(source)
    {
        // To be implemented by derived classes.
    },
    
    // Event handler: Activate.
    onActivate: function(source)
    {
        if (source == this.fluxHeader)
            // index
            window.location = Ionflux.PAGE_LOCATIONS.index;
        else
        if (source == this.mainMenuItems[0])
            // gallery
            window.location = Ionflux.PAGE_LOCATIONS.gallery;
        else
        if (source == this.mainMenuItems[1])
            // blog
            window.location = Ionflux.PAGE_LOCATIONS.blog;
        else
        if (source == this.mainMenuItems[2])
            // contact
            window.location = Ionflux.PAGE_LOCATIONS.contact;
        else
        if (source == this.mainMenuItems[3])
            // projects
            window.location = Ionflux.PAGE_LOCATIONS.projects;
        else
        if (source == this.mainMenuItems[4])
            // links
            window.location = Ionflux.PAGE_LOCATIONS.links;
        else
        if (source == this.mainMenuItems[6])
            // datenschutz
            window.location = Ionflux.PAGE_LOCATIONS.datenschutz;
    }
});

// Datenschutz page.
Ionflux.DatenschutzPage = Class.create({
    
    // Constructor.
    initialize: function()
    {
        // Main content box.
        this.mainBox = null;
        // Main menu item coordinates.
        this.mainMenuItemCoords = [
            [ 0, 0 ], 
            [ 0, 17 ], 
            [ 0, 34 ], 
            [ 0, 51 ], 
            [ 0, 67 ], 
            [ 0, 85 ], 
            [ 0, 102 ]
        ];
        // Header image.
        this.fluxHeader = null;
        // Main menu items.
        this.mainMenuItems = [];
        // Main menu item set.
        this.mainMenuItemSet = new Ionflux.Set();
        // Number of main menu items.
        this.numMainMenuItems = 7;
        // Main menu offset.
        this.mainMenuOffset = [ 50, 108 ];
    },
    
    // Setup UI.
    setupUI: function()
    {
        // Remove 'no javascript stuff'.
        var nojs01 = $('noJSInfoPage');
        var nojs02 = $('noJSMainMenuPage');
        if (nojs01 !== null)
            nojs01.remove();
        if (nojs02 !== null)
            nojs02.remove();
        
        // Wrap the main box so it can be used as a widget.
        this.mainBox = new Ionflux.MainBox();
        
        // Main menu items.
        for (i = 0; i < this.numMainMenuItems; i++)
        {
            var num = ('' + (i + 1)).lpad(2, '0');
            var xi = this.mainMenuItemCoords[i][0] + this.mainMenuOffset[0];
            var yi = this.mainMenuItemCoords[i][1] + this.mainMenuOffset[1];
            it = null;
            if (i == 6)
            {
                // Active menu item.
                it = new Ionflux.Image(Ionflux.IMAGE_PATH + '/fluxmenuitem07hl.jpg', 
                    92, 16, xi, yi);
            } else
            {
                // Menu item links.
                it = new Ionflux.ImageLink(
                    Ionflux.IMAGE_PATH + '/fluxmenuitem' + num + '.jpg', 
                    Ionflux.IMAGE_PATH + '/fluxmenuitem' + num + 'hl.jpg', 
                    92, 16, xi, yi, 0.4, 0.1);
                it.setActivateHandler(this);
                it.setMouseOverHandler(this);
                it.setMouseOutHandler(this);
            }
            this.mainMenuItems.push(it);
            this.mainMenuItemSet.add(it);
            this.mainBox.add(it);
        }
        
        this.mainBox.resizeUI();
    },
    
    // Resize the UI.
    resizeUI: function()
    {
        this.mainBox.resizeUI();
    },
    
    // Setup.
    setup: function()
    {
        this.setupUI();
    },
    
    // Event handler: Mouse over.
    onMouseOver: function(source)
    {
        // Nothing here.
    },
    
    // Event handler: Mouse out.
    onMouseOut: function(source)
    {
        // Nothing here.
    },
    
    // Event handler: activate (sub-page links).
    onActivateSubPages: function(source)
    {
        // To be implemented by derived classes.
    },
    
    // Event handler: Activate.
    onActivate: function(source)
    {
        if (source == this.fluxHeader)
            // index
            window.location = Ionflux.PAGE_LOCATIONS.index;
        else
        if (source == this.mainMenuItems[0])
            // gallery
            window.location = Ionflux.PAGE_LOCATIONS.gallery;
        else
        if (source == this.mainMenuItems[1])
            // blog
            window.location = Ionflux.PAGE_LOCATIONS.blog;
        else
        if (source == this.mainMenuItems[2])
            // contact
            window.location = Ionflux.PAGE_LOCATIONS.contact;
        else
        if (source == this.mainMenuItems[3])
            // projects
            window.location = Ionflux.PAGE_LOCATIONS.projects;
        else
        if (source == this.mainMenuItems[4])
            // links
            window.location = Ionflux.PAGE_LOCATIONS.links;
        else
        if (source == this.mainMenuItems[5])
            // impressum
            window.location = Ionflux.PAGE_LOCATIONS.impressum;
    }
});

// Old stuff page.
Ionflux.OldStuffPage = Class.create({
    
    // Constructor.
    initialize: function()
    {
        // Main content box.
        this.mainBox = null;
        // Main menu item coordinates.
        this.mainMenuItemCoords = [
            [ 0, 0 ], 
            [ 0, 17 ], 
            [ 0, 34 ], 
            [ 0, 51 ], 
            [ 0, 67 ], 
            [ 0, 85 ], 
            [ 0, 102 ]
        ];
        // Header image.
        this.fluxHeader = null;
        // Main menu items.
        this.mainMenuItems = [];
        // Main menu item set.
        this.mainMenuItemSet = new Ionflux.Set();
        // Number of main menu items.
        this.numMainMenuItems = 7;
        // Main menu offset.
        this.mainMenuOffset = [ 50, 108 ];
    },
    
    // Setup UI.
    setupUI: function()
    {
        // Remove 'no javascript stuff'.
        var nojs01 = $('noJSInfoPage');
        var nojs02 = $('noJSMainMenuPage');
        if (nojs01 !== null)
            nojs01.remove();
        if (nojs02 !== null)
            nojs02.remove();
        
        // Wrap the main box so it can be used as a widget.
        this.mainBox = new Ionflux.MainBox();
        
        // Main menu items.
        for (i = 0; i < this.numMainMenuItems; i++)
        {
            var num = ('' + (i + 1)).lpad(2, '0');
            var xi = this.mainMenuItemCoords[i][0] + this.mainMenuOffset[0];
            var yi = this.mainMenuItemCoords[i][1] + this.mainMenuOffset[1];
            it = null;
            // Menu item links.
            it = new Ionflux.ImageLink(
                Ionflux.IMAGE_PATH + '/fluxmenuitem' + num + '.jpg', 
                Ionflux.IMAGE_PATH + '/fluxmenuitem' + num + 'hl.jpg', 
                92, 16, xi, yi, 0.4, 0.1);
            it.setActivateHandler(this);
            it.setMouseOverHandler(this);
            it.setMouseOutHandler(this);
            this.mainMenuItems.push(it);
            this.mainMenuItemSet.add(it);
            this.mainBox.add(it);
        }
        
        this.mainBox.resizeUI();
    },
    
    // Resize the UI.
    resizeUI: function()
    {
        this.mainBox.resizeUI();
    },
    
    // Setup.
    setup: function()
    {
        this.setupUI();
    },
    
    // Event handler: Mouse over.
    onMouseOver: function(source)
    {
        // Nothing here.
    },
    
    // Event handler: Mouse out.
    onMouseOut: function(source)
    {
        // Nothing here.
    },
    
    // Event handler: activate (sub-page links).
    onActivateSubPages: function(source)
    {
        // To be implemented by derived classes.
    },
    
    // Event handler: Activate.
    onActivate: function(source)
    {
        if (source == this.fluxHeader)
            // index
            window.location = Ionflux.PAGE_LOCATIONS.index;
        else
        if (source == this.mainMenuItems[0])
            // gallery
            window.location = Ionflux.PAGE_LOCATIONS.gallery;
        else
        if (source == this.mainMenuItems[1])
            // blog
            window.location = Ionflux.PAGE_LOCATIONS.blog;
        else
        if (source == this.mainMenuItems[2])
            // contact
            window.location = Ionflux.PAGE_LOCATIONS.contact;
        else
        if (source == this.mainMenuItems[3])
            // projects
            window.location = Ionflux.PAGE_LOCATIONS.projects;
        else
        if (source == this.mainMenuItems[4])
            // links
            window.location = Ionflux.PAGE_LOCATIONS.links;
        else
        if (source == this.mainMenuItems[5])
            // impressum
            window.location = Ionflux.PAGE_LOCATIONS.impressum;
        else
        if (source == this.mainMenuItems[6])
            // datenschutz
            window.location = Ionflux.PAGE_LOCATIONS.datenschutz;
    }
});

// Main application.
Ionflux.FluxApp = Class.create({
    
    // Constructor.
    initialize: function()
    {
        // Application name.
        this.appName = "Ionflux.org";
        // Application version number.
        this.version = "3";
        // The info box element.
        this.infoBox = null;
        // Height of the info box.
        this.infoBoxHeight = 80;
        // Index of the first info item.
        this.firstInfoIndex = 0;
        // Index of the last info item.
        this.lastInfoIndex = 0;
        // Maximum number of info items to keep in the info box.
        this.maxNumInfo = 100;
        // The page ID.
        this.pageID = null;
        // The page.
        this.page = null;
    },
    
    // Log an item to the info box.
    log: function(msg, source, level)
    {
        if (typeof(level) == 'undefined')
            level = Ionflux.MSG_INFO;
        if (level < Ionflux.logLevel)
            return;
        var e0 = new Element('div', {
            id: 'infoItem' + this.lastInfoIndex
        });
        e0.addClassName('infoItem');
        var msgPrefix = '';
        if (typeof(source) != 'undefined')
            msgPrefix += ('[' + source + '] ');
        if (level == Ionflux.MSG_DEBUG)
            msgPrefix += 'DEBUG: ';
        else 
        if (level == Ionflux.MSG_WARNING)
            msgPrefix += 'WARNING: ';
        else 
        if (level == Ionflux.MSG_ERROR)
            msgPrefix += 'ERROR: ';
        e0.insert(msgPrefix + msg);
        this.infoBox.insert({ top: e0 });
        this.lastInfoIndex++;
        // Clean up old messages.
        if (this.lastInfoIndex - this.firstInfoIndex > this.maxNumInfo)
        {
            var newFirstInfoIndex = this.lastInfoIndex - this.maxNumInfo + 1;
            for (var i = this.firstInfoIndex; i < newFirstInfoIndex; i++)
            {
                var e1 = $('infoItem' + i);
                if (e1 !== null)
                    e1.remove();
            }
            this.firstInfoIndex = newFirstInfoIndex;
        }
    },
    
    // Resize the UI.
    resizeUI: function()
    {
        var vh = document.viewport.getHeight();
        var vw = document.viewport.getWidth();
        if (Ionflux.DEBUG)
        {
            var infoBoxTop = vh - this.infoBoxHeight - 2;
            this.infoBox.setStyle({
                top: "" + infoBoxTop + "px", 
                left: "0px", 
                width: "" + (vw - 2) + "px", 
                height: "" + this.infoBoxHeight + "px"
            });
        }
        if (this.page !== null)
            this.page.resizeUI();
    },
    
    // Get the page ID.
    getPageID: function()
    {
        var e0 = $('pageID');
        if (e0 === null)
        {
            this.pageID = null;
            return;
        }
        this.pageID = e0.innerHTML;
        /* <---- DEBUG ----- //
        Ionflux.log("pageID = " + this.pageID, "FluxApp.pageID");
        // ----- DEBUG ----> */
        e0.remove();
    },
    
    // Setup logging.
    setupLogging: function()
    {
        Ionflux.log = this.log.bind(this);
        if (Ionflux.DEBUG)
            Ionflux.logLevel = Ionflux.MSG_DEBUG;
    },
    
    // Setup the user interface.
    setupUI: function()
    {
        var main = $('main');
        if (main === null)
            throw new Error("[FluxApp.setupUI] Main content box is missing!");
        this.infoBox = new Element('div', {
            id: 'infoBox'
        });
        main.insert(this.infoBox);
        if (!Ionflux.DEBUG)
            this.infoBox.hide();
        this.resizeUI();
    },
    
    // Setup event handlers.
    setupEventHandlers: function()
    {
        Event.observe(window, 'resize', 
            this.resizeUI.bindAsEventListener(this));
    },
    
    // Setup page.
    setupPage: function()
    {
        this.getPageID();
        if (this.pageID == 'main')
            this.page = new Ionflux.MainPage();
        else
        if (this.pageID == 'gallery')
            this.page = new Ionflux.GalleryPageFinished();
        else
        if (this.pageID == 'gallery_sketches')
            this.page = new Ionflux.GalleryPageSketches();
        else
        if (this.pageID == 'gallery_3d')
            this.page = new Ionflux.GalleryPage3D();
        else
        if (this.pageID == 'gallery_image')
            window.location = 'gallery.html';
        else
        if (this.pageID == 'blog')
            this.page = new Ionflux.BlogPage();
        else
        if (this.pageID == 'contact')
            this.page = new Ionflux.ContactPage();
        else
        if (this.pageID == 'projects')
            this.page = new Ionflux.ProjectsPage();
        else
        if (this.pageID == 'links')
            this.page = new Ionflux.LinksPage();
        else
        if (this.pageID == 'impressum')
            this.page = new Ionflux.ImpressumPage();
        else
        if (this.pageID == 'datenschutz')
            this.page = new Ionflux.DatenschutzPage();
        else
        if (this.pageID == 'oldstuff')
            this.page = new Ionflux.OldStuffPage();
        if (this.page !== null)
            this.page.setup();
    },
    
    // Application setup.
    setup: function()
    {
        this.setupLogging();
        this.setupEventHandlers();
        this.setupUI();
        Ionflux.log(this.appName + " " + this.version + " ready.");
        this.setupPage();
    }
});

