/*global window, ActiveXObject, init, console*/
/*jslint white: true, browser: true, evil: true, undef: true, nomen: true, eqeqeq: true, plusplus: true, bitwise: true, regexp: true, strict: true, newcap: true, immed: true, indent: 2*/

'use strict';

/**
 * @author bevey.org
 * @fileoverview Bevey basic JS framework / toolkit.
 * @note This was developed for self-study.  Use YUI or JQuery if you want
 *        something decent.
 */
var bevey = bevey || {
  version : '6.21.10',
  cssRoot : '/css/',
  debug : true,

 /**
  * Insert the given element as the first child of the parent element passed.
  *
  * @param {Object} elm DOM element to be inserted.
  * @param {Object} parent DOM element that will become the parent to elm.
  */
  insertFirst : function (elm, parent) {
    parent.insertBefore(elm, parent.firstChild);
  },

 /**
  * Stops event bubbling further.
  *
  * @param {Event} e Event to prevent from bubbling further.
  */
  cancelBubble : function (e) {
    e = e || window.event;

    e.cancelBubble = true;

    if (e.stopPropagation) {
      e.stopPropagation();
    }
  },

 /**
  * Cancels a link or form submission default event.  If the element passed is
  *  not a form or anchor, all child anchors within the parent will have their
  *  events bubbled into a canceling event.
  *
  * @param {Object} elm DOM element to have the link event cancelled.
  * @return {Boolean} false.
  */
  cancelLink : function (elm) {
    var tagName = elm.nodeName.toLowerCase(),
        event;

    if (tagName === 'a') {
      event = 'click';
    }

    else if (tagName === 'form') {
      event = 'submit';
    }

    if (event) {
      bevey.event.add(elm, event, function (e) {
        if (e.preventDefault) {
          e.preventDefault();
        }

        return false;
      });
    }

    else {
      bevey.event.add(elm, 'click', function (e) {
        var elm = bevey.getTarget(e);

        if (elm.nodeName.toLowerCase() !== 'a') {
          elm = bevey.getTarget(e);
        }

        if (e.preventDefault) {
          e.preventDefault();
        }

        return false;
      });
    }
  },

 /**
  * Determines if an element is an ancestor to another element.
  *
  * @param {Object} child DOM element to check if it is an ancestor of the
  *         ancestor element passed.
  * @param {Object} ancestor DOM element of potential ancestor node to the
  *         child element passed.
  * @return {Boolean} true if the child is an ancestor of the ancestor element
  *          passed - false, otherwise.
  */
  isChildOf : function (child, ancestor) {
    if (ancestor === child) {
      return false;
    }

    while ((child) && (child !== ancestor) && (child !== document.body)) {
      child = child.parentNode;
    }

    return child === ancestor;
  },

  event : {
    list : [],

   /**
    * Handles event attachment via the best method availalbe.
    *
    * @param {Object} elm Element to have the event attached to.
    * @param {String} event Event to trigger.  Options include all standard
    *         events, minus the "on" prefix (ex: "click", "dblclick", etc).
    *         Additionally, "mouseenter" and "mouseleave" are supported.
    * @param {Function} action Function to be executed when the given event is
    *         triggered.
    * @param {Boolean} capture true if the event should be registered as a
    *         capturing listener.  Defaults to false.
    * @note All events are added to the bevey.event.list array for access
    *        outside this function.
    */
    add : function (elm, event, action, capture) {
      capture = capture || false;

      var mouseEnter = function (action) {
        return function (e) {
          var target = e.relatedTarget;

          if ((this === target) || (bevey.isChildOf(target, this))) {
            return;
          }

          action.call(this, event);
        };
      };

      if (elm.addEventListener) {
        if (event === 'mouseenter') {
          action = mouseEnter(action);
          elm.addEventListener('mouseover', action, capture);
          event = 'mouseover';
        }

        else if (event === 'mouseleave') {
          action = mouseEnter(action);
          elm.addEventListener('mouseout', action, capture);
          event = 'mouseout';
        }

        else {
          elm.addEventListener(event, action, capture);
        }
      }

      else if (elm.attachEvent) {
        elm.attachEvent('on' + event, action);
      }

      else {
        elm['on' + event] = action;
      }

      bevey.event.list.push([elm, event, action]);
    },

   /**
    * Removes events attached to a given element.
    *
    * @param {Object} elm Element to have the event removed from.
    * @param {String} event Event to trigger.  Options include all standard
    *         events, minus the "on" prefix (ex: "click", "dblclick", etc).  If
    *         the events "mouseenter" and "mouseleave" are to be removed - you
    *         must specify their native events "mouseover" and "mouseout".
    * @param {Function} action Function to be removed from the given element and
    *         event.
    * @param {Boolean} capture true if the event was registered as a capturing
    *         listener.  Defaults to false.
    * @note Automatically removes the event from the bevey.event.list array.
    */
    remove : function (elm, event, action, capture) {
      capture = capture || false;

      var i = 0;

      if (event === 'mouseenter') {
        event = 'mouseover';
      }

      else if (event === 'mouseleave') {
        event = 'mouseout';
      }

      if (elm.removeEventListener) {
        elm.removeEventListener(event, action, capture);
      }

      else if (elm.detachEvent) {
        elm.detachEvent('on' + event, action);
      }

      else {
        elm['on' + event] = null;
      }

      for (i; i < bevey.event.list.length; i += 1) {
        if (bevey.event.list[i]) {
          if ((bevey.event.list[i]) &&
              (bevey.event.list[i][0] === elm) &&
              (bevey.event.list[i][1] === event) &&
              (bevey.event.list[i][2] === action)) {
            bevey.event.list.splice(i, 1);

            break;
          }
        }
      }
    },

   /**
    * Loops through all registered events (referencing the bevey.event.list
    *  array) and removes all events.  This should only be executed onunload to
    *  prevent documented IE6 memory leaks.
    */
    removeAll : function (elm) {
      elm = elm || document;

      var i = bevey.event.list.length - 1;

      for (i; i >= 0 ; i -= 1) {
        if (bevey.event.list[i]) {
          if ((bevey.event.list[i]) && ((bevey.event.list[i][0] === elm) || (elm === document))) {
            bevey.event.remove(bevey.event.list[i][0], bevey.event.list[i][1], bevey.event.list[i][2]);
          }
        }
      }
    }
  },

 /**
  * Shortcut function to tie an action to a given element, giving proper scope
  *  for closure.
  *
  * @param {Object} elm Element tied to the given action.
  * @param {Function} action Function to be executed against the given element.
  * @return {Function} Function with the variable scope to execute against the
  *          given element.
  */
  closure : function (elm, action) {
    return function (e) {
      action(elm);
    };
  },

 /**
  * Shortcut function used to quickly find the target of an event (used in
  *  event delegation).
  *
  * @param {Event} e Event to determine the target of.
  * @return {Object} Element that was the target of the specified event.
  */
  getTarget : function (e) {
    e = e || window.event;

    if (e.target) {
      return e.target;
    }

    else {
      return e.srcElement;
    }
  },

 /**
  * Looks for a given attribute with a specified value within the given
  *  element.
  *
  * @param {Object} elm Element to check for a given attribute.
  * @param {String} attribute Attribute being checked.
  * @param {String} value Value of the attribute specifically being checked.
  * @return {Boolean} true if the given attribute and value is found within the
  *          element.
  */
  hasAttribute : function (elm, attribute, value) {
    if (elm[attribute]) {
      return elm[attribute].match(new RegExp('(\\s|^)' + value + '(\\s|$)'));
    }
  },

 /**
  * Sugar function used to find if a given element has the 'className'
  *  attribute specified.
  *
  * @param {Object} elm Element to check for a given class name.
  * @param {String} className Class name being checked.
  */
  hasClass : function (elm, className) {
    return bevey.hasAttribute(elm, 'className', className);
  },

 /**
  * Add the specified class to the given element - but only if it does not
  *  already have the class.
  *
  * @param {Object} elm Element to apply the given class to.
  * @param {String} className Class name to be applied.
  */
  addClass : function (elm, className) {
    if (!bevey.hasClass(elm, className)) {
      elm.className = bevey.trim(elm.className + ' ' + className);
    }
  },

 /**
  * Removes the specified class from the given element.
  *
  * @param {Object} elm Element to remove the given class from.
  * @param {String} className Class name to be removed.
  */
  removeClass : function (elm, className) {
    if (bevey.hasClass(elm, className)) {
      elm.className = elm.className.replace(new RegExp('(\\s|^)' + className + '(\\s|$)'), ' ');
      elm.className = bevey.trim(elm.className);
    }
  },

 /**
  * Sugar function used to add or remove a class from a given element -
  *  depending on if it already has the class applied.
  *
  * @param {Object} elm Element to have the class toggled.
  * @param {String} className Class Name to be toggled.
  */
  toggleClass : function (elm, className) {
    if (!bevey.hasClass(elm, className)) {
      bevey.addClass(elm, className);
    }

    else {
      bevey.removeClass(elm, className);
    }
  },

 /**
  * Finds all elements with the given class name.  Optionally, a tag name can
  *  specified to further refine an element search.
  *
  * @param {String} className Class name to be searched for.
  * @param {Object} parent Parent element to begin the search from.  If no
  *         element is specified, the document root will be used.
  * @param {String} tag Optionally, you may specify a tag name to further
  *         filter.
  * @return {Array} Returns an array of elements matching the entered criteria.
  * @note Uses native getElementsByClassName if available.
  */
  getElementsByClassName : function (className, parent, tag) {
    var elementsWithClass = [],
        children = [],
        i = 0,
        j = 0;

    parent = parent || document;
    tag    = tag.toLowerCase() || '*';

    if ((tag === '*') && (document.getElementsByClassName)) {
      return parent.getElementsByClassName(className);
    }

    if (parent.getElementsByClassName) {
      children = parent.getElementsByClassName(className);

      if ((tag) && (children.length)) {
        for (i in children) {
          if ((children[i].tagName) && (children[i].tagName.toLowerCase() === tag)) {
            elementsWithClass[j] = children[i];
            j += 1;
          }
        }
      }

      else {
        elementsWithClass = children;
      }
    }

    else {
      children = parent.getElementsByTagName(tag);

      for (i in children) {
        if (bevey.hasClass(children[i], className)) {
          elementsWithClass[j] = children[i];
          j += 1;
        }
      }
    }

    return elementsWithClass;
  },

 /**
  * Removes all child elements from a given parent node.
  *
  * @param {Object} elm Parent element to delete all children from.
  * @note Automatically removes events that may be attached to any child node
  *        being removed.
  */
  removeChildren : function (elm) {
    if (elm.hasChildNodes()) {
      while (elm.childNodes.length > 0) {
        bevey.event.removeAll(elm.firstChild);

        elm.removeChild(elm.firstChild);
      }
    }
  },

 /**
  * Enters text into a given element, using the best method available.  If text
  *  already exists within the element, it will be overwritten.
  *
  * @param {Object} elm Element to have text entered into.
  * @param {String} text Text that will populate the element.
  */
  putText : function (elm, text) {
    if (elm.textContent) {
      elm.textContent = text;
    }

    else if (elm.innerText) {
      elm.innerText = text;
    }

    else if (elm.text) {
      elm.text = text;
    }

    else {
      elm.innerHTML = text;
    }
  },

 /**
  * Sugar function to remove units of measure from a given string.
  *
  * @param {String} property Measurement property to have it's units removed.
  * @return {Integer} Integer value of measurement entered - but without units
  *          of measure.
  */
  stripUnits : function (property) {
    if (typeof(property) === 'string') {
      return parseInt(property.replace(new RegExp('(%|px|em)'), ''), 10);
    }

    else {
      return property;
    }
  },

 /**
  * Removes extra whitespace at the beginning or end of a given string.
  *
  * @param {String} string String of text that may have leading or trailing
  *         whitespace.
  * @return {String} String of text with leading or trailing whitespace
  *          removed.
  */
  trim : function (string) {
    string = string || '';

    return string.toString().replace(/^\s\s*/, '').replace(/\s\s*$/, '');
  },

 /**
  * Returns the value for a given variable stored in a cookie.
  *
  * @param {String} variable Variable name of a value stored in a cookie.
  * @return {String} Value stored within that cookie variable.
  */
  readCookie : function (variable) {
    var values = document.cookie.split(';'),
        value  = '',
        i      = 0;

    for (i; i < values.length; i += 1) {
      value = values[i].split('=');

      if (bevey.trim(value[0]) === variable) {
        return bevey.trim(value[1]);
      }
    }
  },

 /**
  * Loads a given external CSS or Javascript asset.
  *
  * @param {Object} asset Object containing:
  *         {String} type Type of asset being requested ("css" or
  *          "javascript").
  *         {String} address URL of the asset to be loaded.
  *         {String} media If the asset type is CSS, you may specify a valid
  *          media type ("screen", "print", "handheld", etc).  Defaults to
  *          "all".
  *         {Function} onComplete Function to be executed when an asset is done
  *          loading.  Although an onComplete event can be associated to a CSS
  *          asset loading, it will fire before the request is complete.
  */
  loadAsset : function (asset) {
    asset.type       = asset.type       || 'css';
    asset.onComplete = asset.onComplete || function () {};

    if (asset.address) {
      var head = document.getElementsByTagName('head')[0],
          file;

      if (asset.type === 'css') {
        file       = document.createElement('link');
        file.type  = 'text/css';
        file.rel   = 'stylesheet';
        file.href  = asset.address;
        file.media = asset.media || 'all';
        head.appendChild(file);
        asset.onComplete();
      }

      else if (asset.type === 'js') {
        file      = document.createElement('script');
        bevey.event.add(file, 'load', function () {
          asset.onComplete();
        });
        file.type = 'text/javascript';
        file.src  = asset.address;
        head.appendChild(file);
      }
    }
  },

 /**
  * Logs the given text to console.log if it is available.  If bevey.debug is
  *  set to true, a console will be loaded to output the log message.
  *
  * @param {String} text Message to be logged.
  * @param {String} type Optionally, you may specify the log type ("warning" or
  *         "error") for highlighting within the rendered logger.
  * @note Uses eval to executed inputted JS commands.
  */
  log : function (text, type) {
    if (bevey.debug === true) {      
      var asset,
          wrapper,
          title,
          logger,
          message,
          input,
          inputBuffer = [],
          inputIndex  = 0,
          tempBuffer  = '',
          drag;

      if (!document.getElementById('logger-console')) {
        asset = {
          type: 'css',
          address: bevey.cssRoot + 'logger.css'
        };

        bevey.loadAsset(asset);

        wrapper = document.createElement('form');
        title   = document.createElement('h4');
        logger  = document.createElement('ol');
        input   = document.createElement('input');

        wrapper.id      = 'logger-console';
        bevey.putText(title, 'Log Console');
        input.type      = 'text';

        wrapper.appendChild(title);
        wrapper.appendChild(logger);
        wrapper.appendChild(input);
        document.body.appendChild(wrapper);

        bevey.event.add(input, 'keyup', function (e) {
          if (e.keyCode === 38) {
            if (inputIndex === inputBuffer.length) {
              tempBuffer = this.value;
            }

            if (inputIndex > 0) {
              inputIndex -= 1;
              this.value = inputBuffer[inputIndex];
            }
          }

          else if (e.keyCode === 40) {
            if (inputBuffer.length > inputIndex) {
              inputIndex += 1;
              if (inputBuffer[inputIndex]) {
                this.value = inputBuffer[inputIndex];
              }

              else {
                this.value = tempBuffer;
              }
            }
          }
        });

        bevey.event.add(wrapper, 'submit', function (e) {
          if (input.value === 'clear') {
            bevey.removeChildren(logger);
          }

          else if (input.value === 'exit') {
            this.style.display = 'none';
          }

          else {
            bevey.log(input.value);

            try {
              if (window.execScript) {
                window.execScript(input.value);
              }

              else {
                window.setTimeout(input.value, 0);
              }
            }

            catch (error) {
              bevey.log(error, 'error');
            }
          }

          inputIndex = inputBuffer.length + 1;
          inputBuffer.push(input.value);
          input.value = '';

          if (e.preventDefault) {
            e.preventDefault();
          }

          return false;
        });

        drag = {
          elm: wrapper,
          dragElm: title
        };

        bevey.clickDrag(drag);
      }

      else {
        logger = document.getElementById('logger-console');
        logger.style.display = 'block';
        logger = logger.getElementsByTagName('ol')[0];
      }

      message = document.createElement('li');

      if (type) {
        bevey.addClass(message, type);
      }

      if (text.tagName) {
        bevey.event.add(message, 'mouseenter', function () {
          if (typeof(text.style.outline) === 'string') {
            text.style.outline = '3px solid #FF0000';
          }

          else {
            text.style.border = '1px solid #FF0000';
          }
        });

        bevey.event.add(message, 'mouseleave', function () {
          if (typeof(text.style.outline) === 'string') {
            text.style.outline = 'none';
          }

          else {
            text.style.border = 'none';
          }
        });
      }

      bevey.putText(message, text);
      logger.appendChild(message);
      logger.scrollTop = logger.scrollHeight;
    }

    if (typeof(console) !== 'undefined') {
      console.log(text);
    }
  },

 /**
  * Accepts a standard string RGB value ("rgb(255,255,255)") and returns an
  *  array of stripped values.  If an array is passed, it is assumed that it is
  *  already formatted correctly.
  *
  * @param {String} rgb String representation of an RGB value:
  *         "rgb(255,255,255)".  If an array is passed, it will be assumed to
  *         be pre-formatted.
  * @return {Array} Array value of integer equivalents to the inputted string
  *          RGB: [255,255,255].
  */
  findRgb : function (rgb) {
    if (typeof(rgb) === 'string') {
      rgb = rgb.replace('rgb(', '').replace(')', '');
      rgb = rgb.split(',');

      for (var i = 0; i < rgb.length; i += 1) {
        rgb[i] = parseInt(bevey.trim(rgb[i]), 10);
      }
    }

    return rgb;
  },

 /**
  * Converts an RGB value into HEX color code.
  *
  * @param {String} rgb String representation of an RGB value.  If an array is
  *         passed, it will be assumed to be pre-formatted.
  * @return {String} String HEX equivalent to the RGB entered: "#FFFFFF".
  */
  rgbToHex : function (rgb) {
    rgb = bevey.findRgb(rgb);

    for (var i = 0; i < rgb.length; i += 1) {
      rgb[i] = '0123456789ABCDEF'.charAt((rgb[i] - rgb[i] % 16) / 16) + '0123456789ABCDEF'.charAt(rgb[i] % 16);
    }

    return ('#' + rgb[0] + rgb[1] + rgb[2]);
  },

 /**
  * Converts a HEX color code into RGB value.
  *
  * @param {String} hex Hex code to be converted.
  * @return {String} String RGB equivalent to the HEX entered:
  *          "rgb(255,255,255)".
  */
  hexToRgb : function (hex) {
    var hexStr = hex.replace('#', '');

    hex = [hexStr.substring(0, 2), hexStr.substring(2, 4), hexStr.substring(4, 6)];

    return 'rgb(' + parseInt(hex[0], 16) + ', ' + parseInt(hex[1], 16) + ', ' + parseInt(hex[2], 16) + ')';
  },

 /**
  * Finds the computed value of a given CSS property.
  *
  * @param {Object} elm Element containing a CSS property.
  * @param {String} property CSS property of the element to be found.
  * @return {String} Computed CSS property value of the given element and
  *          property type.
  */
  findStyle : function (elm, property) {
    var styleValue = '';

    if (elm.currentStyle) {
      property = property.replace(/-\w/g, function (match) {
        return match.charAt(1).toUpperCase();
      });

      if ((property === 'opacity') && (elm.filters)) {
        if (!elm.style.filter) {
          return 1;
        }

        return parseInt(elm.filters.item('alpha').opacity, 10) / 100;
      }

      styleValue = elm.currentStyle[property];
    }

    else if (window.getComputedStyle) {
      styleValue = document.defaultView.getComputedStyle(elm, null).getPropertyValue(property);
    }

    else {
      return 0;
    }

    if (styleValue) {
      if ((property.indexOf('color') !== -1) &&
          (styleValue.indexOf('rgb') !== -1)) {
        styleValue = bevey.rgbToHex(styleValue);
      }

      if ((styleValue.indexOf('px') !== -1) ||
          (styleValue.indexOf('em') !== -1) ||
          (styleValue.indexOf('%')  !== -1)) {
        styleValue = bevey.stripUnits(styleValue);
      }

      if (property === 'opacity') {
        styleValue = parseFloat(styleValue, 10);
      }
    }

    return styleValue;
  },

 /**
  * Finds where on the page the user has scrolled.
  *
  * @return {Object} Object containing:
  *          {Integer} Pixel offset from the top of the page to the top of the
  *           user's scrolled viewport.
  *          {Integer} Pixel offset from the left edge of the user's scrolled
  *           viewport.
  */
  findScroll : function () {
    if (typeof(window.pageYOffset) === 'number') {
      return {
        positionX: window.pageXOffset,
        positionY: window.pageYOffset
      };
    }

    else if ((document.body) && (document.body.scrollTop)) {
      return {
        positionX: document.body.scrollWidth,
        positionY: document.body.scrollTop
      };
    }

    else if ((document.documentElement) && (document.documentElement.scrollTop)) {
      return {
        positionX: document.documentElement.scrollWidth,
        positionY: document.documentElement.scrollTop
      };
    }

    return 0;
  },

 /**
  * Finds the height of the given document.
  *
  * @return {Integer} Pixel height of the document.
  */
  findDocumentHeight : function () {
    return Math.max(Math.max(document.body.scrollHeight, document.documentElement.scrollHeight),
                    Math.max(document.body.offsetHeight, document.documentElement.offsetHeight),
                    Math.max(document.body.clientHeight, document.documentElement.clientHeight));
  },

 /**
  * Finds the pixel offset of a given element.  If the second parameter is set
  *  to true, the pixel offset will be relative to the next ancestor with
  *  binding positioning ("relative" or "absolute").
  *
  * @param {Object} elm Element to find the offset of.
  * @param {Boolean} relative Optionally, measure the pixel offset only to the
  *         first ancestor with a binding positition ("relative" or
  *         "absolute").
  * @return {Object} Object containing:
  *          {Integer} offsetX Pixel offset against the X-axis.
  *          {Integer} offsetY Pixel offset against the Y-axis.
  */
  totalOffset : function (elm, relative) {
    var offsetX = 0,
        offsetY = 0,
        parentPosition;

    if (elm.offsetParent) {
      offsetX = elm.offsetLeft;
      offsetY = elm.offsetTop;

      while (elm.offsetParent) {
        elm = elm.offsetParent;

        if (relative) {
          parentPosition = bevey.findStyle(elm, 'position');

          if ((parentPosition === 'absolute') || (parentPosition === 'relative')) {
            break;
          }
        }

        offsetX += elm.offsetLeft;
        offsetY += elm.offsetTop;
      }
    }

    return { 'offsetX' : offsetX, 'offsetY' : offsetY };
  },

 /**
  * Sets the given element's alpha transparency according to the opacity value
  *  passed.
  *
  * @param {Object} elm Element to set opacity.
  * @param {Float} opacity Opacity level - as measured in decimal percentage,
  *         ie: .5 will be 50% transparent.  .95 will be 5% transparent.
  * @note If no discoverable opacity method can be found, it will be made
  *        hidden or visible using the CSS "visibility" property.
  */
  setOpacity : function (elm, opacity) {
    if (typeof(elm.style.opacity) !== 'undefined') {
      elm.style.opacity = opacity;
    }

    else if (typeof(elm.style.MozOpacity) !== 'undefined') {
      elm.style.MozOpacity = opacity;
    }

    else if (typeof(elm.style.KhtmlOpacity) !== 'undefined') {
      elm.style.KhtmlOpacity = opacity;
    }

    else if (elm.style.filter !== 'undefined') {
      if (opacity === 1) {
        elm.style.filter = '';
      }

      else {
        elm.style.filter = 'alpha(opacity=' + Math.ceil(opacity * 100) + ')';
      }
    }

    else if (elm.style.visibility === 'hidden') {
      elm.style.visibility = 'visible';
    }

    else {
      elm.style.visibility = 'hidden';
    }
  },

 /**
  * Animates a given element from one property value to another.
  *
  * @param {Object} animation Object containing:
  *         {Object} elm Element to be animated.
  *         {Float} start Beginning style value of an animation.  If no start
  *          is defined, the current style value will be queries.
  *         {Float} end End style value of an animation.
  *         {Integer} duration Millisecond duration of the animation.
  *         {Integer} interval Time delay between animation steps.  The lower
  *          this number, the more frames are taken in animation (to the
  *          benefit of animation smoothness, but detriment of performance).
  *         {String} ease Name of the type of easing to be used.  This feature
  *          is currently not developed.  All easing will be linear.
  *         {Function} onStart Function to be executed before an animation
  *           begins.
  *         {Function} onTween Function to be executed after each step in an
  *          animation.
  *         {Function} onComplete Function to be executed after an animation
  *          completes.
  *         {Boolean} noNative Do not use native CSS animations, no matter if
  *          the browser supports it or not.
  *         {Boolean} hideIfNeeded If an animation ends with the element's
  *          animated style property hiding it (such as height, width or
  *          opacity) the element will apply "display: none" automatically.
  *          Defaults to true.
  *         {Integer} steps Calculated number of steps in an animation.  This
  *          value set internally and should not be set.
  *         {Integer/Array} stepsDelta Calculated style difference between
  *          steps.  Data type of integer - except when animating color or
  *          background-color, in which case, it will be an array of integers.
  *          This value set internally and should not be set.
  *         {Integer} stepIndex Current step in the animation sequence.  This
  *          value set internally and should not be set.
  * @note Easing is currently not available for non-CSS3 native animations and
  *        all values defined for easing will revert to simple linear
  *        animation.
  * @note For simple animations (those that do not use onTween functions),
  *        native CSS3 animations will be used if they are supported.
  */
  animate : function (animation) {
    var thisStep = 0,
        fauxStart,
        fauxEnd,
        deltas,
        findChange,
        callBack;

    findChange = function (start, step, totalSteps, delta, end, ease) {
      var change;

      change = parseFloat(start, 10) + (step * delta);

      if (step !== totalSteps) {
        if (animation.property.indexOf('color') !== -1) {
          change = Math.max(0, change);
          change = Math.min(255, change);
          change = Math.round(change);
        }

        return change;
      }

      return end;
    };

    if (!animation.stepIndex) {
      fauxStart = animation.start;
      fauxEnd   = animation.end;

      if ((animation.start <= 1) && (animation.end <= 1)) {
        fauxStart = fauxStart * 100;
        fauxEnd   = fauxEnd   * 100;
      }

      if (bevey.hasClass(animation.elm, 'animating')) {
        return false;
      }

      if (animation.start === null) {
        animation.start = bevey.findStyle(animation.elm, animation.property);
      }

      animation.elm        = animation.elm        || this;
      animation.duration   = animation.duration   || 1000;
      animation.ease       = animation.ease       || 'linear';
      animation.noNative   = animation.noNative   || false;
      animation.interval   = animation.interval   || 50;
      animation.stepIndex  = 0;
      animation.onStart    = animation.onStart    || function () {};
      animation.onComplete = animation.onComplete || function () {};

      if (animation.hideIfNeeded !== false) {
        animation.hideIfNeeded = true;
      }

      if ((!animation.onTween) && (!animation.noNative)) {
        if ((typeof(animation.elm.style['-moz-transition'])    !== 'undefined') ||
            (typeof(animation.elm.style['-ms-transition'])     !== 'undefined') ||
            (typeof(animation.elm.style['-o-transition'])      !== 'undefined') ||
            (typeof(animation.elm.style['-webkit-transition']) !== 'undefined') ||
            (typeof(animation.elm.style.transition)            !== 'undefined')) {
          animation.elm.style['-moz-transition']    = animation.property + ' ' + (animation.duration / 1000) + 's ' + animation.ease;
          animation.elm.style['-ms-transition']     = animation.property + ' ' + (animation.duration / 1000) + 's ' + animation.ease;
          animation.elm.style['-o-transition']      = animation.property + ' ' + (animation.duration / 1000) + 's ' + animation.ease;
          animation.elm.style['-webkit-transition'] = animation.property + ' ' + (animation.duration / 1000) + 's ' + animation.ease;
          animation.elm.style.transition            = animation.property + ' ' + (animation.duration / 1000) + 's ' + animation.ease;

          bevey.addClass(animation.elm, 'animating');
          animation.onStart();

          if ((animation.elm.style.display === 'none') && (animation.end > 0)) {
            animation.elm.style.display = 'block';
          }

          callBack = function () {
            bevey.removeClass(animation.elm, 'animating');

            if ((animation.hideIfNeeded) &&
                (animation.end <= 0) &&
                (animation.property !== 'left') &&
                (animation.property !== 'right') &&
                (animation.property !== 'botom') &&
                (animation.property !== 'top')) {
              animation.elm.style.display = 'none';
            }

            animation.onComplete();

            bevey.event.remove(animation.elm, 'mozTransitionEnd',    callBack);
            bevey.event.remove(animation.elm, 'webkitTransitionEnd', callBack);
            bevey.event.remove(animation.elm, 'transitionEnd',       callBack);
          };

          bevey.event.add(animation.elm, 'mozTransitionEnd',    callBack);
          bevey.event.add(animation.elm, 'webkitTransitionEnd', callBack);
          bevey.event.add(animation.elm, 'transitionEnd',       callBack);

          if (animation.property === 'opacity') {
            bevey.setOpacity(animation.elm, animation.end);
          }

          else if ((animation.property === 'background-color') ||
                   (animation.property === 'color')) {
            animation.elm.style[animation.property] = animation.end;
          }

          else {
            animation.elm.style[animation.property] = animation.end + 'px';
          }

          return;
        }
      }

      animation.onTween = animation.onTween || function () {};

      if ((animation.property.indexOf('color') !== -1)) {
        animation.start     = bevey.findRgb(bevey.hexToRgb(animation.start));
        animation.end       = bevey.findRgb(bevey.hexToRgb(animation.end));
        deltas              = [(animation.end[0] - animation.start[0]),
                               (animation.end[1] - animation.start[1]),
                               (animation.end[2] - animation.start[2])];
        animation.steps     = Math.abs(Math.floor(Math.min(Math.max(deltas[0], deltas[1], deltas[2]), Math.ceil(animation.duration / animation.interval))));
        animation.stepDelta = [Math.ceil(deltas[0] / animation.steps), Math.ceil(deltas[1] / animation.steps), Math.ceil(deltas[2] / animation.steps)];
      }

      else {
        animation.steps     = Math.floor(Math.min(Math.abs(fauxEnd - fauxStart), Math.ceil(animation.duration / animation.interval)));
        animation.stepDelta = (fauxEnd - fauxStart) / animation.steps;
      }

      if ((animation.start <= 1) && (animation.end <= 1)) {
        animation.stepDelta = animation.stepDelta / 100;
      }

      bevey.addClass(animation.elm, 'animating');

      animation.onStart();
    }

    animation.stepIndex += 1;

    thisStep = findChange(animation.start, animation.stepIndex, animation.steps, animation.stepDelta, animation.end, animation.ease);

    if (animation.stepIndex >= animation.steps) {
      animation.stepIndex = animation.steps;

      thisStep = animation.end;
    }

    if (animation.start > animation.end) {
      if (thisStep <= animation.end) {
        animation.stepIndex = animation.steps;

        thisStep = animation.end;
      }

      if ((animation.hideIfNeeded) &&
          (animation.start + (animation.stepIndex * animation.stepDelta) <= 0) &&
          (animation.property !== 'left') &&
          (animation.property !== 'right') &&
          (animation.property !== 'botom') &&
          (animation.property !== 'top')) {
        animation.elm.style.display = 'none';
      }
    }

    else {
      if (thisStep >= animation.end) {
        animation.stepIndex = animation.steps;

        thisStep = animation.end;
      }

      if ((animation.elm.style.display === 'none') && (animation.end > 0)) {
        animation.elm.style.display = 'block';
      }
    }

    switch (animation.property) {
    case 'opacity' :
      bevey.setOpacity(animation.elm, thisStep);
      break;

    case 'background-color' :
      animation.elm.style.backgroundColor = bevey.rgbToHex([findChange(animation.start[0], animation.stepIndex, animation.steps, animation.stepDelta[0], animation.end[0], animation.ease),
                                                            findChange(animation.start[1], animation.stepIndex, animation.steps, animation.stepDelta[1], animation.end[1], animation.ease),
                                                            findChange(animation.start[2], animation.stepIndex, animation.steps, animation.stepDelta[2], animation.end[2], animation.ease)]);
      break;

    case 'color' :
      animation.elm.style.color = bevey.rgbToHex([findChange(animation.start[0], animation.stepIndex, animation.steps, animation.stepDelta[0], animation.end[0], animation.ease),
                                                  findChange(animation.start[1], animation.stepIndex, animation.steps, animation.stepDelta[1], animation.end[1], animation.ease),
                                                  findChange(animation.start[2], animation.stepIndex, animation.steps, animation.stepDelta[2], animation.end[2], animation.ease)]);
      break;

    default :
      animation.elm.style[animation.property] = thisStep + 'px';
      break;
    }

    if ((animation.steps > animation.stepIndex) && (bevey.hasClass(animation.elm, 'animating'))) {
      animation.onTween();

      setTimeout(function () {
        bevey.animate(animation);
      }, animation.interval);
    }

    else {
      if (animation.property.indexOf('color') !== -1) {
        animation.stepIndex = 0;
      }

      bevey.removeClass(animation.elm, 'animating');

      animation.onComplete();
    }

    return animation;
  },

  ajax : {
    cache : [],

   /**
    * Sends an AJAX request to the specified URL.  On receipt of a response, you
    *  may define a element to populate with the raw response or define an
    *  onComplete function to process the response.
    *
    * @param {Object} ajaxRequest Object containing:
    *         {String} method Method of request ("GET" or "POST").
    *         {String} param Additional parameters.
    *         {Boolean} cache true if responses should be cached locally, false
    *          if a fresh request should be made each time.
    *         {Function} onStart Function to be executed before an AJAX request
    *          begins.
    *         {Function/Object} onComplete Function to be executed after an AJAX
    *          request completes.  If a DOM object is passed instead of a
    *          function, the raw AJAX response will populate the form or element.
    */
    request : function (ajaxRequest) {
      ajaxRequest.method     = ajaxRequest.method     || 'GET';
      ajaxRequest.onStart    = ajaxRequest.onStart    || function () {};
      ajaxRequest.onComplete = ajaxRequest.onComplete || function () {};

      if (ajaxRequest.cache !== false) {
        ajaxRequest.cache = true;
      }

      var request,
          ajaxProcess;

      ajaxProcess = function () {
        ajaxRequest.onStart();

        switch (typeof(ajaxRequest.onComplete)) {
        case 'object' :
          if (ajaxRequest.onComplete.value) {
            ajaxRequest.onComplete.value = ajaxRequest.response;
          }

          else if (ajaxRequest.onComplete.childNodes[0]) {
            bevey.putText(ajaxRequest.onComplete, ajaxRequest.response);
          }
          break;

        case 'function' :
          ajaxRequest.onComplete();
          break;
        }
      };

      if ((bevey.ajax.cache[ajaxRequest.path + '?' + ajaxRequest.param + '?'] !== undefined) && (ajaxRequest.cache)) {
        ajaxRequest.response = bevey.ajax.cache[ajaxRequest.path + '?' + ajaxRequest.param + '?'];

        ajaxProcess();

        return true;
      }

      if (window.XMLHttpRequest) {
        request = new XMLHttpRequest();
      }

      else if (window.ActiveXObject) {
        request = new ActiveXObject('Microsoft.XMLHTTP');
      }

      else {
        return false;
      }

      if (ajaxRequest.method === 'GET') {
        ajaxRequest.path  = ajaxRequest.path + '?' + ajaxRequest.param;
        ajaxRequest.param = '';
      }

      request.open(ajaxRequest.method.toUpperCase(), ajaxRequest.path, true);

      if (ajaxRequest.method === 'POST') {
        request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
      }

      request.setRequestHeader('AJAX', 'true');

      bevey.event.add(request, 'readystatechange', function () {
        if (request.readyState === 4) {
          if (request.status === 200) {
            ajaxRequest.response = request.responseText;

            if (ajaxRequest.cache) {
              bevey.ajax.cache[ajaxRequest.path + '?' + ajaxRequest.param] = ajaxRequest.response;
            }

            ajaxProcess();
          }

          else {
            return false;
          }
        }
      });

      request.send(ajaxRequest.param);
    }
  },

 /**
  * Checks all links from a given element parent for the attribute
  *  "rel="external"" and adds the attribute "target="_blank"" - as to avoid
  *  validation errors.
  *
  * @param {Object} elm Element to be searched for anchors with the rel
  *         attribute of "external".  If no element is specified, the document
  *         root will be searched.
  */
  externalLinks : function (elm) {
    elm = elm || document;

    var links = elm.getElementsByTagName('a'),
        i     = 0;

    for (i; i < links.length; i += 1) {
      if (/(?:^|\s)external(?:\s|$)/.test(links[i].rel)) {
        links[i].target = '_blank';
      }
    }
  },

 /**
  * Finds the current mouse position.
  *
  * @param {Event} e Mouse event.
  * @return {Object} position Object containing:
  *          {Integer} positionX Mouse offset on X-axis.
  *          {Integer} positionY Mouse offset on Y-axis.
  */
  findMousePosition : function (e) {
    if (window.event) {
      return {
        positionX: event.clientX,
        positionY: event.clientY
      };
    }

    else {
      return {
        positionX: e.clientX,
        positionY: e.clientY
      };
    }
  },

 /**
  * Creates a dragable element with optional drop points.
  *
  * @param {Object} drag Object containing:
  *         {Object} elm Element to be movable.
  *         {Object} dragElm Element that will act as the drag focus.  If none
  *          is specified, it will default to drag.elm.
  *         {Boolean} restrict true if the element should be restricted to
  *          movement only within it's direct parent.
  *         {Function} onStart Function to be executed when the element first
  *          gets clicked.
  *         {Function} onTween Function to be executed after each step in the
  *          element's movement.
  *         {Function} onComplete Function to be executed after the element is
  *          done moving (mouseup).
  *         {Function} onDrop Function to be executed after an element has been
  *          dropped into a specified drop area.
  *         {Array} dropElm Array of elements that are drop areas for the
  *          dragable element.
  */
  clickDrag : function (drag) {
    drag.dragElm    = drag.dragElm    || drag.elm;
    drag.restrict   = drag.restrict   || false;
    drag.onStart    = drag.onStart    || function () {};
    drag.onTween    = drag.onTween    || function () {};
    drag.onComplete = drag.onComplete || function () {};
    drag.onDrop     = drag.onDrop     || function () {};

    var mover,
        dropper,
        dropable,
        wrapperBorderOffsetX = bevey.findStyle(drag.elm.parentNode, 'border-left-width') + bevey.findStyle(drag.elm.parentNode, 'border-right-width'),
        wrapperBorderOffsetY = bevey.findStyle(drag.elm.parentNode, 'border-top-width')  + bevey.findStyle(drag.elm.parentNode, 'border-bottom-width');

   /**
    * @private
    */
    mover = function (e) {
      if (bevey.hasClass(drag.elm, 'active')) {
        bevey.cancelBubble(e);

        if (e.preventDefault) {
          e.preventDefault();
        }

        var position  = bevey.findMousePosition(e),
            positionX = position.positionX,
            positionY = position.positionY,
            width     = drag.dragElm.offsetWidth,
            height    = drag.dragElm.offsetHeight,
            endX,
            endY;

        drag.newX = positionX - drag.clickOffsetX + drag.startOffsetX - (drag.startWidth  - bevey.findScroll().positionX);
        drag.newY = positionY - drag.clickOffsetY + drag.startOffsetY - (drag.startHeight - bevey.findScroll().positionY);

        if (drag.restrict) {
          endX = drag.elm.parentNode.offsetWidth  - width  - wrapperBorderOffsetX;
          endY = drag.elm.parentNode.offsetHeight - height - wrapperBorderOffsetY;

          if (drag.newX > endX) {
            drag.newX = endX;
          }

          if (drag.newX < 0) {
            drag.newX = 0;
          }

          if (drag.newY > endY) {
            drag.newY = endY;
          }

          if (drag.newY < 0) {
            drag.newY = 0;
          }
        }

        if (drag.dropElm) {
          dropable(drag);
        }

        drag.elm.style.margin = 0;
        drag.elm.style.left   = drag.newX + 'px';
        drag.elm.style.top    = drag.newY + 'px';

        drag.onTween();

        return false;
      }
    };

   /**
    * @private
    */
    dropper = function (e) {
      if (bevey.hasClass(drag.elm, 'active')) {
        bevey.removeClass(drag.elm, 'active');

        if (drag.dropElm) {
          dropable(drag, true);
        }

        bevey.event.remove(document, 'mousemove', mover);
        bevey.event.remove(document, 'mouseup',   dropper);

        drag.onComplete();
      }
    };

   /**
    * @private
    */
    dropable = function (drag, drop) {
      var i = 0,
          temp,
          dropBox,
          offset,
          dropX,
          dropY,
          dragWidth,
          dragHeight,
          dropWidth,
          dropHeight,
          canDrop = false;

      if (drag.dropElm.length === undefined) {
        temp            = drag.dropElm;
        drag.dropElm    = [];
        drag.dropElm[0] = temp;

        temp = drag.onDrop;
        drag.onDrop    = [];
        drag.onDrop[0] = temp;
      }

      for (i; i < drag.dropElm.length; i += 1) {
        dropBox = drag.dropElm[i];
        offset  = bevey.totalOffset(dropBox, true);
        dropX   = offset.offsetX;
        dropY   = offset.offsetY;

        dragWidth  = drag.elm.offsetWidth;
        dragHeight = drag.elm.offsetHeight;
        dropWidth  = dropBox.offsetWidth;
        dropHeight = dropBox.offsetHeight;

        if (((drag.newX > parseInt(dropX - 30, 10)) && (drag.newX < parseInt(dropX + dropBox.offsetWidth  - 10, 10))) &&
            ((drag.newY > parseInt(dropY - 30, 10)) && (drag.newY < parseInt(dropY + dropBox.offsetHeight - 10, 10)))) {
          canDrop = true;
          drag.activeDropBox = dropBox;
          bevey.addClass(dropBox, 'active');

          drag.newX = dropX;
          drag.newY = dropY;

          if (dropWidth > dragWidth) {
            drag.newX += (dropWidth - dragWidth) / 2;
          }

          if (dropHeight > dragHeight) {
            drag.newY += (dropHeight - dragHeight) / 2;
          }

          if (drop) {
            bevey.removeClass(dropBox, 'active');

            drag.onDrop[i](drag.elm);
          }
        }

        else {
          bevey.removeClass(dropBox, 'active');
        }
      }

      if (canDrop === true) {
        bevey.addClass(drag.elm, 'dropable');
      }

      else {
        bevey.removeClass(drag.elm, 'dropable');
      }
    };

    bevey.event.add(drag.dragElm, 'mousedown', function (e) {
      var startOffset = bevey.findMousePosition(e);

      bevey.clickDrag.zindex  = bevey.clickDrag.zindex || 99;

      bevey.cancelBubble(e);
      drag.clickOffsetX       = startOffset.positionX;
      drag.clickOffsetY       = startOffset.positionY;
      drag.startOffsetX       = drag.elm.offsetLeft;
      drag.startOffsetY       = drag.elm.offsetTop;
      drag.startWidth         = bevey.findScroll().positionX;
      drag.startHeight        = bevey.findScroll().positionY;
      drag.elm.style.zIndex   = bevey.clickDrag.zindex += 1;
      drag.elm.style.margin   = 0;
      drag.elm.style.bottom   = 'auto';
      drag.elm.style.right    = 'auto';
      drag.elm.style.position = 'absolute';
      bevey.addClass(drag.elm, 'active');

      drag.onStart();

      if (e.preventDefault) {
        e.preventDefault();
      }

      bevey.event.add(document, 'mousemove', mover);
      bevey.event.add(document, 'mouseup',   dropper);

      mover(e);
    });
  },

 /**
  * Creates a dragable element within a parent element that will be used to
  *  resize the parent.
  *
  * @param {Object} resize Object containing:
  *         {Object} elm Element to be resizable.
  *         {Function} onStart Function to be executed when the element first
  *          gets clicked.
  *         {Function} onComplete Function to be executed after the element is
  *          done moving (mouseup).
  */
  resize : function (resize) {
    var restrictParent       = resize.restrict,
        dragElm              = document.createElement('div'),
        parentBorderOffsetX  = bevey.findStyle(resize.elm, 'border-left-width') + bevey.findStyle(resize.elm, 'border-right-width'),
        parentBorderOffsetY  = bevey.findStyle(resize.elm, 'border-top-width') + bevey.findStyle(resize.elm, 'border-bottom-width'),
        wrapperBorderOffsetX = bevey.findStyle(resize.elm.parentNode, 'border-left-width') + bevey.findStyle(resize.elm.parentNode, 'border-right-width'),
        wrapperBorderOffsetY = bevey.findStyle(resize.elm.parentNode, 'border-top-width') + bevey.findStyle(resize.elm.parentNode, 'border-bottom-width'),
        parentHeight         = resize.elm.offsetHeight,
        parentWidth          = resize.elm.offsetWidth;

    resize.restrict   = false;
    resize.onStart    = resize.onStart    || function () {};
    resize.onComplete = resize.onComplete || function () {};

    resize.elm.appendChild(dragElm);

    dragElm.style.left = parentWidth  - dragElm.offsetWidth  - parentBorderOffsetX + 'px';
    dragElm.style.top  = parentHeight - dragElm.offsetHeight - parentBorderOffsetY + 'px';
    resize.elm         = dragElm;

    resize.onTween = function () {
      var containerWidth  = resize.elm.parentNode.parentNode.offsetWidth,
          containerHeight = resize.elm.parentNode.parentNode.offsetHeight,
          width  = bevey.stripUnits(resize.elm.style.left) + dragElm.offsetWidth,
          height = bevey.stripUnits(resize.elm.style.top)  + dragElm.offsetHeight,
         parentLeft = resize.elm.parentNode.offsetLeft,
         parentTop  = resize.elm.parentNode.offsetTop;

      if ((width > dragElm.offsetWidth) && ((!restrictParent) || ((width < (containerWidth - parentLeft - parentBorderOffsetX - wrapperBorderOffsetX))))) {
        resize.elm.parentNode.style.width = width + 'px';
      }

      else if (width >= (containerWidth - parentLeft - parentBorderOffsetX - wrapperBorderOffsetX)) {
        resize.elm.parentNode.style.width = resize.elm.parentNode.parentNode.offsetWidth - resize.elm.parentNode.offsetLeft - parentBorderOffsetX - wrapperBorderOffsetX + 'px';
        resize.elm.style.left = resize.elm.parentNode.offsetWidth - dragElm.offsetWidth - parentBorderOffsetX + 'px';
      }

      if ((height > dragElm.offsetHeight) && ((!restrictParent) || (height < (containerHeight - parentTop - parentBorderOffsetY - wrapperBorderOffsetY)))) {
        resize.elm.parentNode.style.height = height + 'px';
      }

      else if (height >= (containerHeight - parentTop - parentBorderOffsetY - wrapperBorderOffsetY)) {
        resize.elm.parentNode.style.height = resize.elm.parentNode.parentNode.offsetHeight - resize.elm.parentNode.offsetTop - parentBorderOffsetY - wrapperBorderOffsetY + 'px';
        dragElm.style.top = resize.elm.parentNode.offsetHeight - dragElm.offsetHeight - parentBorderOffsetY + 'px';
      }

      if (width <= dragElm.offsetWidth) {
        resize.elm.parentNode.style.width = dragElm.offsetWidth + 'px';
        dragElm.style.left = 0;
      }

      if (height <= dragElm.offsetHeight) {
        resize.elm.parentNode.style.height = dragElm.offsetHeight + 'px';
        dragElm.style.top = 0;
      }
    };

    bevey.clickDrag(resize);
  },

  rotator : {
   /**
    * Animated transitions between a series of images.
    *
    * @param {Object} rotator Object containing:
    *         {Object} elm Element to contain the transitioned images.
    *         {Array} photos Array of images to be rotated through.
    *         {Integer} index Current image being viewed.
    *         {Integer} last Last image that has been viewed.  Should not be
    *          modified by user.
    *         {Boolean} wrap true if the rotator should begin at the first image
    *          once the last image has been reached.
    *         {Boolean} bounce true if the rotator should begin decending through
    *          the photo list once the last image has been reached.
    *         {Function} onComplete Function to be executed after the image
    *          transitions.
    */
    rotate : function (rotator) {
      rotator.onComplete = rotator.onComplete || function () {};

      var imageFetch,
          animation;

      if (rotator.index === undefined) {
        rotator.index = 0;
        rotator.last  = 0;
      }

      if ((rotator.wrap) && ((rotator.index + 1) >= rotator.photos.length)) {
        rotator.last  = rotator.index;
        rotator.index = 0;
      }

      else if (rotator.wrap) {
        rotator.last = rotator.index;
        rotator.index += 1;
      }

      else if (((rotator.bounce) && (rotator.index === 0)) ||
              ((rotator.bounce) && ((rotator.index + 1) < rotator.photos.length) && (rotator.index >= rotator.last)) ||
              ((rotator.direction === 'next') && ((rotator.index + 1) < rotator.photos.length))) {
        rotator.last = rotator.index;
        rotator.index += 1;

        if ((rotator.index + 1) < rotator.photos.length) {
          imageFetch = new Image();
          imageFetch.index = rotator.index + 1;
        }
      }

      else if (((rotator.bounce) ||
                (rotator.direction === 'prev')) && (rotator.index > 0)) {
        rotator.last = rotator.index;
        rotator.index -= 1;

        if (rotator.index > 0) {
          imageFetch = new Image();
          imageFetch.index = rotator.index - 1;
        }
      }

      else {
        return false;
      }

      if (imageFetch) {
        if (typeof(rotator.photos[imageFetch.index]) === 'object') {
          imageFetch.src = rotator.photos[imageFetch.index].src;
        }

        else {
          imageFetch.src = rotator.photos[imageFetch.index];
        }
      }

      animation = {
        elm        : rotator.elm,
        duration   : 250,
        start      : 1,
        end        : 0,
        property   : 'opacity',
        onComplete : function () {
          rotator.elm.style.display = 'block';

          if (typeof(rotator.photos[rotator.index]) === 'object') {
            rotator.elm.src = rotator.photos[rotator.index].src;
            rotator.elm.alt = rotator.photos[rotator.index].alt;
          }

          else {
            if (rotator.alt) {
              rotator.elm.alt = rotator.alt[rotator.index];
            }

            rotator.elm.src = rotator.photos[rotator.index];
          }

          animation  = {
            elm      : rotator.elm,
            duration : 250,
            start    : 0,
            end      : 1,
            property : 'opacity'
          };

          setTimeout(function () {
            bevey.animate(animation);
          }, 250);

          rotator.onComplete();
        }
      };

      bevey.animate(animation);
    },

   /**
    * Transitions through a set of images automatically, pausing when a user
    *  mouses over the image.  A simple slide-show.
    *
    * @param {Object} rotator Object containing:
    *         {Object} elm Element to contain the transitioned images.
    *         {Array} photos Array of images to be rotated through.
    *         {Integer} interval Time delay of each image.  Defaults to 5000 ms.
    *         {Integer} index Current image being viewed.
    *         {Integer} autoIndex Current step in the image rotation.  Should not
    *          be modified by user.
    *         {Boolean} wrap true if the rotator should begin at the first image
    *          once the last image has been reached.
    *         {Boolean} bounce true if the rotator should begin decending through
    *          the photo list once the last image has been reached.
    *         {Boolean} pause If set to true, the rotator will pause on the
    *          current image.
    *         {Function} onComplete Function to be executed after the image
    *          transitions.
    */
    auto : function (rotator) {
      rotator.interval  = rotator.interval  || 5000;
      rotator.index     = rotator.index     || 0;
      rotator.autoIndex = rotator.autoIndex || 0;
      rotator.bounce    = rotator.bounce    || false;
      rotator.onStart   = rotator.onStart   || function () {};
      rotator.onTween   = rotator.onTween   || function () {};

      if (rotator.wrap !== false) {
        rotator.wrap = true;
      }

      if (rotator.autoIndex === 0) {
        bevey.event.add(rotator.elm, 'mouseenter', function () {
          rotator.pause = true;
        });

        bevey.event.add(rotator.elm, 'mouseleave', function () {
          if (rotator.pause === true) {
            rotator.pause = false;
          }
        });
      }

      if ((rotator.autoIndex) && (rotator.pause !== true)) {
        bevey.rotator.rotate(rotator);
      }

      rotator.autoIndex += 1;

      setTimeout(function () {
        rotator.onStart();
        bevey.rotator.auto(rotator);
        rotator.onTween();
      }, rotator.interval);
    },

   /**
    * Transitions through a set of images according to a user's selection via a
    *  simple "next" / "previous" navigation.
    *
    * @param {Object} rotator Object containing:
    *         {Object} elm Element to contain the transitioned images.
    *         {Array} photos Array of images to be rotated through.
    *         {Integer} index Current image being viewed.
    *         {Function} onStart Function to be executed when the user clicks a
    *          navigation link.
    *         {Function} onComplete Function to be executed after the image
    *          transitions.
    */
    control : function (rotator) {
      rotator.index      = rotator.index      || 0;
      rotator.onStart    = rotator.onStart    || function () {};
      rotator.onComplete = rotator.onComplete || function () {};

      var prev,
          next,
          navAction,
          i = 0,
          linkCheck;

     /**
      * @private
      */
      navAction = function (e) {
        var elm = bevey.getTarget(e);

        if (elm.tagName.toLowerCase() === 'a') {
          if (bevey.hasClass(elm, 'prev')) {
            rotator.direction = 'prev';
          }

          else if (bevey.hasClass(elm, 'next')) {
            rotator.direction = 'next';
          }

          rotator.onStart();
          bevey.rotator.rotate(rotator);
          linkCheck();
          rotator.onComplete();
        }

        e.cancelBubble = true;

        if (e.stopPropagation) {
          e.stopPropagation();
        }

        if (e.preventDefault) {
          e.preventDefault();
        }

        return false;
      };

     /**
      * @private
      */
      linkCheck = function () {
        if (rotator.index <= 0) {
          bevey.addClass(prev, 'disabled');
          prev.href = '#';
        }

        else {
          bevey.removeClass(prev, 'disabled');

          if (typeof(rotator.photos[(rotator.index - 1)]) === 'object') {
            prev.href = rotator.photos[(rotator.index - 1)].src;
          }

          else {
            prev.href = rotator.photos[(rotator.index - 1)];
          }
        }

        if ((rotator.index + 1) >= rotator.photos.length) {
          bevey.addClass(next, 'disabled');
          next.href = '#';
        }

        else {
          bevey.removeClass(next, 'disabled');

          if (typeof(rotator.photos[(rotator.index + 1)]) === 'object') {
            next.href = rotator.photos[(rotator.index + 1)].src;
          }

          else {
            next.href = rotator.photos[(rotator.index + 1)];
          }
        }
      };

      if (rotator.elm.parentNode.getElementsByTagName('a').length) {
        prev = bevey.getElementsByClassName('prev', rotator.elm.parentNode, 'a')[0];
        next = bevey.getElementsByClassName('next', rotator.elm.parentNode, 'a')[0];

        for (i; i < bevey.event.list.length; i += 1) {
          if ((bevey.event.list[i]) &&
              (bevey.event.list[i][0] === rotator.elm.parentNode) &&
              (bevey.event.list[i][1] === 'click')) {
            bevey.event.remove(rotator.elm.parentNode, 'click', bevey.event.list[i][2]);
          }
        }

        bevey.event.add(rotator.elm.parentNode, 'click', navAction);
      }

      else {
        prev = document.createElement('a');
        next = document.createElement('a');
        bevey.addClass(prev, 'prev');
        bevey.putText(prev, 'Previous Image');
        bevey.addClass(next, 'next');
        bevey.putText(next, 'Next Image');
        rotator.elm.parentNode.appendChild(prev);
        rotator.elm.parentNode.appendChild(next);
        bevey.event.add(rotator.elm.parentNode, 'click', navAction);
      }

      linkCheck();
    }
  },

 /**
  * Creates a basic lightbox image viewer from a list of specifically formatted
  *  images and links.
  *
  * @param {Object} lightbox Object containing:
  *         {Object} elm Parent element that contains the image and link
  *          references.
  *         {Array} images Optional array of image thumbnail elements that are
  *          children of links to larger images.  If no array is specified, the
  *          elm object will be searched for all img tags.
  *         {Function} onStart Function to be executed when the user clicks an
  *          image thumbnail for viewing.
  *         {Function} onComplete Function to be executed after a user closes
  *          lightbox.
  */
  lightbox : function (lightbox) {
    lightbox.onStart    = lightbox.onStart    || function () {};
    lightbox.onComplete = lightbox.onComplete || function () {};
    lightbox.images     = lightbox.images     || lightbox.elm.getElementsByTagName('img');

    var images = [],
        image,
        full,
        i = 0,
        findSize;

    findSize = function (imageTag) {
      if ((imageTag.naturalWidth > 0) && (imageTag.naturalHeight > 0)) {
        imageTag.width                   = imageTag.naturalWidth;
        imageTag.height                  = imageTag.naturalHeight;
        imageTag.parentNode.style.width  = imageTag.width  + 'px';
        imageTag.parentNode.style.height = imageTag.height + 'px';
      }

      else if ((imageTag.complete !== false) && (imageTag.width > 0) && (imageTag.height > 0)) {
        imageTag.parentNode.style.width  = imageTag.width  + 'px';
        imageTag.parentNode.style.height = imageTag.height + 'px';
      }

      else {
        setTimeout(function () {
          findSize(imageTag);
        }, 250);
      }
    };

    for (i; i < lightbox.images.length; i += 1) {
      image       = new Image();
      image       = lightbox.images[i];
      image.index = i;

      full        = new Image();
      full.src    = image.parentNode.href;
      full.alt    = image.alt;
      images[i]   = full;
    }

    bevey.event.add(lightbox.elm, 'click', function (e) {
      if (bevey.getTarget(e).nodeName.toLowerCase() === 'img') {
        var elm       = bevey.getTarget(e),
            image     = elm,
            fullImage = image.parentNode.href,
            caption   = image.alt,
            overlay,
            content,
            curtain,
            imageContainer,
            imageWrapper,
            imageTag,
            captionTag,
            rotator;

        lightbox.onStart();

        if (bevey.hasClass(document.body.firstChild, 'overlay')) {
          overlay = document.body.firstChild;
          content = bevey.getElementsByClassName('content', overlay, 'div')[0];
          overlay.style.display = 'block';
          bevey.setOpacity(overlay, 1);

          imageWrapper   = content.getElementsByTagName('div')[0];
          imageContainer = imageWrapper.getElementsByTagName('div')[0];
          imageTag       = imageContainer.getElementsByTagName('img')[0];
          captionTag     = imageContainer.nextSibling;
        }

        else {
          overlay = document.createElement('div');
          bevey.addClass(overlay, 'overlay');
          curtain = document.createElement('div');
          bevey.addClass(curtain, 'curtain');
          overlay.appendChild(curtain);
          content = document.createElement('div');
          bevey.addClass(content, 'content');

          imageContainer = document.createElement('div');
          imageWrapper   = document.createElement('div');
          imageTag       = document.createElement('img');
          captionTag     = document.createElement('p');

          imageWrapper.appendChild(imageTag);
          imageContainer.appendChild(imageWrapper);
          imageContainer.appendChild(captionTag);
          content.appendChild(imageContainer);
          overlay.appendChild(content);

          bevey.event.add(imageContainer, 'click', function (e) {
            bevey.cancelBubble(e);
          });

          bevey.event.add(overlay, 'click', function () {
            var overlayAnimation = {
              elm        : overlay,
              property   : 'opacity',
              duration   : 250,
              start      : 1,
              end        : 0,
              onComplete : lightbox.onComplete
            };

            bevey.animate(overlayAnimation);
          });

          bevey.insertFirst(overlay, document.body);
        }

        imageTag.src = fullImage;
        imageTag.alt = caption;

        bevey.putText(captionTag, caption);

        findSize(imageTag);

        overlay.style.height = bevey.findDocumentHeight() + 'px';

        if (bevey.findStyle(content, 'position') === 'relative') {
          content.style.paddingTop = bevey.findScroll().positionY + 'px';
        }

        else {
          if ((imageTag.offsetHeight + 85) > Math.min(document.body.clientHeight, document.documentElement.clientHeight)) {
            content.style.position   = 'absolute';
            content.style.paddingTop = bevey.findScroll().positionY + 'px';
          }

          else {
            content.style.position   = 'fixed';
            content.style.paddingTop = 0;
          }
        }

        rotator = {
          elm        : imageTag,
          photos     : images,
          index      : image.index,
          onComplete : function () {
            var tempImage      = new Image();
            tempImage.src      = rotator.elm.src;

            findSize(rotator.elm);

            overlay.style.height = bevey.findDocumentHeight() + 'px';

            bevey.putText(rotator.elm.parentNode.nextSibling, rotator.elm.alt);
          }
        };

        bevey.rotator.control(rotator);

        if (e.preventDefault) {
          e.preventDefault();
        }

        return false;
      }
    });
  },

 /**
  * Interupts the loading of images outside the initial viewport.  Once the
  *  user's viewport comes near any of the images, their loading will be
  *  resumed.
  *
  * @param {Object} elm Parent element containing images that should be lazy
  *         loaded.  If no parent element is defined, the document will be used
  *         (all images will be lazy loaded).
  */
  lazyLoad : function (elm) {
    elm = elm || document;

    var allImages,
        lazyImages = [],
        loadImages,
        setSrc,
        i = 0;

    if (elm.nodeName) {
      allImages = elm.getElementsByTagName('img');
    }

    else {
      allImages = elm;
    }

    loadImages = function () {
      var scroll,
          i = lazyImages.length - 1;

      if (lazyImages.length) {
        scroll = bevey.findScroll().positionY + Math.min(document.body.clientHeight, document.documentElement.clientHeight);

        for (i; i >= 0; i -= 1) {
          if (lazyImages[i].offsetTop < scroll) {
            lazyImages[i].src = lazyImages[i].getAttribute('data-src');
            lazyImages.splice(i, 1);
          }
        }
      }
    };

    setSrc = function (image) {
      image.setAttribute('data-src', image.src);
      image.src = '#';
    };

    for (i; i < allImages.length; i += 1) {
      if (allImages[i].offsetTop > Math.min(document.body.clientHeight, document.documentElement.clientHeight)) {
        if (!allImages[i].complete) {
          lazyImages.push(allImages[i]);
          bevey.closure(allImages[i], setSrc(allImages[i]));
        }
      }
    }

    if (lazyImages.length > 0) {
      bevey.event.add(window, 'scroll', function () {
        loadImages();
      });

      bevey.event.add(window, 'resize', function () {
        loadImages();
      });
    }

    loadImages();
  },

 /**
  * Creates a carousel from pre-formatted list of carousel panes.
  *
  * @param {Object} elm Element that contains the preformated carousel markup.
  *         If no parent element is defined, the document will be used to find
  *         all applicable carousels.
  */
  carousel : function (elm) {
    elm = elm || document;

    var carouselTemp,
        carousel,
        i = 0,
        j,
        carousels = bevey.getElementsByClassName('carousel-wrap', elm, 'div'),
        movePrev,
        moveNext;

   /**
    * @private
    */
    movePrev = function (carousel) {
      return function () {
        if (carousel.currPane > 0) {
          bevey.removeClass(carousel.navNext, 'carousel-disabled');

          var animation = {
            elm      : carousel.parent,
            duration : 500,
            start    : parseInt(carousel.currPane * carousel.paneStep * -1, 10),
            end      : parseInt(carousel.currPane * carousel.paneStep * -1, 10) + carousel.paneStep,
            property : 'left',
            ease     : 'ease-out',
            onStart  : function () {
              carousel.currPane = carousel.currPane - 1;
              if (carousel.currPane <= 0) {
                bevey.addClass(carousel.navPrev, 'carousel-disabled');
              }

              else {
                bevey.removeClass(carousel.navPrev, 'carousel-disabled');
              }
            }
          };

          bevey.animate(animation);
        }
      };
    };

   /**
    * @private
    */
    moveNext = function (carousel) {
      return function () {
        if (carousel.currPane < carousel.panes.length - 1) {
          bevey.removeClass(carousel.navPrev, 'carousel-disabled');

          var animation = {
            elm      : carousel.parent,
            duration : 500,
            start    : parseInt(carousel.currPane * carousel.paneStep * -1, 10),
            end      : parseInt(carousel.currPane * carousel.paneStep * -1, 10) - carousel.paneStep,
            property : 'left',
            ease     : 'ease-out',
            onStart  : function () {
              carousel.currPane += 1;
              if (carousel.currPane >= carousel.panes.length - 1) {
                bevey.addClass(carousel.navNext, 'carousel-disabled');
              }

              else {
                bevey.removeClass(carousel.navNext, 'carousel-disabled');
              }
            }
          };

          bevey.animate(animation);
        }
      };
    };

    for (i; i < carousels.length; i += 1) {
      carouselTemp = bevey.getElementsByClassName('carousel', carousels[i], 'ul');

      carousel = {
        parent     : carouselTemp[0],
        panes      : bevey.getElementsByClassName('pane', carouselTemp[0], 'li'),
        totalWidth : 0,
        currPane   : 0,
        paneStep   : 0,
        navPrev    : document.createElement('a'),
        navNext    : document.createElement('a')
      };

      for (j = 0; j < carousel.panes.length; j += 1) {
        carousel.totalWidth = carousel.totalWidth + carousel.panes[i].offsetWidth;
        carousel.paneStep   = carousel.panes[i].offsetWidth;
      }

      carousel.parent.style.width = carousel.totalWidth + 'px';

      carousel.navPrev.href  = '#';
      carousel.navPrev.title = 'Previous pane in carousel';
      bevey.cancelLink(carousel.navPrev);
      bevey.addClass(carousel.navPrev, 'carousel-prev');
      bevey.addClass(carousel.navPrev, 'carousel-disabled');
      carousels[i].appendChild(carousel.navPrev);

      carousel.navNext.href  = '#';
      carousel.navNext.title = 'Next pane in carousel';
      bevey.cancelLink(carousel.navNext);
      bevey.addClass(carousel.navNext, 'carousel-next');
      carousels[i].appendChild(carousel.navNext);

      bevey.event.add(carousel.navPrev, 'click', bevey.closure(carousel, movePrev(carousel)));
      bevey.event.add(carousel.navNext, 'click', bevey.closure(carousel, moveNext(carousel)));
    }
  },

 /**
  * Creates a list of items dynamically loaded based off the criteria entered
  *  into the form element provided.
  *
  * @param {Object} autoComplete Object containing:
  *         {Object} elm Form element.
  *         {String} preText Text that will populate the result area before a
  *          request is made.
  *         {String} noResultText Text that will populate the result area if no
  *          results are found.
  *         {String} param Additional query parameters used in the AJAX
  *          request.
  *         {String} template HTML or text template of contents of each
  *          response list item's contents.  For each property replacement, the
  *          propertie's name should be referenced, surrounded by brackets:
  *          ("Result: [name]").
  *         {Integer} time Time of last request.  Should not be modified by the
  *          user.
  *         {Function} onStart Function to be executed when an AJAX search
  *          request begins.
  *         {Function} onComplete Function to be executed after an AJAX search
  *          request completes.
  * @note Uses eval if JSON.parse is not available.
  */
  autoComplete : function (autoComplete) {
    autoComplete.onStart      = autoComplete.onStart      || function () {};
    autoComplete.onComplete   = autoComplete.onComplete   || function () {};
    autoComplete.preText      = autoComplete.preText      || 'Your list of responses will show up here';
    autoComplete.noResultText = autoComplete.noResultText || 'No matches found';
    autoComplete.param        = autoComplete.param        || '';

    if (autoComplete.param) {
      autoComplete.param = '&' + autoComplete.param;
    }

    autoComplete.elm.setAttribute('autocomplete', 'off');

    var container = document.createElement('div'),
        anchor    = document.createElement('a'),
        list      = document.createElement('ul'),
        item      = document.createElement('li'),
        reply,
        newKey;

    if (!autoComplete.external) {
      autoComplete.external = true;
    }

    bevey.addClass(anchor, 'closeResults');
    bevey.addClass(container, 'noResults');
    bevey.putText(item, autoComplete.preText);
    container.appendChild(anchor);
    container.appendChild(list);
    list.appendChild(item);
    autoComplete.elm.parentNode.appendChild(container);

    bevey.event.add(anchor, 'click', function () {
      bevey.addClass(container, 'noResults');
    });

   /**
    * @private
    */
    newKey = function (elm, index) {
      var date  = new Date(),
          time  = date.getTime(),
          input = elm.value,
          newItem,
          ajaxRequest,
          item,
          link,
          caption;

      index = index || 0;
      autoComplete.time = autoComplete.time || 0;

      setTimeout(function () {
        if (((time - 2000) > autoComplete.time) && (input.length > 3)) {
          index += 1;
          newKey(elm, index);
        }
      }, 2000);

      if (index && input) {
        autoComplete.onStart();

        bevey.addClass(elm, 'active');

        ajaxRequest = {
          path       : autoComplete.path,
          param      : 'q=' + input + autoComplete.param,
          method     : 'GET',
          onComplete : function () {
            var i = 0,
                property,
                templateItem,
                template;

            if (ajaxRequest.response !== '[]') {
              bevey.removeClass(container, 'noResults');

              if (typeof(JSON) === 'object') {
                reply = JSON.parse(ajaxRequest.response);
              }

              else {
                reply = eval('(' + ajaxRequest.response + ')');
              }

              for (i; i < reply.length; i += 1) {
                if (autoComplete.template) {
                  template = autoComplete.template;
                  property = '';
                  templateItem = document.createElement('li');

                  for (property in reply[i]) {
                    if (reply[i].hasOwnProperty(property)) {
                      while (template.indexOf('[' + property + ']') !== -1) {
                        template = template.replace('[' + property + ']', reply[i][property]);
                      }
                    }
                  }

                  templateItem.innerHTML = template;
                  list.appendChild(templateItem);
                }

                else {
                  item = document.createElement('li');
                  link = document.createElement('a');
                  caption = document.createElement('span');

                  link.setAttribute('href', reply[i].clickUrl);

                  if (autoComplete.external) {
                    link.setAttribute('rel', 'external');
                    link.target = '_blank';
                  }

                  bevey.putText(link, reply[i].url);
                  bevey.putText(caption, reply[i].title);

                  item.appendChild(link);
                  item.appendChild(caption);
                  list.appendChild(item);
                }
              }
            }

            else {
              bevey.removeChildren(list);
              newItem = document.createElement('li');
              bevey.putText(newItem, autoComplete.noResultText);

              bevey.addClass(container, 'noResults');
              list.appendChild(newItem);
            }

            bevey.removeClass(elm, 'active');

            autoComplete.onComplete();
          }
        };

        autoComplete.time = time;
        bevey.removeChildren(list);

        bevey.ajax.request(ajaxRequest);
      }
    };

    bevey.event.add(autoComplete.elm, 'keyup', bevey.closure(autoComplete.elm, function () {
      newKey(autoComplete.elm);
    }));
  },

 /**
  * Creates a text default for the given form element.  Once the form element
  *  is focused, the text will be removed, allowing normal form use.  If no
  *  alternate text is defined, that input's <label> text will be used.
  *
  * @param {Object/String} input Form input element to have text default added
  *         to.  If a string value is passed, it's assumed to be the ID of the
  *         desired form element.
  * @param {String} text Optionally, default text to populate the form may be
  *         defined.  If no text value is defined, the input element's <label>
  *         value will be used.
  */
  formDefault : function (input, text) {
    var elm,
        labels,
        i = 0,
        textNode;

    if (typeof(input) === 'string') {
      elm = document.getElementById(input);
    }

    else {
      elm = input;
    }

    if (elm !== null) {
      if (text === undefined) {
        text = '';
        labels = elm.parentNode.getElementsByTagName('label');

        for (i; i < labels.length; i += 1) {
          if (labels[i].htmlFor) {
            textNode = labels[i].childNodes[0];

            if (textNode !== null) {
              text = textNode.nodeValue;
            }

            break;
          }
        }
      }

      bevey.event.add(elm, 'focus', function () {
        if (elm.value === text) {
          elm.value = '';
        }
      });

      bevey.event.add(elm, 'blur', function () {
        elm.value = bevey.trim(elm.value);

        if (elm.value === '') {
          elm.value = text;
        }
      });

      elm.value = bevey.trim(text);
    }
  },

 /**
  * For each form element in the parent element with a maxlength specified,
  *  shift focus to the next visible form element once that maxlength is
  *  reached.  If no parent is specified, the document root will be used.
  */
  formFocus : function (elm) {
    var removeHidden,
        focusNext,
        checkFocus,
        inputs,
        i = 0;

    elm = elm || document;

   /**
    * @private
    */
    removeHidden = function (inputs) {
      var newInputs = [],
          j = 0,
          i = 0;

      for (i; i < inputs.length; i += 1) {
        if (inputs[i].type !== 'hidden') {
          newInputs[j] = inputs[i];
          j += 1;
        }
      }

      return newInputs;
    };

   /**
    * @private
    */
    focusNext = function (input) {
      var i = 0,
          parentForm = input.form,
          formInputs = removeHidden(parentForm.getElementsByTagName('input'));

      for (i; (i + 1) < formInputs.length; i += 1) {
        if (formInputs[i] === input) {
          if (formInputs[i + 1].type !== 'hidden') {
            formInputs[i + 1].focus();
          }
        }
      }
    };

   /**
    * @private
    */
    checkFocus = function (elm) {
      return function (elm) {
        if (elm.value.length >= elm.maxLength) {
          focusNext(elm);
        }
      };
    };

    inputs = removeHidden(elm.getElementsByTagName('input'));

    for (i; i < inputs.length; i += 1) {
      if (inputs[i].maxLength > 0) {
        bevey.event.add(inputs[i], 'keyup', bevey.closure(inputs[i], checkFocus(inputs[i])));
      }
    }
  },

 /**
  * Creates a dynamic tab interface to specially structured markup.
  *
  * @return {Object} Object containing:
  *          {Object} elm Parent element of the tab markup.  If no parent is
  *           specified, the document root will be used.
  *          {Boolean} animate Specifies if tabs should fade when changed.
  *           Defaults to true.
  *          {Function} onStart Function to be executed before a tab change.
  *          {Function} onComplete Function to be executed after a tab change.
  */
  tabs : function (tabs) {
    tabs = tabs || [];

    tabs.elm        = tabs.elm        || document;
    tabs.onStart    = tabs.onStart    || function () {};
    tabs.onComplete = tabs.onComplete || function () {};

    if (tabs.animate !== false) {
      tabs.animate = true;
    }

    var tabSets = bevey.getElementsByClassName('tabs', tabs.elm, 'ul'),
        i       = 0,
        changeTab;

   /**
    * @private
    */
    changeTab = function () {
      return function (e) {
        var elm = bevey.getTarget(e),
            tabSet,
            paneSet,
            index,
            animation,
            j = 0;

        while (elm.nodeName.toLowerCase() !== 'li') {
          elm = elm.parentNode;
        }

        tabSet  = elm.parentNode.getElementsByTagName('li');
        paneSet = bevey.getElementsByClassName('panes', elm.parentNode.parentNode, 'ul');
        paneSet = paneSet[0].getElementsByTagName('li');

        tabs.onStart();

        for (j; j < tabSet.length; j += 1) {
          if (tabSet[j] === elm) {
            index = j;
          }

          else {
            bevey.removeClass(tabSet[j],  'selected');
            bevey.removeClass(paneSet[j], 'selected');
          }
        }

        if (tabs.animate) {
          if (!bevey.hasClass(paneSet[index], 'selected')) {
            bevey.setOpacity(paneSet[index], 0);

            animation = {
              elm        : paneSet[index],
              property   : 'opacity',
              start      : 0,
              end        : 1,
              duration   : 250,
              noNative   : true,
              onComplete : function () {
                paneSet[index].style.display = '';
                tabs.onComplete();
              }
            };

            bevey.animate(animation);
          }
        }

        bevey.addClass(tabSet[index],  'selected');
        bevey.addClass(paneSet[index], 'selected');

        if (e.preventDefault) {
          e.preventDefault();
        }

        return false;
      };
    };

    for (i; i < tabSets.length; i += 1) {
      bevey.event.add(tabSets[i], 'click', changeTab());
    }
  },

 /**
  * Alters the document body's font-size to zoom text up and down.  Behaves
  *  best when a page is built using the correct "em" measurement.
  *
  * @param {String} direction Direction of zoom.  Valid values are "up" and
  *         "down".  If any other value is passed, it is assumed to be a call
  *         to be initialized - and the value stored in a cookie (if any) will
  *         be applied.
  */
  zoomer : function (direction) {
    var currentSize = bevey.findStyle(document.body, 'font-size') || 12;

    if (direction === undefined) {
      currentSize = bevey.readCookie('zoomer') || currentSize;
    }

    if (direction === 'down') {
      currentSize -= 1;
    }

    else if (direction === 'up') {
      currentSize += 1;
    }

    if ((currentSize !== undefined) && (currentSize < 40) && (currentSize > 8)) {
      document.body.style.fontSize = currentSize + 'px';
      document.cookie = 'zoomer=' + currentSize + '; path=/';
    }
  },

 /**
  * If a user is viewing from any version if Internet Explorer, they will be
  *  prompted to upgrade to a randomly selected alternative browser.
  *
  * @note Uses browser sniffing.
  */
  nagBar : function () {
    if (navigator.appVersion.indexOf('MSIE') !== -1) {
      var nagBarCookie = bevey.readCookie('nagBar'),
          browsers,
          browser,
          elm,
          para,
          link,
          close,
          animation;

      if (nagBarCookie !== 'false') {
        browsers = [['http://mozilla.com', 'Firefox'],
                    ['http://opera.com', 'Opera'],
                    ['http://www.google.com/chrome', 'Google Chrome'],
                    ['http://www.apple.com/safari', 'Apple Safari']];
        browser  = Math.floor(Math.random() * browsers.length);
        elm      = document.createElement('div');
        para     = document.createElement('p');
        link     = document.createElement('a');
        close    = document.createElement('span');

        bevey.event.add(elm, 'mouseenterr', function () {
          bevey.addClass(this, 'nagHover');
        });

        bevey.event.add(elm, 'mouseleave', function () {
          bevey.removeClass(elm, 'nagHover');
        });

        bevey.addClass(elm, 'nagBar');
        bevey.putText(para, 'Your browser should be upgraded to take full advantage of this site: ');

        link.setAttribute('rel', 'external');
        link.target = '_blank';
        link.setAttribute('href', browsers[browser][0]);
        bevey.putText(link, 'Upgrade to ' + browsers[browser][1]);

        bevey.putText(close, 'Close');

        bevey.event.add(close, 'click', function () {
          elm.style.display = 'none';
          document.cookie   = 'nagBar=false; path=/';
        });

        para.appendChild(link);
        elm.appendChild(close);
        elm.appendChild(para);

        document.body.appendChild(elm);

        animation = {
          elm      : elm,
          start    : 0,
          end      : para.offsetHeight,
          property : 'height'
        };

        bevey.animate(animation);
      }
    }
  },

 /**
  * Initialization for Bevey.  Executes the standard functions used.  If a
  *  global function of "init" is available, it will also be executed.
  */
  init : function () {
    bevey.lazyLoad();
    bevey.addClass(document.body, 'rich');

    var lightbox,
        dirWrap,
        dirList,
        listItem,
        cropPath,
        cropFilename,
        crop,
        cropCanvas,
        cropImage,
        createBox;

    if (document.getElementById('imageList')) {
      lightbox = {
        elm : document.getElementById('imageList')
      };

      bevey.lightbox(lightbox);
    }

    if (document.getElementById('crop')) {
      crop         = bevey.getElementsByClassName('bd', document.getElementById('crop'), 'div');
      crop         = crop[0];
      cropCanvas   = document.createElement('div');
      cropImage    = crop.getElementsByTagName('img')[0];
      cropPath     = cropImage.src;
      cropPath     = cropPath.split('/');
      cropFilename = cropPath[(cropPath.length - 1)];
      cropPath.splice((cropPath.length - 2), 2);
      cropPath     = cropPath.join('/');

      bevey.addClass(cropCanvas, 'canvas');
      crop.appendChild(cropCanvas);

      createBox = function () {
        var callBack,
            resize,
            elm,
            drag;

        callBack = function () {
          var width  = drag.elm.style.width  || bevey.findStyle(drag.elm, 'width')  + 'px',
              height = drag.elm.style.height || bevey.findStyle(drag.elm, 'height') + 'px';
          bevey.log('<a href="' + cropPath + '/' + bevey.stripUnits(width) + 'x' + bevey.stripUnits(height) + '/' + drag.newX + 'x' + drag.newY + '/' + cropFilename + '" rel="external" target="_blank">' + cropPath + '/' + bevey.stripUnits(width) + 'x' + bevey.stripUnits(height) + '/' + drag.newX + 'x' + drag.newY + '/' + cropFilename + '</a>');
        };

        elm = document.createElement('div');
        bevey.addClass(elm, 'dragBox');
        elm.style.backgroundImage = 'url(' + cropImage.src + ')';
        crop.appendChild(elm);

        resize = {
          elm        : elm,
          restrict   : true,
          onComplete : callBack
        };

        bevey.resize(resize);

        drag = {
          elm      : elm,
          restrict : true,
          onTween  : function () {
            elm.style.backgroundPosition = '-' + (drag.newX + bevey.findStyle(drag.elm, 'border-left-width')) + 'px -' + (drag.newY + bevey.findStyle(drag.elm, 'border-top-width')) + 'px';
          },

          onComplete : callBack
        };

        bevey.clickDrag(drag);
      };

      createBox();
    }

    if (!bevey.hasClass(document.getElementById('directoryList').parentNode, 'directoryList')) {
      dirWrap  = bevey.getElementsByClassName('bd', document.getElementById('directoryList'), 'div');
      dirWrap  = dirWrap[0];
      dirList  = dirWrap.getElementsByTagName('ul');
      dirList  = dirList[0];
      listItem = dirList.getElementsByTagName('li');
      dirWrap.style.height = listItem[0].offsetHeight + 'px';

      bevey.event.add(dirWrap, 'mouseenter', function (e) {
        var startHeight = dirWrap.offsetHeight,
            endHeight   = dirList.offsetHeight,
            animation;

        if (endHeight > 50) {
          if (dirWrap.style.height === endHeight + 'px') {
            return false;
          }

          animation = {
            elm      : dirWrap,
            start    : startHeight,
            end      : endHeight,
            property : 'height',
            duration : 250
          };

          bevey.animate(animation);
        }
      });

      bevey.event.add(dirWrap, 'mouseleave', function (e) {
        var startHeight = dirWrap.offsetHeight,
            endHeight   = listItem[0].offsetHeight,
            animation;

        if (startHeight > 50) {
          if (dirWrap.style.height === endHeight + 'px') {
            return false;
          }

          animation = {
            elm      : dirWrap,
            start    : startHeight,
            end      : endHeight,
            property : 'height',
            duration : 250
          };

          bevey.animate(animation);
        }
      });
    }

    if (typeof(init) === 'function') {
      init();
    }
  }
};

if (document.addEventListener) {
  document.addEventListener('DOMContentLoaded', bevey.init, false);
}

bevey.event.add(window, 'load', function () {
  if (!document.addEventListener) {
    bevey.init();
  }
});

bevey.event.add(window, 'unload', function () {
  bevey.event.removeAll();
});