/* common */
function cp(event){
	event = Event.extend(event?event:window.event);
	event.stop();
}

function addScrollPosition(link) {
	var windowScrollPositions = getWindowScrollPositions();
	
	link.href +=
		(link.href.search(/\?/) > 0?'&':'?')
		+ 'pc3Scroll='
		+ windowScrollPositions['left']
		+ 'x'
		+ windowScrollPositions['top']
	;
}

function applyScrollPosition(){
	var key;
	var match = document.location.search.match('pc3Scroll=([0-9]+)x([0-9]+)');
	if ( !match ) return;
	
	setWindowScrollPositions(match[1], match[2]);
}

/**
 * 20080822.acn : set the scrollposition of the window
 * 
 * @param {int} left position from left of the top-left corner of the window
 * @param {int} top position from top of the top-left corner of the window
 */
function setWindowScrollPositions(left, top) {
	if ( typeof window.pageXOffset != 'undefined' && typeof window.pageYOffset != 'undefined' ) {
		window.scrollTo(left, top);
	}
	else if ( typeof document.compatMode != 'undefined' && document.compatMode != 'BackCompat' ) {
		document.documentElement.scrollLeft = left;
		document.documentElement.scrollTop = top;
	}
	else if ( typeof document.body != 'undefined' ) {
		document.body.scrollLeft = left;
		document.body.scrollTop = top;
	}
}

/**
 * 20080822.acn : get the scrollposition of the window
 * 
 * @return {array} windowScrollPositions['left'] and windowScrollPositions['top']
 */
function getWindowScrollPositions() {
	var windowScrollPositions = {};
		windowScrollPositions['left'] = 0;
		windowScrollPositions['top']  = 0;
	
	if (  typeof window.pageXOffset != 'undefined' && typeof window.pageYOffset != 'undefined' ) {
		windowScrollPositions['left'] = window.pageXOffset;
		windowScrollPositions['top'] = window.pageYOffset;
	}
	else if ( typeof document.compatMode != 'undefined' && document.compatMode != 'BackCompat' ) {
		windowScrollPositions['left'] = document.documentElement.scrollLeft;
		windowScrollPositions['top'] = document.documentElement.scrollTop;
	}
	else if ( typeof document.body != 'undefined' ) {
		windowScrollPositions['left'] = document.body.scrollLeft;
		windowScrollPositions['top'] = document.body.scrollTop;
	}
	
	return windowScrollPositions;
}

/**
 * Get the GET parameters from the URL.<br>
 * <ul><li>If no paramName is defined, all url parameters are returned.</li><li>If paramName is defined (array with parameter keys), only these parameters are returned.</li></ul>
 * Note: parameter name and parameter value are finally lower case.<br>
 * <br>
 * Tested under:
 * <ul><li>Internet Explorer 5.5+
 * <li>Firefox 3.6
 * <li>Safari 4.0.4
 * <li>Opera 10.10
 * </ul>
 * 
 * @author acn
 * @param {}|{Array} paramName : no paramName -> all, array -> these params are returned.
 * @return {Object} urlParams : parameter key is used as the object member var, parameter value is the according value.
 */
function getUrlParam(paramName) {
	var params = document.location.search.substr(1, document.location.search.length);
	if (params == '') return;

	var urlParams = new Object;
	var uPs = new Object;

	//-- get all url params (for internal use)
	gets = params.split('&'); // array with 'key=value'
	for (var i = 0; i < gets.length; ++i) {
		var keyAndValue = gets[i];
		keyAndValue = keyAndValue.split('='); // array with 0 = 'key' and 1 = 'value'

		var key = null;
		var value = null;
		
		key = keyAndValue[0].toLowerCase();
		if (keyAndValue[1]) value = keyAndValue[1].toLowerCase();
		
		uPs[key] = value;
	}
	//--
	
	//-- filter params when wanted
	var behaviour = arguments.length; // 0 : no filter; all url params asked, 1 : filter; array with asked param names
	switch (behaviour) {
		case 1 : // array given, loop over all and collect only wanted for return
			for (var idx in paramName) {
				key = paramName[idx];
				if (uPs[key]) urlParams[key] = uPs[key];
			}
			break;

		case 0 :
		default:
			urlParams = uPs;
	}
	//--
	
	return urlParams;
}

function pc3CreateElementsByHTML(html,parent,parentTag){
	if ( !html ) return new Array(); //empty html
	
	var nestingLevel = 0;
	var content = document.createElement('div');
	
	parentTag = (parent?parent.nodeName:parentTag);
	
	switch( parentTag ) {
		case 'TR':
			html = '<table><tbody><tr>'+ html +'</tr></tbody></table>';
			nestingLevel = 3;
			break;
			
		case 'TBODY':
			html = '<table><tbody>'+ html +'</tbody></table>';
			nestingLevel = 2;
			break;
			
		case 'TABLE':
			html = '<table>'+ html +'</table>';
			nestingLevel = 1;
			break;
	}
	
	content.innerHTML = html;
	
	if ( !content.firstChild ) {
		// 20081223.acn : BUG #437 : why ever, but under some circumstances, there isn't a content.firstChild and because of that, the alert message was shown (a.e. at vpbank ...)?
		// alert('failed to create: '+ html + ' within '+ parentTag);
		return null;
	}
	
	while ( nestingLevel-- > 0 ) {
		content = content.removeChild(content.firstChild);
	};
	
	var childs = new Array();
	
	while( content.firstChild ) {
		var child = content.removeChild(content.firstChild);
		
		if ( parent ) parent.appendChild(child);
		childs.push(child);
	}
	
	return childs;
}

function dc(code){
	code = Base64.decode(code);
	
	var plain = '';
	
	for(var i=0;i<code.length;i++) {
		plain += String.fromCharCode(code.charCodeAt(i)^xEncryptionKey.charCodeAt(i%xEncryptionKey.length));
	}
	
	return plain;
}

function dcw(code){ document.write(dc(code)); }
function dcm(code){ return 'mailto:'+ dc(code); }

var Base64 = {

    // private property
    _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",

    // public method for encoding
    encode : function (input) {
        var output = "";
        var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
        var i = 0;

        input = Base64._utf8_encode(input);

        while (i < input.length) {

            chr1 = input.charCodeAt(i++);
            chr2 = input.charCodeAt(i++);
            chr3 = input.charCodeAt(i++);

            enc1 = chr1 >> 2;
            enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
            enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
            enc4 = chr3 & 63;

            if (isNaN(chr2)) {
                enc3 = enc4 = 64;
            } else if (isNaN(chr3)) {
                enc4 = 64;
            }

            output = output +
            this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
            this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);

        }

        return output;
    },

    // public method for decoding
    decode : function (input) {
        var output = "";
        var chr1, chr2, chr3;
        var enc1, enc2, enc3, enc4;
        var i = 0;

        input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");

        while (i < input.length) {

            enc1 = this._keyStr.indexOf(input.charAt(i++));
            enc2 = this._keyStr.indexOf(input.charAt(i++));
            enc3 = this._keyStr.indexOf(input.charAt(i++));
            enc4 = this._keyStr.indexOf(input.charAt(i++));

            chr1 = (enc1 << 2) | (enc2 >> 4);
            chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
            chr3 = ((enc3 & 3) << 6) | enc4;

            output = output + String.fromCharCode(chr1);

            if (enc3 != 64) {
                output = output + String.fromCharCode(chr2);
            }
            if (enc4 != 64) {
                output = output + String.fromCharCode(chr3);
            }

        }

        output = Base64._utf8_decode(output);

        return output;

    },

    // private method for UTF-8 encoding
    _utf8_encode : function (string) {
        string = string.replace(/\r\n/g,"\n");
        var utftext = "";

        for (var n = 0; n < string.length; n++) {

            var c = string.charCodeAt(n);

            if (c < 128) {
                utftext += String.fromCharCode(c);
            }
            else if((c > 127) && (c < 2048)) {
                utftext += String.fromCharCode((c >> 6) | 192);
                utftext += String.fromCharCode((c & 63) | 128);
            }
            else {
                utftext += String.fromCharCode((c >> 12) | 224);
                utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                utftext += String.fromCharCode((c & 63) | 128);
            }

        }

        return utftext;
    },

    // private method for UTF-8 decoding
    _utf8_decode : function (utftext) {
        var string = "";
        var i = 0;
        var c = c1 = c2 = 0;

        while ( i < utftext.length ) {

            c = utftext.charCodeAt(i);

            if (c < 128) {
                string += String.fromCharCode(c);
                i++;
            }
            else if((c > 191) && (c < 224)) {
                c2 = utftext.charCodeAt(i+1);
                string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
                i += 2;
            }
            else {
                c2 = utftext.charCodeAt(i+1);
                c3 = utftext.charCodeAt(i+2);
                string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
                i += 3;
            }

        }

        return string;
    }

}

/* prototype */
/*  Prototype JavaScript framework, version 1.6.0.3
 *  (c) 2005-2008 Sam Stephenson
 *
 *  Prototype is freely distributable under the terms of an MIT-style license.
 *  For details, see the Prototype web site: http://www.prototypejs.org/
 *
 *--------------------------------------------------------------------------*/

var Prototype = {
  Version: '1.6.0.3',

  Browser: {
    IE:     !!(window.attachEvent &&
      navigator.userAgent.indexOf('Opera') === -1),
    Opera:  navigator.userAgent.indexOf('Opera') > -1,
    WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
    Gecko:  navigator.userAgent.indexOf('Gecko') > -1 &&
      navigator.userAgent.indexOf('KHTML') === -1,
    MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/)
  },

  BrowserFeatures: {
    XPath: !!document.evaluate,
    SelectorsAPI: !!document.querySelector,
    ElementExtensions: !!window.HTMLElement,
    SpecificElementExtensions:
      document.createElement('div')['__proto__'] &&
      document.createElement('div')['__proto__'] !==
        document.createElement('form')['__proto__']
  },

  ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',
  JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,

  emptyFunction: function() { },
  K: function(x) { return x }
};

//-- 20090421.acn : get msie version, tested with msie 5.5, 6, 7, 8
if (Prototype.Browser.IE) {
	if (/MSIE (\d+\.\d+);/.test(navigator.userAgent)) {
		Prototype.BrowserFeatures['Version'] = new Number(RegExp.$1);
	}
}
//--

if (Prototype.Browser.MobileSafari)
  Prototype.BrowserFeatures.SpecificElementExtensions = false;


/* Based on Alex Arnell's inheritance implementation. */
var Class = {
  create: function() {
    var parent = null, properties = $A(arguments);
    if (Object.isFunction(properties[0]))
      parent = properties.shift();

    function klass() {
      this.initialize.apply(this, arguments);
    }

    Object.extend(klass, Class.Methods);
    klass.superclass = parent;
    klass.subclasses = [];

    if (parent) {
      var subclass = function() { };
      subclass.prototype = parent.prototype;
      klass.prototype = new subclass;
      parent.subclasses.push(klass);
    }

    for (var i = 0; i < properties.length; i++)
      klass.addMethods(properties[i]);

    if (!klass.prototype.initialize)
      klass.prototype.initialize = Prototype.emptyFunction;

    klass.prototype.constructor = klass;

    return klass;
  }
};

Class.Methods = {
  addMethods: function(source) {
    var ancestor   = this.superclass && this.superclass.prototype;
    var properties = Object.keys(source);

    if (!Object.keys({ toString: true }).length)
      properties.push("toString", "valueOf");

    for (var i = 0, length = properties.length; i < length; i++) {
      var property = properties[i], value = source[property];
      if (ancestor && Object.isFunction(value) &&
          value.argumentNames().first() == "$super") {
        var method = value;
        value = (function(m) {
          return function() { return ancestor[m].apply(this, arguments) };
        })(property).wrap(method);

        value.valueOf = method.valueOf.bind(method);
        value.toString = method.toString.bind(method);
      }
      this.prototype[property] = value;
    }

    return this;
  }
};

var Abstract = { };

Object.extend = function(destination, source) {
  for (var property in source)
    destination[property] = source[property];
  return destination;
};

Object.extend(Object, {
  inspect: function(object) {
    try {
      if (Object.isUndefined(object)) return 'undefined';
      if (object === null) return 'null';
      return object.inspect ? object.inspect() : String(object);
    } catch (e) {
      if (e instanceof RangeError) return '...';
      throw e;
    }
  },

  toJSON: function(object) {
    var type = typeof object;
    switch (type) {
      case 'undefined':
      case 'function':
      case 'unknown': return;
      case 'boolean': return object.toString();
    }

    if (object === null) return 'null';
    if (object.toJSON) return object.toJSON();
    if (Object.isElement(object)) return;

    var results = [];
    for (var property in object) {
      var value = Object.toJSON(object[property]);
      if (!Object.isUndefined(value))
        results.push(property.toJSON() + ': ' + value);
    }

    return '{' + results.join(', ') + '}';
  },

  toQueryString: function(object) {
    return $H(object).toQueryString();
  },

  toHTML: function(object) {
    return object && object.toHTML ? object.toHTML() : String.interpret(object);
  },

  keys: function(object) {
    var keys = [];
    for (var property in object)
      keys.push(property);
    return keys;
  },

  values: function(object) {
    var values = [];
    for (var property in object)
      values.push(object[property]);
    return values;
  },

  clone: function(object) {
    return Object.extend({ }, object);
  },

  isElement: function(object) {
    return !!(object && object.nodeType == 1);
  },

  isArray: function(object) {
    return object != null && typeof object == "object" &&
      'splice' in object && 'join' in object;
  },

  isHash: function(object) {
    return object instanceof Hash;
  },

  isFunction: function(object) {
    return typeof object == "function";
  },

  isString: function(object) {
    return typeof object == "string";
  },

  isNumber: function(object) {
    return typeof object == "number";
  },

  isUndefined: function(object) {
    return typeof object == "undefined";
  }
});

Object.extend(Function.prototype, {
  argumentNames: function() {
    var names = this.toString().match(/^[\s\(]*function[^(]*\(([^\)]*)\)/)[1]
      .replace(/\s+/g, '').split(',');
    return names.length == 1 && !names[0] ? [] : names;
  },

  bind: function() {
    if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;
    var __method = this, args = $A(arguments), object = args.shift();
    return function() {
      return __method.apply(object, args.concat($A(arguments)));
    }
  },

  bindAsEventListener: function() {
    var __method = this, args = $A(arguments), object = args.shift();
    return function(event) {
      return __method.apply(object, [event || window.event].concat(args));
    }
  },

  curry: function() {
    if (!arguments.length) return this;
    var __method = this, args = $A(arguments);
    return function() {
      return __method.apply(this, args.concat($A(arguments)));
    }
  },

  delay: function() {
    var __method = this, args = $A(arguments), timeout = args.shift() * 1000;
    return window.setTimeout(function() {
      return __method.apply(__method, args);
    }, timeout);
  },

  defer: function() {
    var args = [0.01].concat($A(arguments));
    return this.delay.apply(this, args);
  },

  wrap: function(wrapper) {
    var __method = this;
    return function() {
      return wrapper.apply(this, [__method.bind(this)].concat($A(arguments)));
    }
  },

  methodize: function() {
    if (this._methodized) return this._methodized;
    var __method = this;
    return this._methodized = function() {
      return __method.apply(null, [this].concat($A(arguments)));
    };
  }
});

Date.prototype.toJSON = function() {
  return '"' + this.getUTCFullYear() + '-' +
    (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
    this.getUTCDate().toPaddedString(2) + 'T' +
    this.getUTCHours().toPaddedString(2) + ':' +
    this.getUTCMinutes().toPaddedString(2) + ':' +
    this.getUTCSeconds().toPaddedString(2) + 'Z"';
};

var Try = {
  these: function() {
    var returnValue;

    for (var i = 0, length = arguments.length; i < length; i++) {
      var lambda = arguments[i];
      try {
        returnValue = lambda();
        break;
      } catch (e) { }
    }

    return returnValue;
  }
};

RegExp.prototype.match = RegExp.prototype.test;

RegExp.escape = function(str) {
  return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
};

/*--------------------------------------------------------------------------*/

var PeriodicalExecuter = Class.create({
  initialize: function(callback, frequency) {
    this.callback = callback;
    this.frequency = frequency;
    this.currentlyExecuting = false;

    this.registerCallback();
  },

  registerCallback: function() {
    this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
  },

  execute: function() {
    this.callback(this);
  },

  stop: function() {
    if (!this.timer) return;
    clearInterval(this.timer);
    this.timer = null;
  },

  onTimerEvent: function() {
    if (!this.currentlyExecuting) {
      try {
        this.currentlyExecuting = true;
        this.execute();
      } finally {
        this.currentlyExecuting = false;
      }
    }
  }
});
Object.extend(String, {
  interpret: function(value) {
    return value == null ? '' : String(value);
  },
  specialChar: {
    '\b': '\\b',
    '\t': '\\t',
    '\n': '\\n',
    '\f': '\\f',
    '\r': '\\r',
    '\\': '\\\\'
  }
});

Object.extend(String.prototype, {
  gsub: function(pattern, replacement) {
    var result = '', source = this, match;
    replacement = arguments.callee.prepareReplacement(replacement);

    while (source.length > 0) {
      if (match = source.match(pattern)) {
        result += source.slice(0, match.index);
        result += String.interpret(replacement(match));
        source  = source.slice(match.index + match[0].length);
      } else {
        result += source, source = '';
      }
    }
    return result;
  },

  sub: function(pattern, replacement, count) {
    replacement = this.gsub.prepareReplacement(replacement);
    count = Object.isUndefined(count) ? 1 : count;

    return this.gsub(pattern, function(match) {
      if (--count < 0) return match[0];
      return replacement(match);
    });
  },

  scan: function(pattern, iterator) {
    this.gsub(pattern, iterator);
    return String(this);
  },

  truncate: function(length, truncation) {
    length = length || 30;
    truncation = Object.isUndefined(truncation) ? '...' : truncation;
    return this.length > length ?
      this.slice(0, length - truncation.length) + truncation : String(this);
  },

  strip: function() {
    return this.replace(/^\s+/, '').replace(/\s+$/, '');
  },

  stripTags: function() {
    return this.replace(/<\/?[^>]+>/gi, '');
  },

  stripScripts: function() {
    return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
  },

  extractScripts: function() {
    var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
    var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
    return (this.match(matchAll) || []).map(function(scriptTag) {
      return (scriptTag.match(matchOne) || ['', ''])[1];
    });
  },

  evalScripts: function() {
    return this.extractScripts().map(function(script) { return eval(script) });
  },

  escapeHTML: function() {
    var self = arguments.callee;
    self.text.data = this;
    return self.div.innerHTML;
  },

  unescapeHTML: function() {
    var div = new Element('div');
    div.innerHTML = this.stripTags();
    return div.childNodes[0] ? (div.childNodes.length > 1 ?
      $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) :
      div.childNodes[0].nodeValue) : '';
  },

  toQueryParams: function(separator) {
    var match = this.strip().match(/([^?#]*)(#.*)?$/);
    if (!match) return { };

    return match[1].split(separator || '&').inject({ }, function(hash, pair) {
      if ((pair = pair.split('='))[0]) {
        var key = decodeURIComponent(pair.shift());
        var value = pair.length > 1 ? pair.join('=') : pair[0];
        if (value != undefined) value = decodeURIComponent(value);

        if (key in hash) {
          if (!Object.isArray(hash[key])) hash[key] = [hash[key]];
          hash[key].push(value);
        }
        else hash[key] = value;
      }
      return hash;
    });
  },

  toArray: function() {
    return this.split('');
  },

  succ: function() {
    return this.slice(0, this.length - 1) +
      String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
  },

  times: function(count) {
    return count < 1 ? '' : new Array(count + 1).join(this);
  },

  camelize: function() {
    var parts = this.split('-'), len = parts.length;
    if (len == 1) return parts[0];

    var camelized = this.charAt(0) == '-'
      ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
      : parts[0];

    for (var i = 1; i < len; i++)
      camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);

    return camelized;
  },

  capitalize: function() {
    return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
  },

  underscore: function() {
    return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
  },

  dasherize: function() {
    return this.gsub(/_/,'-');
  },

  inspect: function(useDoubleQuotes) {
    var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) {
      var character = String.specialChar[match[0]];
      return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16);
    });
    if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
    return "'" + escapedString.replace(/'/g, '\\\'') + "'";
  },

  toJSON: function() {
    return this.inspect(true);
  },

  unfilterJSON: function(filter) {
    return this.sub(filter || Prototype.JSONFilter, '#{1}');
  },

  isJSON: function() {
    var str = this;
    if (str.blank()) return false;
    str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
    return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);
  },

  evalJSON: function(sanitize) {
    var json = this.unfilterJSON();
    try {
      if (!sanitize || json.isJSON()) return eval('(' + json + ')');
    } catch (e) { }
    throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
  },

  include: function(pattern) {
    return this.indexOf(pattern) > -1;
  },

  startsWith: function(pattern) {
    return this.indexOf(pattern) === 0;
  },

  endsWith: function(pattern) {
    var d = this.length - pattern.length;
    return d >= 0 && this.lastIndexOf(pattern) === d;
  },

  empty: function() {
    return this == '';
  },

  blank: function() {
    return /^\s*$/.test(this);
  },

  interpolate: function(object, pattern) {
    return new Template(this, pattern).evaluate(object);
  }
});

if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, {
  escapeHTML: function() {
    return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
  },
  unescapeHTML: function() {
    return this.stripTags().replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>');
  }
});

String.prototype.gsub.prepareReplacement = function(replacement) {
  if (Object.isFunction(replacement)) return replacement;
  var template = new Template(replacement);
  return function(match) { return template.evaluate(match) };
};

String.prototype.parseQuery = String.prototype.toQueryParams;

Object.extend(String.prototype.escapeHTML, {
  div:  document.createElement('div'),
  text: document.createTextNode('')
});

String.prototype.escapeHTML.div.appendChild(String.prototype.escapeHTML.text);

var Template = Class.create({
  initialize: function(template, pattern) {
    this.template = template.toString();
    this.pattern = pattern || Template.Pattern;
  },

  evaluate: function(object) {
    if (Object.isFunction(object.toTemplateReplacements))
      object = object.toTemplateReplacements();

    return this.template.gsub(this.pattern, function(match) {
      if (object == null) return '';

      var before = match[1] || '';
      if (before == '\\') return match[2];

      var ctx = object, expr = match[3];
      var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
      match = pattern.exec(expr);
      if (match == null) return before;

      while (match != null) {
        var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1];
        ctx = ctx[comp];
        if (null == ctx || '' == match[3]) break;
        expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
        match = pattern.exec(expr);
      }

      return before + String.interpret(ctx);
    });
  }
});
Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;

var $break = { };

var Enumerable = {
  each: function(iterator, context) {
    var index = 0;
    try {
      this._each(function(value) {
        iterator.call(context, value, index++);
      });
    } catch (e) {
      if (e != $break) throw e;
    }
    return this;
  },

  eachSlice: function(number, iterator, context) {
    var index = -number, slices = [], array = this.toArray();
    if (number < 1) return array;
    while ((index += number) < array.length)
      slices.push(array.slice(index, index+number));
    return slices.collect(iterator, context);
  },

  all: function(iterator, context) {
    iterator = iterator || Prototype.K;
    var result = true;
    this.each(function(value, index) {
      result = result && !!iterator.call(context, value, index);
      if (!result) throw $break;
    });
    return result;
  },

  any: function(iterator, context) {
    iterator = iterator || Prototype.K;
    var result = false;
    this.each(function(value, index) {
      if (result = !!iterator.call(context, value, index))
        throw $break;
    });
    return result;
  },

  collect: function(iterator, context) {
    iterator = iterator || Prototype.K;
    var results = [];
    this.each(function(value, index) {
      results.push(iterator.call(context, value, index));
    });
    return results;
  },

  detect: function(iterator, context) {
    var result;
    this.each(function(value, index) {
      if (iterator.call(context, value, index)) {
        result = value;
        throw $break;
      }
    });
    return result;
  },

  findAll: function(iterator, context) {
    var results = [];
    this.each(function(value, index) {
      if (iterator.call(context, value, index))
        results.push(value);
    });
    return results;
  },

  grep: function(filter, iterator, context) {
    iterator = iterator || Prototype.K;
    var results = [];

    if (Object.isString(filter))
      filter = new RegExp(filter);

    this.each(function(value, index) {
      if (filter.match(value))
        results.push(iterator.call(context, value, index));
    });
    return results;
  },

  include: function(object) {
    if (Object.isFunction(this.indexOf))
      if (this.indexOf(object) != -1) return true;

    var found = false;
    this.each(function(value) {
      if (value == object) {
        found = true;
        throw $break;
      }
    });
    return found;
  },

  inGroupsOf: function(number, fillWith) {
    fillWith = Object.isUndefined(fillWith) ? null : fillWith;
    return this.eachSlice(number, function(slice) {
      while(slice.length < number) slice.push(fillWith);
      return slice;
    });
  },

  inject: function(memo, iterator, context) {
    this.each(function(value, index) {
      memo = iterator.call(context, memo, value, index);
    });
    return memo;
  },

  invoke: function(method) {
    var args = $A(arguments).slice(1);
    return this.map(function(value) {
      return value[method].apply(value, args);
    });
  },

  max: function(iterator, context) {
    iterator = iterator || Prototype.K;
    var result;
    this.each(function(value, index) {
      value = iterator.call(context, value, index);
      if (result == null || value >= result)
        result = value;
    });
    return result;
  },

  min: function(iterator, context) {
    iterator = iterator || Prototype.K;
    var result;
    this.each(function(value, index) {
      value = iterator.call(context, value, index);
      if (result == null || value < result)
        result = value;
    });
    return result;
  },

  partition: function(iterator, context) {
    iterator = iterator || Prototype.K;
    var trues = [], falses = [];
    this.each(function(value, index) {
      (iterator.call(context, value, index) ?
        trues : falses).push(value);
    });
    return [trues, falses];
  },

  pluck: function(property) {
    var results = [];
    this.each(function(value) {
      results.push(value[property]);
    });
    return results;
  },

  reject: function(iterator, context) {
    var results = [];
    this.each(function(value, index) {
      if (!iterator.call(context, value, index))
        results.push(value);
    });
    return results;
  },

  sortBy: function(iterator, context) {
    return this.map(function(value, index) {
      return {
        value: value,
        criteria: iterator.call(context, value, index)
      };
    }).sort(function(left, right) {
      var a = left.criteria, b = right.criteria;
      return a < b ? -1 : a > b ? 1 : 0;
    }).pluck('value');
  },

  toArray: function() {
    return this.map();
  },

  zip: function() {
    var iterator = Prototype.K, args = $A(arguments);
    if (Object.isFunction(args.last()))
      iterator = args.pop();

    var collections = [this].concat(args).map($A);
    return this.map(function(value, index) {
      return iterator(collections.pluck(index));
    });
  },

  size: function() {
    return this.toArray().length;
  },

  inspect: function() {
    return '#<Enumerable:' + this.toArray().inspect() + '>';
  }
};

Object.extend(Enumerable, {
  map:     Enumerable.collect,
  find:    Enumerable.detect,
  select:  Enumerable.findAll,
  filter:  Enumerable.findAll,
  member:  Enumerable.include,
  entries: Enumerable.toArray,
  every:   Enumerable.all,
  some:    Enumerable.any
});
function $A(iterable) {
  if (!iterable) return [];
  if (iterable.toArray) return iterable.toArray();
  var length = iterable.length || 0, results = new Array(length);
  while (length--) results[length] = iterable[length];
  return results;
}

if (Prototype.Browser.WebKit) {
  $A = function(iterable) {
    if (!iterable) return [];
    // In Safari, only use the `toArray` method if it's not a NodeList.
    // A NodeList is a function, has an function `item` property, and a numeric
    // `length` property. Adapted from Google Doctype.
    if (!(typeof iterable === 'function' && typeof iterable.length ===
        'number' && typeof iterable.item === 'function') && iterable.toArray)
      return iterable.toArray();
    var length = iterable.length || 0, results = new Array(length);
    while (length--) results[length] = iterable[length];
    return results;
  };
}

Array.from = $A;

Object.extend(Array.prototype, Enumerable);

if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse;

Object.extend(Array.prototype, {
  _each: function(iterator) {
    for (var i = 0, length = this.length; i < length; i++)
      iterator(this[i]);
  },

  clear: function() {
    this.length = 0;
    return this;
  },

  first: function() {
    return this[0];
  },

  last: function() {
    return this[this.length - 1];
  },

  compact: function() {
    return this.select(function(value) {
      return value != null;
    });
  },

  flatten: function() {
    return this.inject([], function(array, value) {
      return array.concat(Object.isArray(value) ?
        value.flatten() : [value]);
    });
  },

  without: function() {
    var values = $A(arguments);
    return this.select(function(value) {
      return !values.include(value);
    });
  },

  reverse: function(inline) {
    return (inline !== false ? this : this.toArray())._reverse();
  },

  reduce: function() {
    return this.length > 1 ? this : this[0];
  },

  uniq: function(sorted) {
    return this.inject([], function(array, value, index) {
      if (0 == index || (sorted ? array.last() != value : !array.include(value)))
        array.push(value);
      return array;
    });
  },

  intersect: function(array) {
    return this.uniq().findAll(function(item) {
      return array.detect(function(value) { return item === value });
    });
  },

  clone: function() {
    return [].concat(this);
  },

  size: function() {
    return this.length;
  },

  inspect: function() {
    return '[' + this.map(Object.inspect).join(', ') + ']';
  },

  toJSON: function() {
    var results = [];
    this.each(function(object) {
      var value = Object.toJSON(object);
      if (!Object.isUndefined(value)) results.push(value);
    });
    return '[' + results.join(', ') + ']';
  }
});

// use native browser JS 1.6 implementation if available
if (Object.isFunction(Array.prototype.forEach))
  Array.prototype._each = Array.prototype.forEach;

if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) {
  i || (i = 0);
  var length = this.length;
  if (i < 0) i = length + i;
  for (; i < length; i++)
    if (this[i] === item) return i;
  return -1;
};

if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) {
  i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;
  var n = this.slice(0, i).reverse().indexOf(item);
  return (n < 0) ? n : i - n - 1;
};

Array.prototype.toArray = Array.prototype.clone;

function $w(string) {
  if (!Object.isString(string)) return [];
  string = string.strip();
  return string ? string.split(/\s+/) : [];
}

if (Prototype.Browser.Opera){
  Array.prototype.concat = function() {
    var array = [];
    for (var i = 0, length = this.length; i < length; i++) array.push(this[i]);
    for (var i = 0, length = arguments.length; i < length; i++) {
      if (Object.isArray(arguments[i])) {
        for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)
          array.push(arguments[i][j]);
      } else {
        array.push(arguments[i]);
      }
    }
    return array;
  };
}
Object.extend(Number.prototype, {
  toColorPart: function() {
    return this.toPaddedString(2, 16);
  },

  succ: function() {
    return this + 1;
  },

  times: function(iterator, context) {
    $R(0, this, true).each(iterator, context);
    return this;
  },

  toPaddedString: function(length, radix) {
    var string = this.toString(radix || 10);
    return '0'.times(length - string.length) + string;
  },

  toJSON: function() {
    return isFinite(this) ? this.toString() : 'null';
  }
});

$w('abs round ceil floor').each(function(method){
  Number.prototype[method] = Math[method].methodize();
});
function $H(object) {
  return new Hash(object);
};

var Hash = Class.create(Enumerable, (function() {

  function toQueryPair(key, value) {
    if (Object.isUndefined(value)) return key;
    return key + '=' + encodeURIComponent(String.interpret(value));
  }

  return {
    initialize: function(object) {
      this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
    },

    _each: function(iterator) {
      for (var key in this._object) {
        var value = this._object[key], pair = [key, value];
        pair.key = key;
        pair.value = value;
        iterator(pair);
      }
    },

    set: function(key, value) {
      return this._object[key] = value;
    },

    get: function(key) {
      // simulating poorly supported hasOwnProperty
      if (this._object[key] !== Object.prototype[key])
        return this._object[key];
    },

    unset: function(key) {
      var value = this._object[key];
      delete this._object[key];
      return value;
    },

    toObject: function() {
      return Object.clone(this._object);
    },

    keys: function() {
      return this.pluck('key');
    },

    values: function() {
      return this.pluck('value');
    },

    index: function(value) {
      var match = this.detect(function(pair) {
        return pair.value === value;
      });
      return match && match.key;
    },

    merge: function(object) {
      return this.clone().update(object);
    },

    update: function(object) {
      return new Hash(object).inject(this, function(result, pair) {
        result.set(pair.key, pair.value);
        return result;
      });
    },

    toQueryString: function() {
      return this.inject([], function(results, pair) {
        var key = encodeURIComponent(pair.key), values = pair.value;

        if (values && typeof values == 'object') {
          if (Object.isArray(values))
            return results.concat(values.map(toQueryPair.curry(key)));
        } else results.push(toQueryPair(key, values));
        return results;
      }).join('&');
    },

    inspect: function() {
      return '#<Hash:{' + this.map(function(pair) {
        return pair.map(Object.inspect).join(': ');
      }).join(', ') + '}>';
    },

    toJSON: function() {
      return Object.toJSON(this.toObject());
    },

    clone: function() {
      return new Hash(this);
    }
  }
})());

Hash.prototype.toTemplateReplacements = Hash.prototype.toObject;
Hash.from = $H;
var ObjectRange = Class.create(Enumerable, {
  initialize: function(start, end, exclusive) {
    this.start = start;
    this.end = end;
    this.exclusive = exclusive;
  },

  _each: function(iterator) {
    var value = this.start;
    while (this.include(value)) {
      iterator(value);
      value = value.succ();
    }
  },

  include: function(value) {
    if (value < this.start)
      return false;
    if (this.exclusive)
      return value < this.end;
    return value <= this.end;
  }
});

var $R = function(start, end, exclusive) {
  return new ObjectRange(start, end, exclusive);
};

var Ajax = {
  getTransport: function() {
    return Try.these(
      function() {return new XMLHttpRequest()},
      function() {return new ActiveXObject('Msxml2.XMLHTTP')},
      function() {return new ActiveXObject('Microsoft.XMLHTTP')}
    ) || false;
  },

  activeRequestCount: 0
};

Ajax.Responders = {
  responders: [],

  _each: function(iterator) {
    this.responders._each(iterator);
  },

  register: function(responder) {
    if (!this.include(responder))
      this.responders.push(responder);
  },

  unregister: function(responder) {
    this.responders = this.responders.without(responder);
  },

  dispatch: function(callback, request, transport, json) {
    this.each(function(responder) {
      if (Object.isFunction(responder[callback])) {
        try {
          responder[callback].apply(responder, [request, transport, json]);
        } catch (e) { }
      }
    });
  }
};

Object.extend(Ajax.Responders, Enumerable);

Ajax.Responders.register({
  onCreate:   function() { Ajax.activeRequestCount++ },
  onComplete: function() { Ajax.activeRequestCount-- }
});

Ajax.Base = Class.create({
  initialize: function(options) {
    this.options = {
      method:       'post',
      asynchronous: true,
      contentType:  'application/x-www-form-urlencoded',
      encoding:     'UTF-8',
      parameters:   '',
      evalJSON:     true,
      evalJS:       true
    };
    Object.extend(this.options, options || { });

    this.options.method = this.options.method.toLowerCase();

    if (Object.isString(this.options.parameters))
      this.options.parameters = this.options.parameters.toQueryParams();
    else if (Object.isHash(this.options.parameters))
      this.options.parameters = this.options.parameters.toObject();
  }
});

Ajax.Request = Class.create(Ajax.Base, {
  _complete: false,

  initialize: function($super, url, options) {
    $super(options);
    this.transport = Ajax.getTransport();
    this.request(url);
  },

  request: function(url) {
    this.url = url;
    this.method = this.options.method;
    var params = Object.clone(this.options.parameters);

    if (!['get', 'post'].include(this.method)) {
      // simulate other verbs over post
      params['_method'] = this.method;
      this.method = 'post';
    }

    this.parameters = params;

    if (params = Object.toQueryString(params)) {
      // when GET, append parameters to URL
      if (this.method == 'get')
        this.url += (this.url.include('?') ? '&' : '?') + params;
      else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
        params += '&_=';
    }

    try {
      var response = new Ajax.Response(this);
      if (this.options.onCreate) this.options.onCreate(response);
      Ajax.Responders.dispatch('onCreate', this, response);

      this.transport.open(this.method.toUpperCase(), this.url,
        this.options.asynchronous);

      if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);

      this.transport.onreadystatechange = this.onStateChange.bind(this);
      this.setRequestHeaders();

      this.body = this.method == 'post' ? (this.options.postBody || params) : null;
      this.transport.send(this.body);

      /* Force Firefox to handle ready state 4 for synchronous requests */
      if (!this.options.asynchronous && this.transport.overrideMimeType)
        this.onStateChange();

    }
    catch (e) {
      this.dispatchException(e);
    }
  },

  onStateChange: function() {
    var readyState = this.transport.readyState;
    if (readyState > 1 && !((readyState == 4) && this._complete))
      this.respondToReadyState(this.transport.readyState);
  },

  setRequestHeaders: function() {
    var headers = {
      'X-Requested-With': 'XMLHttpRequest',
      'X-Prototype-Version': Prototype.Version,
      'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
    };

    if (this.method == 'post') {
      headers['Content-type'] = this.options.contentType +
        (this.options.encoding ? '; charset=' + this.options.encoding : '');

      /* Force "Connection: close" for older Mozilla browsers to work
       * around a bug where XMLHttpRequest sends an incorrect
       * Content-length header. See Mozilla Bugzilla #246651.
       */
      if (this.transport.overrideMimeType &&
          (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
            headers['Connection'] = 'close';
    }

    // user-defined headers
    if (typeof this.options.requestHeaders == 'object') {
      var extras = this.options.requestHeaders;

      if (Object.isFunction(extras.push))
        for (var i = 0, length = extras.length; i < length; i += 2)
          headers[extras[i]] = extras[i+1];
      else
        $H(extras).each(function(pair) { headers[pair.key] = pair.value });
    }

    for (var name in headers)
      this.transport.setRequestHeader(name, headers[name]);
  },

  success: function() {
    var status = this.getStatus();
    return !status || (status >= 200 && status < 300);
  },

  getStatus: function() {
    try {
      return this.transport.status || 0;
    } catch (e) { return 0 }
  },

  respondToReadyState: function(readyState) {
    var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);

    if (state == 'Complete') {
      try {
        this._complete = true;
        (this.options['on' + response.status]
         || this.options['on' + (this.success() ? 'Success' : 'Failure')]
         || Prototype.emptyFunction)(response, response.headerJSON);
      } catch (e) {
        this.dispatchException(e);
      }

      var contentType = response.getHeader('Content-type');
      if (this.options.evalJS == 'force'
          || (this.options.evalJS && this.isSameOrigin() && contentType
          && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
        this.evalResponse();
    }

    try {
      (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON);
      Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON);
    } catch (e) {
      this.dispatchException(e);
    }

    if (state == 'Complete') {
      // avoid memory leak in MSIE: clean up
      this.transport.onreadystatechange = Prototype.emptyFunction;
    }
  },

  isSameOrigin: function() {
    var m = this.url.match(/^\s*https?:\/\/[^\/]*/);
    return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({
      protocol: location.protocol,
      domain: document.domain,
      port: location.port ? ':' + location.port : ''
    }));
  },

  getHeader: function(name) {
    try {
      return this.transport.getResponseHeader(name) || null;
    } catch (e) { return null }
  },

  evalResponse: function() {
    try {
      return eval((this.transport.responseText || '').unfilterJSON());
    } catch (e) {
      this.dispatchException(e);
    }
  },

  dispatchException: function(exception) {
    (this.options.onException || Prototype.emptyFunction)(this, exception);
    Ajax.Responders.dispatch('onException', this, exception);
  }
});

Ajax.Request.Events =
  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];

Ajax.Response = Class.create({
  initialize: function(request){
    this.request = request;
    var transport  = this.transport  = request.transport,
        readyState = this.readyState = transport.readyState;

    if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {
      this.status       = this.getStatus();
      this.statusText   = this.getStatusText();
      this.responseText = String.interpret(transport.responseText);
      this.headerJSON   = this._getHeaderJSON();
    }

    if(readyState == 4) {
      var xml = transport.responseXML;
      this.responseXML  = Object.isUndefined(xml) ? null : xml;
      this.responseJSON = this._getResponseJSON();
    }
  },

  status:      0,
  statusText: '',

  getStatus: Ajax.Request.prototype.getStatus,

  getStatusText: function() {
    try {
      return this.transport.statusText || '';
    } catch (e) { return '' }
  },

  getHeader: Ajax.Request.prototype.getHeader,

  getAllHeaders: function() {
    try {
      return this.getAllResponseHeaders();
    } catch (e) { return null }
  },

  getResponseHeader: function(name) {
    return this.transport.getResponseHeader(name);
  },

  getAllResponseHeaders: function() {
    return this.transport.getAllResponseHeaders();
  },

  _getHeaderJSON: function() {
    var json = this.getHeader('X-JSON');
    if (!json) return null;
    json = decodeURIComponent(escape(json));
    try {
      return json.evalJSON(this.request.options.sanitizeJSON ||
        !this.request.isSameOrigin());
    } catch (e) {
      this.request.dispatchException(e);
    }
  },

  _getResponseJSON: function() {
    var options = this.request.options;
    if (!options.evalJSON || (options.evalJSON != 'force' &&
      !(this.getHeader('Content-type') || '').include('application/json')) ||
        this.responseText.blank())
          return null;
    try {
      return this.responseText.evalJSON(options.sanitizeJSON ||
        !this.request.isSameOrigin());
    } catch (e) {
      this.request.dispatchException(e);
    }
  }
});

Ajax.Updater = Class.create(Ajax.Request, {
  initialize: function($super, container, url, options) {
    this.container = {
      success: (container.success || container),
      failure: (container.failure || (container.success ? null : container))
    };

    options = Object.clone(options);
    var onComplete = options.onComplete;
    options.onComplete = (function(response, json) {
      this.updateContent(response.responseText);
      if (Object.isFunction(onComplete)) onComplete(response, json);
    }).bind(this);

    $super(url, options);
  },

  updateContent: function(responseText) {
    var receiver = this.container[this.success() ? 'success' : 'failure'],
        options = this.options;

    if (!options.evalScripts) responseText = responseText.stripScripts();

    if (receiver = $(receiver)) {
      if (options.insertion) {
        if (Object.isString(options.insertion)) {
          var insertion = { }; insertion[options.insertion] = responseText;
          receiver.insert(insertion);
        }
        else options.insertion(receiver, responseText);
      }
      else receiver.update(responseText);
    }
  }
});

Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {
  initialize: function($super, container, url, options) {
    $super(options);
    this.onComplete = this.options.onComplete;

    this.frequency = (this.options.frequency || 2);
    this.decay = (this.options.decay || 1);

    this.updater = { };
    this.container = container;
    this.url = url;

    this.start();
  },

  start: function() {
    this.options.onComplete = this.updateComplete.bind(this);
    this.onTimerEvent();
  },

  stop: function() {
    this.updater.options.onComplete = undefined;
    clearTimeout(this.timer);
    (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
  },

  updateComplete: function(response) {
    if (this.options.decay) {
      this.decay = (response.responseText == this.lastText ?
        this.decay * this.options.decay : 1);

      this.lastText = response.responseText;
    }
    this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency);
  },

  onTimerEvent: function() {
    this.updater = new Ajax.Updater(this.container, this.url, this.options);
  }
});
function $(element) {
  if (arguments.length > 1) {
    for (var i = 0, elements = [], length = arguments.length; i < length; i++)
      elements.push($(arguments[i]));
    return elements;
  }
  if (Object.isString(element))
    element = document.getElementById(element);
  return Element.extend(element);
}

if (Prototype.BrowserFeatures.XPath) {
  document._getElementsByXPath = function(expression, parentElement) {
    var results = [];
    var query = document.evaluate(expression, $(parentElement) || document,
      null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
    for (var i = 0, length = query.snapshotLength; i < length; i++)
      results.push(Element.extend(query.snapshotItem(i)));
    return results;
  };
}

/*--------------------------------------------------------------------------*/

if (!window.Node) var Node = { };

if (!Node.ELEMENT_NODE) {
  // DOM level 2 ECMAScript Language Binding
  Object.extend(Node, {
    ELEMENT_NODE: 1,
    ATTRIBUTE_NODE: 2,
    TEXT_NODE: 3,
    CDATA_SECTION_NODE: 4,
    ENTITY_REFERENCE_NODE: 5,
    ENTITY_NODE: 6,
    PROCESSING_INSTRUCTION_NODE: 7,
    COMMENT_NODE: 8,
    DOCUMENT_NODE: 9,
    DOCUMENT_TYPE_NODE: 10,
    DOCUMENT_FRAGMENT_NODE: 11,
    NOTATION_NODE: 12
  });
}

(function() {
  var element = this.Element;
  this.Element = function(tagName, attributes) {
    attributes = attributes || { };
    tagName = tagName.toLowerCase();
    var cache = Element.cache;
    if (Prototype.Browser.IE && attributes.name) {
      tagName = '<' + tagName + ' name="' + attributes.name + '">';
      delete attributes.name;
      return Element.writeAttribute(document.createElement(tagName), attributes);
    }
    if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));
    return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);
  };
  Object.extend(this.Element, element || { });
  if (element) this.Element.prototype = element.prototype;
}).call(window);

Element.cache = { };

Element.Methods = {
  visible: function(element) {
    return $(element).style.display != 'none';
  },

  toggle: function(element) {
    element = $(element);
    Element[Element.visible(element) ? 'hide' : 'show'](element);
    return element;
  },

  hide: function(element) {
    element = $(element);
    element.style.display = 'none';
    return element;
  },

  show: function(element) {
    element = $(element);
    element.style.display = '';
    return element;
  },

  remove: function(element) {
    element = $(element);
    element.parentNode.removeChild(element);
    return element;
  },

  update: function(element, content) {
    element = $(element);
    if (content && content.toElement) content = content.toElement();
    if (Object.isElement(content)) return element.update().insert(content);
    content = Object.toHTML(content);
    element.innerHTML = content.stripScripts();
    content.evalScripts.bind(content).defer();
    return element;
  },

  replace: function(element, content) {
    element = $(element);
    if (content && content.toElement) content = content.toElement();
    else if (!Object.isElement(content)) {
      content = Object.toHTML(content);
      var range = element.ownerDocument.createRange();
      range.selectNode(element);
      content.evalScripts.bind(content).defer();
      content = range.createContextualFragment(content.stripScripts());
    }
    element.parentNode.replaceChild(content, element);
    return element;
  },

  insert: function(element, insertions) {
    element = $(element);

    if (Object.isString(insertions) || Object.isNumber(insertions) ||
        Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
          insertions = {bottom:insertions};

    var content, insert, tagName, childNodes;

    for (var position in insertions) {
      content  = insertions[position];
      position = position.toLowerCase();
      insert = Element._insertionTranslations[position];

      if (content && content.toElement) content = content.toElement();
      if (Object.isElement(content)) {
        insert(element, content);
        continue;
      }

      content = Object.toHTML(content);

      tagName = ((position == 'before' || position == 'after')
        ? element.parentNode : element).tagName.toUpperCase();

      childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts());

      if (position == 'top' || position == 'after') childNodes.reverse();
      childNodes.each(insert.curry(element));

      content.evalScripts.bind(content).defer();
    }

    return element;
  },

  wrap: function(element, wrapper, attributes) {
    element = $(element);
    if (Object.isElement(wrapper))
      $(wrapper).writeAttribute(attributes || { });
    else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes);
    else wrapper = new Element('div', wrapper);
    if (element.parentNode)
      element.parentNode.replaceChild(wrapper, element);
    wrapper.appendChild(element);
    return wrapper;
  },

  inspect: function(element) {
    element = $(element);
    var result = '<' + element.tagName.toLowerCase();
    $H({'id': 'id', 'className': 'class'}).each(function(pair) {
      var property = pair.first(), attribute = pair.last();
      var value = (element[property] || '').toString();
      if (value) result += ' ' + attribute + '=' + value.inspect(true);
    });
    return result + '>';
  },

  recursivelyCollect: function(element, property) {
    element = $(element);
    var elements = [];
    while (element = element[property])
      if (element.nodeType == 1)
        elements.push(Element.extend(element));
    return elements;
  },

  ancestors: function(element) {
    return $(element).recursivelyCollect('parentNode');
  },

  descendants: function(element) {
    return $(element).select("*");
  },

  firstDescendant: function(element) {
    element = $(element).firstChild;
    while (element && element.nodeType != 1) element = element.nextSibling;
    return $(element);
  },

  immediateDescendants: function(element) {
    if (!(element = $(element).firstChild)) return [];
    while (element && element.nodeType != 1) element = element.nextSibling;
    if (element) return [element].concat($(element).nextSiblings());
    return [];
  },

  previousSiblings: function(element) {
    return $(element).recursivelyCollect('previousSibling');
  },

  nextSiblings: function(element) {
    return $(element).recursivelyCollect('nextSibling');
  },

  siblings: function(element) {
    element = $(element);
    return element.previousSiblings().reverse().concat(element.nextSiblings());
  },

  match: function(element, selector) {
    if (Object.isString(selector))
      selector = new Selector(selector);
    return selector.match($(element));
  },

  up: function(element, expression, index) {
    element = $(element);
    if (arguments.length == 1) return $(element.parentNode);
    var ancestors = element.ancestors();
    return Object.isNumber(expression) ? ancestors[expression] :
      Selector.findElement(ancestors, expression, index);
  },

  down: function(element, expression, index) {
    element = $(element);
    if (arguments.length == 1) return element.firstDescendant();
    return Object.isNumber(expression) ? element.descendants()[expression] :
      Element.select(element, expression)[index || 0];
  },

  previous: function(element, expression, index) {
    element = $(element);
    if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
    var previousSiblings = element.previousSiblings();
    return Object.isNumber(expression) ? previousSiblings[expression] :
      Selector.findElement(previousSiblings, expression, index);
  },

  next: function(element, expression, index) {
    element = $(element);
    if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
    var nextSiblings = element.nextSiblings();
    return Object.isNumber(expression) ? nextSiblings[expression] :
      Selector.findElement(nextSiblings, expression, index);
  },

  select: function() {
    var args = $A(arguments), element = $(args.shift());
    return Selector.findChildElements(element, args);
  },

  adjacent: function() {
    var args = $A(arguments), element = $(args.shift());
    return Selector.findChildElements(element.parentNode, args).without(element);
  },

  identify: function(element) {
    element = $(element);
    var id = element.readAttribute('id'), self = arguments.callee;
    if (id) return id;
    do { id = 'anonymous_element_' + self.counter++ } while ($(id));
    element.writeAttribute('id', id);
    return id;
  },

  readAttribute: function(element, name) {
    element = $(element);
    if (Prototype.Browser.IE) {
      var t = Element._attributeTranslations.read;
      if (t.values[name]) return t.values[name](element, name);
      if (t.names[name]) name = t.names[name];
      if (name.include(':')) {
        return (!element.attributes || !element.attributes[name]) ? null :
         element.attributes[name].value;
      }
    }
    return element.getAttribute(name);
  },

  writeAttribute: function(element, name, value) {
    element = $(element);
    var attributes = { }, t = Element._attributeTranslations.write;

    if (typeof name == 'object') attributes = name;
    else attributes[name] = Object.isUndefined(value) ? true : value;

    for (var attr in attributes) {
      name = t.names[attr] || attr;
      value = attributes[attr];
      if (t.values[attr]) name = t.values[attr](element, value);
      
      if (value === false || value === null) {
        element.removeAttribute(name);
      }
      else if (value === true) {
        element.setAttribute(name, name);
      }
      else {
      	//-- 20090421.acn : workaround for msie 8, tested with msie 5.5, 6, 7, 8
      	// element.setAttribute(name, value);
      	if (name === 'className' && Prototype.Browser.IE && Prototype.BrowserFeatures.Version == 8) element.setAttribute('class', value);
      	else element.setAttribute(name, value);
      	//--
      }
      
    }
    return element;
  },

  getHeight: function(element) {
    return $(element).getDimensions().height;
  },

  getWidth: function(element) {
    return $(element).getDimensions().width;
  },

  classNames: function(element) {
    return new Element.ClassNames(element);
  },

  hasClassName: function(element, className) {
    if (!(element = $(element))) return;
    var elementClassName = element.className;
    return (elementClassName.length > 0 && (elementClassName == className ||
      new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
  },

  addClassName: function(element, className) {
    if (!(element = $(element))) return;
    if (!element.hasClassName(className))
      element.className += (element.className ? ' ' : '') + className;
    return element;
  },

  removeClassName: function(element, className) {
    if (!(element = $(element))) return;
    element.className = element.className.replace(
      new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip();
    return element;
  },

  toggleClassName: function(element, className) {
    if (!(element = $(element))) return;
    return element[element.hasClassName(className) ?
      'removeClassName' : 'addClassName'](className);
  },

  // removes whitespace-only text node children
  cleanWhitespace: function(element) {
    element = $(element);
    var node = element.firstChild;
    while (node) {
      var nextNode = node.nextSibling;
      if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
        element.removeChild(node);
      node = nextNode;
    }
    return element;
  },

  empty: function(element) {
    return $(element).innerHTML.blank();
  },

  descendantOf: function(element, ancestor) {
    element = $(element), ancestor = $(ancestor);

    if (element.compareDocumentPosition)
      return (element.compareDocumentPosition(ancestor) & 8) === 8;

    if (ancestor.contains)
      return ancestor.contains(element) && ancestor !== element;

    while (element = element.parentNode)
      if (element == ancestor) return true;

    return false;
  },

  scrollTo: function(element) {
    element = $(element);
    var pos = element.cumulativeOffset();
    window.scrollTo(pos[0], pos[1]);
    return element;
  },

  getStyle: function(element, style) {
    element = $(element);
    style = style == 'float' ? 'cssFloat' : style.camelize();
    var value = element.style[style];
    if (!value || value == 'auto') {
      var css = document.defaultView.getComputedStyle(element, null);
      value = css ? css[style] : null;
    }
    if (style == 'opacity') return value ? parseFloat(value) : 1.0;
    return value == 'auto' ? null : value;
  },

  getOpacity: function(element) {
    return $(element).getStyle('opacity');
  },

  setStyle: function(element, styles) {
    element = $(element);
    var elementStyle = element.style, match;
    if (Object.isString(styles)) {
      element.style.cssText += ';' + styles;
      return styles.include('opacity') ?
        element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element;
    }
    for (var property in styles)
      if (property == 'opacity') element.setOpacity(styles[property]);
      else
      	if (styles[property] != null && styles[property] != '') { // FIX #558 : https://prototype.lighthouseapp.com/projects/8886/tickets/558-ie-crash-on-elementsetstyle
        // alert('prototype.js setStyle()\nproperty: '+ property +'\nvalue: '+ styles[property] +'\nisNaN(): '+ (isNaN(styles[property]) ? 'NaN' : 'number'));
          elementStyle[(property == 'float' || property == 'cssFloat') ?
            (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') :
              property] = styles[property];
      	}

    return element;
  },

  setOpacity: function(element, value) {
    element = $(element);
    element.style.opacity = (value == 1 || value === '') ? '' :
      (value < 0.00001) ? 0 : value;
    return element;
  },
  
  getDimensions: function(element) { // 20100308.acn : getDimensions() from 1.6.1
    element = $(element);
    var display = Element.getStyle(element, 'display');
    
    if (display != 'none' && display != null) // Safari bug
      return {width: element.offsetWidth, height: element.offsetHeight};
      
    var els = element.style;
    var originalVisibility = els.visibility;
    var originalPosition = els.position;
    var originalDisplay = els.display;
    els.visibility = 'hidden';
    if (originalPosition != 'fixed') // Switching fixed to absolute causes issues in Safari
      els.position = 'absolute';
    els.display = 'block';
    var originalWidth = element.clientWidth;
    var originalHeight = element.clientHeight;
    els.display = originalDisplay;
    els.position = originalPosition;
    els.visibility = originalVisibility;
    return {width: originalWidth, height: originalHeight};
  },

  makePositioned: function(element) {
    element = $(element);
    var pos = Element.getStyle(element, 'position');
    if (pos == 'static' || !pos) {
      element._madePositioned = true;
      element.style.position = 'relative';
      // Opera returns the offset relative to the positioning context, when an
      // element is position relative but top and left have not been defined
      if (Prototype.Browser.Opera) {
        element.style.top = 0;
        element.style.left = 0;
      }
    }
    return element;
  },

  undoPositioned: function(element) {
    element = $(element);
    if (element._madePositioned) {
      element._madePositioned = undefined;
      element.style.position =
        element.style.top =
        element.style.left =
        element.style.bottom =
        element.style.right = '';
    }
    return element;
  },

  makeClipping: function(element) {
    element = $(element);
    if (element._overflow) return element;
    element._overflow = Element.getStyle(element, 'overflow') || 'auto';
    if (element._overflow !== 'hidden')
      element.style.overflow = 'hidden';
    return element;
  },

  undoClipping: function(element) {
    element = $(element);
    if (!element._overflow) return element;
    element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
    element._overflow = null;
    return element;
  },

  cumulativeOffset: function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      element = element.offsetParent;
    } while (element);
    return Element._returnOffset(valueL, valueT);
  },

  positionedOffset: function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      element = element.offsetParent;
      if (element) {
        if (element.tagName.toUpperCase() == 'BODY') break;
        var p = Element.getStyle(element, 'position');
        if (p !== 'static') break;
      }
    } while (element);
    return Element._returnOffset(valueL, valueT);
  },

  absolutize: function(element) {
    element = $(element);
    if (element.getStyle('position') == 'absolute') return element;
    // Position.prepare(); // To be done manually by Scripty when it needs it.

    var offsets = element.positionedOffset();
    var top     = offsets[1];
    var left    = offsets[0];
    var width   = element.clientWidth;
    var height  = element.clientHeight;

    element._originalLeft   = left - parseFloat(element.style.left  || 0);
    element._originalTop    = top  - parseFloat(element.style.top || 0);
    element._originalWidth  = element.style.width;
    element._originalHeight = element.style.height;

    element.style.position = 'absolute';
    element.style.top    = top + 'px';
    element.style.left   = left + 'px';
    element.style.width  = width + 'px';
    element.style.height = height + 'px';
    return element;
  },

  relativize: function(element) {
    element = $(element);
    if (element.getStyle('position') == 'relative') return element;
    // Position.prepare(); // To be done manually by Scripty when it needs it.

    element.style.position = 'relative';
    var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);
    var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);

    element.style.top    = top + 'px';
    element.style.left   = left + 'px';
    element.style.height = element._originalHeight;
    element.style.width  = element._originalWidth;
    return element;
  },

  cumulativeScrollOffset: function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.scrollTop  || 0;
      valueL += element.scrollLeft || 0;
      element = element.parentNode;
    } while (element);
    return Element._returnOffset(valueL, valueT);
  },

  getOffsetParent: function(element) {
    if (element.offsetParent) return $(element.offsetParent);
    if (element == document.body) return $(element);

    while ((element = element.parentNode) && element != document.body)
      if (Element.getStyle(element, 'position') != 'static')
        return $(element);

    return $(document.body);
  },

  viewportOffset: function(forElement) {
    var valueT = 0, valueL = 0;

    var element = forElement;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;

      // Safari fix
      if (element.offsetParent == document.body &&
        Element.getStyle(element, 'position') == 'absolute') break;

    } while (element = element.offsetParent);

    element = forElement;
    do {
      if (!Prototype.Browser.Opera || (element.tagName && (element.tagName.toUpperCase() == 'BODY'))) {
        valueT -= element.scrollTop  || 0;
        valueL -= element.scrollLeft || 0;
      }
    } while (element = element.parentNode);

    return Element._returnOffset(valueL, valueT);
  },

  clonePosition: function(element, source) {
    var options = Object.extend({
      setLeft:    true,
      setTop:     true,
      setWidth:   true,
      setHeight:  true,
      offsetTop:  0,
      offsetLeft: 0
    }, arguments[2] || { });

    // find page position of source
    source = $(source);
    var p = source.viewportOffset();

    // find coordinate system to use
    element = $(element);
    var delta = [0, 0];
    var parent = null;
    // delta [0,0] will do fine with position: fixed elements,
    // position:absolute needs offsetParent deltas
    if (Element.getStyle(element, 'position') == 'absolute') {
      parent = element.getOffsetParent();
      delta = parent.viewportOffset();
    }

    // correct by body offsets (fixes Safari)
    if (parent == document.body) {
      delta[0] -= document.body.offsetLeft;
      delta[1] -= document.body.offsetTop;
    }

    // set position
    if (options.setLeft)   element.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
    if (options.setTop)    element.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
    if (options.setWidth)  element.style.width = source.offsetWidth + 'px';
    if (options.setHeight) element.style.height = source.offsetHeight + 'px';
    return element;
  }
};

Element.Methods.identify.counter = 1;

Object.extend(Element.Methods, {
  getElementsBySelector: Element.Methods.select,
  childElements: Element.Methods.immediateDescendants
});

Element._attributeTranslations = {
  write: {
    names: {
      className: 'class',
      htmlFor:   'for'
    },
    values: { }
  }
};

if (Prototype.Browser.Opera) {
  Element.Methods.getStyle = Element.Methods.getStyle.wrap(
    function(proceed, element, style) {
      switch (style) {
        case 'left': case 'top': case 'right': case 'bottom':
          if (proceed(element, 'position') === 'static') return null;
        case 'height': case 'width':
          // returns '0px' for hidden elements; we want it to return null
          if (!Element.visible(element)) return null;

          // returns the border-box dimensions rather than the content-box
          // dimensions, so we subtract padding and borders from the value
          var dim = parseInt(proceed(element, style), 10);

          if (dim !== element['offset' + style.capitalize()])
            return dim + 'px';

          var properties;
          if (style === 'height') {
            properties = ['border-top-width', 'padding-top',
             'padding-bottom', 'border-bottom-width'];
          }
          else {
            properties = ['border-left-width', 'padding-left',
             'padding-right', 'border-right-width'];
          }
          return properties.inject(dim, function(memo, property) {
            var val = proceed(element, property);
            return val === null ? memo : memo - parseInt(val, 10);
          }) + 'px';
        default: return proceed(element, style);
      }
    }
  );

  Element.Methods.readAttribute = Element.Methods.readAttribute.wrap(
    function(proceed, element, attribute) {
      if (attribute === 'title') return element.title;
      return proceed(element, attribute);
    }
  );
}

else if (Prototype.Browser.IE) {
  // IE doesn't report offsets correctly for static elements, so we change them
  // to "relative" to get the values, then change them back.
  Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap(
    function(proceed, element) {
      element = $(element);
      // IE throws an error if element is not in document
      try { element.offsetParent }
      catch(e) { return $(document.body) }
      var position = element.getStyle('position');
      if (position !== 'static') return proceed(element);
      element.setStyle({ position: 'relative' });
      var value = proceed(element);
      element.setStyle({ position: position });
      return value;
    }
  );

  $w('positionedOffset viewportOffset').each(function(method) {
    Element.Methods[method] = Element.Methods[method].wrap(
      function(proceed, element) {
        element = $(element);
        try { element.offsetParent }
        catch(e) { return Element._returnOffset(0,0) }
        var position = element.getStyle('position');
        if (position !== 'static') return proceed(element);
        // Trigger hasLayout on the offset parent so that IE6 reports
        // accurate offsetTop and offsetLeft values for position: fixed.
        var offsetParent = element.getOffsetParent();
        if (offsetParent && offsetParent.getStyle('position') === 'fixed')
          offsetParent.setStyle({ zoom: 1 });
        element.setStyle({ position: 'relative' });
        var value = proceed(element);
        element.setStyle({ position: position });
        return value;
      }
    );
  });

  Element.Methods.cumulativeOffset = Element.Methods.cumulativeOffset.wrap(
    function(proceed, element) {
      try { element.offsetParent }
      catch(e) { return Element._returnOffset(0,0) }
      return proceed(element);
    }
  );

  Element.Methods.getStyle = function(element, style) {
    element = $(element);
    style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
    var value = element.style[style];
    if (!value && element.currentStyle) value = element.currentStyle[style];

    if (style == 'opacity') {
      if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
        if (value[1]) return parseFloat(value[1]) / 100;
      return 1.0;
    }

    if (value == 'auto') {
      if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))
        return element['offset' + style.capitalize()] + 'px';
      return null;
    }
    return value;
  };

  Element.Methods.setOpacity = function(element, value) {
    function stripAlpha(filter){
      return filter.replace(/alpha\([^\)]*\)/gi,'');
    }
    element = $(element);
    var currentStyle = element.currentStyle;
    if ((currentStyle && !currentStyle.hasLayout) ||
      (!currentStyle && element.style.zoom == 'normal'))
        element.style.zoom = 1;

    var filter = element.getStyle('filter'), style = element.style;
    if (value == 1 || value === '') {
      (filter = stripAlpha(filter)) ?
        style.filter = filter : style.removeAttribute('filter');
      return element;
    } else if (value < 0.00001) value = 0;
    style.filter = stripAlpha(filter) +
      'alpha(opacity=' + (value * 100) + ')';
    return element;
  };

  Element._attributeTranslations = {
    read: {
      names: {
        'class': 'className',
        'for':   'htmlFor'
      },
      values: {
        _getAttr: function(element, attribute) {
          return element.getAttribute(attribute, 2);
        },
        _getAttrNode: function(element, attribute) {
          var node = element.getAttributeNode(attribute);
          return node ? node.value : "";
        },
        _getEv: function(element, attribute) {
          attribute = element.getAttribute(attribute);
          return attribute ? attribute.toString().slice(23, -2) : null;
        },
        _flag: function(element, attribute) {
          return $(element).hasAttribute(attribute) ? attribute : null;
        },
        style: function(element) {
          return element.style.cssText.toLowerCase();
        },
        title: function(element) {
          return element.title;
        }
      }
    }
  };

  Element._attributeTranslations.write = {
    names: Object.extend({
      cellpadding: 'cellPadding',
      cellspacing: 'cellSpacing'
    }, Element._attributeTranslations.read.names),
    values: {
      checked: function(element, value) {
        element.checked = !!value;
      },

      style: function(element, value) {
        element.style.cssText = value ? value : '';
      }
    }
  };

  Element._attributeTranslations.has = {};

  $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' +
      'encType maxLength readOnly longDesc frameBorder').each(function(attr) {
    Element._attributeTranslations.write.names[attr.toLowerCase()] = attr;
    Element._attributeTranslations.has[attr.toLowerCase()] = attr;
  });

  (function(v) {
    Object.extend(v, {
      href:        v._getAttr,
      src:         v._getAttr,
      type:        v._getAttr,
      action:      v._getAttrNode,
      disabled:    v._flag,
      checked:     v._flag,
      readonly:    v._flag,
      multiple:    v._flag,
      onload:      v._getEv,
      onunload:    v._getEv,
      onclick:     v._getEv,
      ondblclick:  v._getEv,
      onmousedown: v._getEv,
      onmouseup:   v._getEv,
      onmouseover: v._getEv,
      onmousemove: v._getEv,
      onmouseout:  v._getEv,
      onfocus:     v._getEv,
      onblur:      v._getEv,
      onkeypress:  v._getEv,
      onkeydown:   v._getEv,
      onkeyup:     v._getEv,
      onsubmit:    v._getEv,
      onreset:     v._getEv,
      onselect:    v._getEv,
      onchange:    v._getEv
    });
  })(Element._attributeTranslations.read.values);
}

else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) {
  Element.Methods.setOpacity = function(element, value) {
    element = $(element);
    element.style.opacity = (value == 1) ? 0.999999 :
      (value === '') ? '' : (value < 0.00001) ? 0 : value;
    return element;
  };
}

else if (Prototype.Browser.WebKit) {
  Element.Methods.setOpacity = function(element, value) {
    element = $(element);
    element.style.opacity = (value == 1 || value === '') ? '' :
      (value < 0.00001) ? 0 : value;

    if (value == 1)
      if(element.tagName.toUpperCase() == 'IMG' && element.width) {
        element.width++; element.width--;
      } else try {
        var n = document.createTextNode(' ');
        element.appendChild(n);
        element.removeChild(n);
      } catch (e) { }

    return element;
  };

  // Safari returns margins on body which is incorrect if the child is absolutely
  // positioned.  For performance reasons, redefine Element#cumulativeOffset for
  // KHTML/WebKit only.
  Element.Methods.cumulativeOffset = function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      if (element.offsetParent == document.body)
        if (Element.getStyle(element, 'position') == 'absolute') break;

      element = element.offsetParent;
    } while (element);

    return Element._returnOffset(valueL, valueT);
  };
}

if (Prototype.Browser.IE || Prototype.Browser.Opera) {
  // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements
  Element.Methods.update = function(element, content) {
    element = $(element);

    if (content && content.toElement) content = content.toElement();
    if (Object.isElement(content)) return element.update().insert(content);

    content = Object.toHTML(content);
    var tagName = element.tagName.toUpperCase();

    if (tagName in Element._insertionTranslations.tags) {
      $A(element.childNodes).each(function(node) { element.removeChild(node) });
      Element._getContentFromAnonymousElement(tagName, content.stripScripts())
        .each(function(node) { element.appendChild(node) });
    }
    else element.innerHTML = content.stripScripts();

    content.evalScripts.bind(content).defer();
    return element;
  };
}

if ('outerHTML' in document.createElement('div')) {
  Element.Methods.replace = function(element, content) {
    element = $(element);

    if (content && content.toElement) content = content.toElement();
    if (Object.isElement(content)) {
      element.parentNode.replaceChild(content, element);
      return element;
    }

    content = Object.toHTML(content);
    var parent = element.parentNode, tagName = parent.tagName.toUpperCase();

    if (Element._insertionTranslations.tags[tagName]) {
      var nextSibling = element.next();
      var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
      parent.removeChild(element);
      if (nextSibling)
        fragments.each(function(node) { parent.insertBefore(node, nextSibling) });
      else
        fragments.each(function(node) { parent.appendChild(node) });
    }
    else element.outerHTML = content.stripScripts();

    content.evalScripts.bind(content).defer();
    return element;
  };
}

Element._returnOffset = function(l, t) {
  var result = [l, t];
  result.left = l;
  result.top = t;
  return result;
};

Element._getContentFromAnonymousElement = function(tagName, html) {
  var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];
  if (t) {
    div.innerHTML = t[0] + html + t[1];
    t[2].times(function() { div = div.firstChild });
  } else div.innerHTML = html;
  return $A(div.childNodes);
};

Element._insertionTranslations = {
  before: function(element, node) {
    element.parentNode.insertBefore(node, element);
  },
  top: function(element, node) {
    element.insertBefore(node, element.firstChild);
  },
  bottom: function(element, node) {
    element.appendChild(node);
  },
  after: function(element, node) {
    element.parentNode.insertBefore(node, element.nextSibling);
  },
  tags: {
    TABLE:  ['<table>',                '</table>',                   1],
    TBODY:  ['<table><tbody>',         '</tbody></table>',           2],
    TR:     ['<table><tbody><tr>',     '</tr></tbody></table>',      3],
    TD:     ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],
    SELECT: ['<select>',               '</select>',                  1]
  }
};

(function() {
  Object.extend(this.tags, {
    THEAD: this.tags.TBODY,
    TFOOT: this.tags.TBODY,
    TH:    this.tags.TD
  });
}).call(Element._insertionTranslations);

Element.Methods.Simulated = {
  hasAttribute: function(element, attribute) {
    attribute = Element._attributeTranslations.has[attribute] || attribute;
    var node = $(element).getAttributeNode(attribute);
    return !!(node && node.specified);
  }
};

Element.Methods.ByTag = { };

Object.extend(Element, Element.Methods);

if (!Prototype.BrowserFeatures.ElementExtensions &&
    document.createElement('div')['__proto__']) {
  window.HTMLElement = { };
  window.HTMLElement.prototype = document.createElement('div')['__proto__'];
  Prototype.BrowserFeatures.ElementExtensions = true;
}

Element.extend = (function() {
  if (Prototype.BrowserFeatures.SpecificElementExtensions)
    return Prototype.K;

  var Methods = { }, ByTag = Element.Methods.ByTag;

  var extend = Object.extend(function(element) {
    if (!element || element._extendedByPrototype ||
        element.nodeType != 1 || element == window) return element;

    var methods = Object.clone(Methods),
      tagName = element.tagName.toUpperCase(), property, value;

    // extend methods for specific tags
    if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);

    for (property in methods) {
      value = methods[property];
      if (Object.isFunction(value) && !(property in element))
        element[property] = value.methodize();
    }

    element._extendedByPrototype = Prototype.emptyFunction;
    return element;

  }, {
    refresh: function() {
      // extend methods for all tags (Safari doesn't need this)
      if (!Prototype.BrowserFeatures.ElementExtensions) {
        Object.extend(Methods, Element.Methods);
        Object.extend(Methods, Element.Methods.Simulated);
      }
    }
  });

  extend.refresh();
  return extend;
})();

Element.hasAttribute = function(element, attribute) {
  if (element.hasAttribute) return element.hasAttribute(attribute);
  return Element.Methods.Simulated.hasAttribute(element, attribute);
};

Element.addMethods = function(methods) {
  var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;

  if (!methods) {
    Object.extend(Form, Form.Methods);
    Object.extend(Form.Element, Form.Element.Methods);
    Object.extend(Element.Methods.ByTag, {
      "FORM":     Object.clone(Form.Methods),
      "INPUT":    Object.clone(Form.Element.Methods),
      "SELECT":   Object.clone(Form.Element.Methods),
      "TEXTAREA": Object.clone(Form.Element.Methods)
    });
  }

  if (arguments.length == 2) {
    var tagName = methods;
    methods = arguments[1];
  }

  if (!tagName) Object.extend(Element.Methods, methods || { });
  else {
    if (Object.isArray(tagName)) tagName.each(extend);
    else extend(tagName);
  }

  function extend(tagName) {
    tagName = tagName.toUpperCase();
    if (!Element.Methods.ByTag[tagName])
      Element.Methods.ByTag[tagName] = { };
    Object.extend(Element.Methods.ByTag[tagName], methods);
  }

  function copy(methods, destination, onlyIfAbsent) {
    onlyIfAbsent = onlyIfAbsent || false;
    for (var property in methods) {
      var value = methods[property];
      if (!Object.isFunction(value)) continue;
      if (!onlyIfAbsent || !(property in destination))
        destination[property] = value.methodize();
    }
  }

  function findDOMClass(tagName) {
    var klass;
    var trans = {
      "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",
      "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",
      "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",
      "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",
      "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":
      "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":
      "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":
      "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":
      "FrameSet", "IFRAME": "IFrame"
    };
    if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';
    if (window[klass]) return window[klass];
    klass = 'HTML' + tagName + 'Element';
    if (window[klass]) return window[klass];
    klass = 'HTML' + tagName.capitalize() + 'Element';
    if (window[klass]) return window[klass];

    window[klass] = { };
    window[klass].prototype = document.createElement(tagName)['__proto__'];
    return window[klass];
  }

  if (F.ElementExtensions) {
    copy(Element.Methods, HTMLElement.prototype);
    copy(Element.Methods.Simulated, HTMLElement.prototype, true);
  }

  if (F.SpecificElementExtensions) {
    for (var tag in Element.Methods.ByTag) {
      var klass = findDOMClass(tag);
      if (Object.isUndefined(klass)) continue;
      copy(T[tag], klass.prototype);
    }
  }

  Object.extend(Element, Element.Methods);
  delete Element.ByTag;

  if (Element.extend.refresh) Element.extend.refresh();
  Element.cache = { };
};

document.viewport = {
  getDimensions: function() {
    var dimensions = { }, B = Prototype.Browser;
    $w('width height').each(function(d) {
      var D = d.capitalize();
      if (B.WebKit && !document.evaluate) {
        // Safari <3.0 needs self.innerWidth/Height
        dimensions[d] = self['inner' + D];
      } else if (B.Opera && parseFloat(window.opera.version()) < 9.5) {
        // Opera <9.5 needs document.body.clientWidth/Height
        dimensions[d] = document.body['client' + D]
      } else {
        dimensions[d] = document.documentElement['client' + D];
      }
    });
    return dimensions;
  },

  getWidth: function() {
    return this.getDimensions().width;
  },

  getHeight: function() {
    return this.getDimensions().height;
  },

  getScrollOffsets: function() {
    return Element._returnOffset(
      window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
      window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
  }
};
/* Portions of the Selector class are derived from Jack Slocum's DomQuery,
 * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
 * license.  Please see http://www.yui-ext.com/ for more information. */

var Selector = Class.create({
  initialize: function(expression) {
    this.expression = expression.strip();

    if (this.shouldUseSelectorsAPI()) {
      this.mode = 'selectorsAPI';
    } else if (this.shouldUseXPath()) {
      this.mode = 'xpath';
      this.compileXPathMatcher();
    } else {
      this.mode = "normal";
      this.compileMatcher();
    }

  },

  shouldUseXPath: function() {
    if (!Prototype.BrowserFeatures.XPath) return false;

    var e = this.expression;

    // Safari 3 chokes on :*-of-type and :empty
    if (Prototype.Browser.WebKit &&
     (e.include("-of-type") || e.include(":empty")))
      return false;

    // XPath can't do namespaced attributes, nor can it read
    // the "checked" property from DOM nodes
    if ((/(\[[\w-]*?:|:checked)/).test(e))
      return false;

    return true;
  },

  shouldUseSelectorsAPI: function() {
    if (!Prototype.BrowserFeatures.SelectorsAPI) return false;

    if (!Selector._div) Selector._div = new Element('div');

    // Make sure the browser treats the selector as valid. Test on an
    // isolated element to minimize cost of this check.
    try {
      Selector._div.querySelector(this.expression);
    } catch(e) {
      return false;
    }

    return true;
  },

  compileMatcher: function() {
    var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
        c = Selector.criteria, le, p, m;

    if (Selector._cache[e]) {
      this.matcher = Selector._cache[e];
      return;
    }

    this.matcher = ["this.matcher = function(root) {",
                    "var r = root, h = Selector.handlers, c = false, n;"];

    while (e && le != e && (/\S/).test(e)) {
      le = e;
      for (var i in ps) {
        p = ps[i];
        if (m = e.match(p)) {
          this.matcher.push(Object.isFunction(c[i]) ? c[i](m) :
            new Template(c[i]).evaluate(m));
          e = e.replace(m[0], '');
          break;
        }
      }
    }

    this.matcher.push("return h.unique(n);\n}");
    eval(this.matcher.join('\n'));
    Selector._cache[this.expression] = this.matcher;
  },

  compileXPathMatcher: function() {
    var e = this.expression, ps = Selector.patterns,
        x = Selector.xpath, le, m;

    if (Selector._cache[e]) {
      this.xpath = Selector._cache[e]; return;
    }

    this.matcher = ['.//*'];
    while (e && le != e && (/\S/).test(e)) {
      le = e;
      for (var i in ps) {
        if (m = e.match(ps[i])) {
          this.matcher.push(Object.isFunction(x[i]) ? x[i](m) :
            new Template(x[i]).evaluate(m));
          e = e.replace(m[0], '');
          break;
        }
      }
    }

    this.xpath = this.matcher.join('');
    Selector._cache[this.expression] = this.xpath;
  },

  findElements: function(root) {
    root = root || document;
    var e = this.expression, results;

    switch (this.mode) {
      case 'selectorsAPI':
        // querySelectorAll queries document-wide, then filters to descendants
        // of the context element. That's not what we want.
        // Add an explicit context to the selector if necessary.
        if (root !== document) {
          var oldId = root.id, id = $(root).identify();
          e = "#" + id + " " + e;
        }

        results = $A(root.querySelectorAll(e)).map(Element.extend);
        root.id = oldId;

        return results;
      case 'xpath':
        return document._getElementsByXPath(this.xpath, root);
      default:
       return this.matcher(root);
    }
  },

  match: function(element) {
    this.tokens = [];

    var e = this.expression, ps = Selector.patterns, as = Selector.assertions;
    var le, p, m;

    while (e && le !== e && (/\S/).test(e)) {
      le = e;
      for (var i in ps) {
        p = ps[i];
        if (m = e.match(p)) {
          // use the Selector.assertions methods unless the selector
          // is too complex.
          if (as[i]) {
            this.tokens.push([i, Object.clone(m)]);
            e = e.replace(m[0], '');
          } else {
            // reluctantly do a document-wide search
            // and look for a match in the array
            return this.findElements(document).include(element);
          }
        }
      }
    }

    var match = true, name, matches;
    for (var i = 0, token; token = this.tokens[i]; i++) {
      name = token[0], matches = token[1];
      if (!Selector.assertions[name](element, matches)) {
        match = false; break;
      }
    }

    return match;
  },

  toString: function() {
    return this.expression;
  },

  inspect: function() {
    return "#<Selector:" + this.expression.inspect() + ">";
  }
});

Object.extend(Selector, {
  _cache: { },

  xpath: {
    descendant:   "//*",
    child:        "/*",
    adjacent:     "/following-sibling::*[1]",
    laterSibling: '/following-sibling::*',
    tagName:      function(m) {
      if (m[1] == '*') return '';
      return "[local-name()='" + m[1].toLowerCase() +
             "' or local-name()='" + m[1].toUpperCase() + "']";
    },
    className:    "[contains(concat(' ', @class, ' '), ' #{1} ')]",
    id:           "[@id='#{1}']",
    attrPresence: function(m) {
      m[1] = m[1].toLowerCase();
      return new Template("[@#{1}]").evaluate(m);
    },
    attr: function(m) {
      m[1] = m[1].toLowerCase();
      m[3] = m[5] || m[6];
      return new Template(Selector.xpath.operators[m[2]]).evaluate(m);
    },
    pseudo: function(m) {
      var h = Selector.xpath.pseudos[m[1]];
      if (!h) return '';
      if (Object.isFunction(h)) return h(m);
      return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);
    },
    operators: {
      '=':  "[@#{1}='#{3}']",
      '!=': "[@#{1}!='#{3}']",
      '^=': "[starts-with(@#{1}, '#{3}')]",
      '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",
      '*=': "[contains(@#{1}, '#{3}')]",
      '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",
      '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"
    },
    pseudos: {
      'first-child': '[not(preceding-sibling::*)]',
      'last-child':  '[not(following-sibling::*)]',
      'only-child':  '[not(preceding-sibling::* or following-sibling::*)]',
      'empty':       "[count(*) = 0 and (count(text()) = 0)]",
      'checked':     "[@checked]",
      'disabled':    "[(@disabled) and (@type!='hidden')]",
      'enabled':     "[not(@disabled) and (@type!='hidden')]",
      'not': function(m) {
        var e = m[6], p = Selector.patterns,
            x = Selector.xpath, le, v;

        var exclusion = [];
        while (e && le != e && (/\S/).test(e)) {
          le = e;
          for (var i in p) {
            if (m = e.match(p[i])) {
              v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m);
              exclusion.push("(" + v.substring(1, v.length - 1) + ")");
              e = e.replace(m[0], '');
              break;
            }
          }
        }
        return "[not(" + exclusion.join(" and ") + ")]";
      },
      'nth-child':      function(m) {
        return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);
      },
      'nth-last-child': function(m) {
        return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);
      },
      'nth-of-type':    function(m) {
        return Selector.xpath.pseudos.nth("position() ", m);
      },
      'nth-last-of-type': function(m) {
        return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m);
      },
      'first-of-type':  function(m) {
        m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m);
      },
      'last-of-type':   function(m) {
        m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m);
      },
      'only-of-type':   function(m) {
        var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);
      },
      nth: function(fragment, m) {
        var mm, formula = m[6], predicate;
        if (formula == 'even') formula = '2n+0';
        if (formula == 'odd')  formula = '2n+1';
        if (mm = formula.match(/^(\d+)$/)) // digit only
          return '[' + fragment + "= " + mm[1] + ']';
        if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
          if (mm[1] == "-") mm[1] = -1;
          var a = mm[1] ? Number(mm[1]) : 1;
          var b = mm[2] ? Number(mm[2]) : 0;
          predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " +
          "((#{fragment} - #{b}) div #{a} >= 0)]";
          return new Template(predicate).evaluate({
            fragment: fragment, a: a, b: b });
        }
      }
    }
  },

  criteria: {
    tagName:      'n = h.tagName(n, r, "#{1}", c);      c = false;',
    className:    'n = h.className(n, r, "#{1}", c);    c = false;',
    id:           'n = h.id(n, r, "#{1}", c);           c = false;',
    attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;',
    attr: function(m) {
      m[3] = (m[5] || m[6]);
      return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m);
    },
    pseudo: function(m) {
      if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
      return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m);
    },
    descendant:   'c = "descendant";',
    child:        'c = "child";',
    adjacent:     'c = "adjacent";',
    laterSibling: 'c = "laterSibling";'
  },

  patterns: {
    // combinators must be listed first
    // (and descendant needs to be last combinator)
    laterSibling: /^\s*~\s*/,
    child:        /^\s*>\s*/,
    adjacent:     /^\s*\+\s*/,
    descendant:   /^\s/,

    // selectors follow
    tagName:      /^\s*(\*|[\w\-]+)(\b|$)?/,
    id:           /^#([\w\-\*]+)(\b|$)/,
    className:    /^\.([\w\-\*]+)(\b|$)/,
    pseudo:
/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/,
    attrPresence: /^\[((?:[\w]+:)?[\w]+)\]/,
    attr:         /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/
  },

  // for Selector.match and Element#match
  assertions: {
    tagName: function(element, matches) {
      return matches[1].toUpperCase() == element.tagName.toUpperCase();
    },

    className: function(element, matches) {
      return Element.hasClassName(element, matches[1]);
    },

    id: function(element, matches) {
      return element.id === matches[1];
    },

    attrPresence: function(element, matches) {
      return Element.hasAttribute(element, matches[1]);
    },

    attr: function(element, matches) {
      var nodeValue = Element.readAttribute(element, matches[1]);
      return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]);
    }
  },

  handlers: {
    // UTILITY FUNCTIONS
    // joins two collections
    concat: function(a, b) {
      for (var i = 0, node; node = b[i]; i++)
        a.push(node);
      return a;
    },

    // marks an array of nodes for counting
    mark: function(nodes) {
      var _true = Prototype.emptyFunction;
      for (var i = 0, node; node = nodes[i]; i++)
        node._countedByPrototype = _true;
      return nodes;
    },

    unmark: function(nodes) {
      for (var i = 0, node; node = nodes[i]; i++)
        node._countedByPrototype = undefined;
      return nodes;
    },

    // mark each child node with its position (for nth calls)
    // "ofType" flag indicates whether we're indexing for nth-of-type
    // rather than nth-child
    index: function(parentNode, reverse, ofType) {
      parentNode._countedByPrototype = Prototype.emptyFunction;
      if (reverse) {
        for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
          var node = nodes[i];
          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
        }
      } else {
        for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
      }
    },

    // filters out duplicates and extends all nodes
    unique: function(nodes) {
      if (nodes.length == 0) return nodes;
      var results = [], n;
      for (var i = 0, l = nodes.length; i < l; i++)
        if (!(n = nodes[i])._countedByPrototype) {
          n._countedByPrototype = Prototype.emptyFunction;
          results.push(Element.extend(n));
        }
      return Selector.handlers.unmark(results);
    },

    // COMBINATOR FUNCTIONS
    descendant: function(nodes) {
      var h = Selector.handlers;
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        h.concat(results, node.getElementsByTagName('*'));
      return results;
    },

    child: function(nodes) {
      var h = Selector.handlers;
      for (var i = 0, results = [], node; node = nodes[i]; i++) {
        for (var j = 0, child; child = node.childNodes[j]; j++)
          if (child.nodeType == 1 && child.tagName != '!') results.push(child);
      }
      return results;
    },

    adjacent: function(nodes) {
      for (var i = 0, results = [], node; node = nodes[i]; i++) {
        var next = this.nextElementSibling(node);
        if (next) results.push(next);
      }
      return results;
    },

    laterSibling: function(nodes) {
      var h = Selector.handlers;
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        h.concat(results, Element.nextSiblings(node));
      return results;
    },

    nextElementSibling: function(node) {
      while (node = node.nextSibling)
        if (node.nodeType == 1) return node;
      return null;
    },

    previousElementSibling: function(node) {
      while (node = node.previousSibling)
        if (node.nodeType == 1) return node;
      return null;
    },

    // TOKEN FUNCTIONS
    tagName: function(nodes, root, tagName, combinator) {
      var uTagName = tagName.toUpperCase();
      var results = [], h = Selector.handlers;
      if (nodes) {
        if (combinator) {
          // fastlane for ordinary descendant combinators
          if (combinator == "descendant") {
            for (var i = 0, node; node = nodes[i]; i++)
              h.concat(results, node.getElementsByTagName(tagName));
            return results;
          } else nodes = this[combinator](nodes);
          if (tagName == "*") return nodes;
        }
        for (var i = 0, node; node = nodes[i]; i++)
          if (node.tagName.toUpperCase() === uTagName) results.push(node);
        return results;
      } else return root.getElementsByTagName(tagName);
    },

    id: function(nodes, root, id, combinator) {
      var targetNode = $(id), h = Selector.handlers;
      if (!targetNode) return [];
      if (!nodes && root == document) return [targetNode];
      if (nodes) {
        if (combinator) {
          if (combinator == 'child') {
            for (var i = 0, node; node = nodes[i]; i++)
              if (targetNode.parentNode == node) return [targetNode];
          } else if (combinator == 'descendant') {
            for (var i = 0, node; node = nodes[i]; i++)
              if (Element.descendantOf(targetNode, node)) return [targetNode];
          } else if (combinator == 'adjacent') {
            for (var i = 0, node; node = nodes[i]; i++)
              if (Selector.handlers.previousElementSibling(targetNode) == node)
                return [targetNode];
          } else nodes = h[combinator](nodes);
        }
        for (var i = 0, node; node = nodes[i]; i++)
          if (node == targetNode) return [targetNode];
        return [];
      }
      return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : [];
    },

    className: function(nodes, root, className, combinator) {
      if (nodes && combinator) nodes = this[combinator](nodes);
      return Selector.handlers.byClassName(nodes, root, className);
    },

    byClassName: function(nodes, root, className) {
      if (!nodes) nodes = Selector.handlers.descendant([root]);
      var needle = ' ' + className + ' ';
      for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {
        nodeClassName = node.className;
        if (nodeClassName.length == 0) continue;
        if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))
          results.push(node);
      }
      return results;
    },

    attrPresence: function(nodes, root, attr, combinator) {
      if (!nodes) nodes = root.getElementsByTagName("*");
      if (nodes && combinator) nodes = this[combinator](nodes);
      var results = [];
      for (var i = 0, node; node = nodes[i]; i++)
        if (Element.hasAttribute(node, attr)) results.push(node);
      return results;
    },

    attr: function(nodes, root, attr, value, operator, combinator) {
      if (!nodes) nodes = root.getElementsByTagName("*");
      if (nodes && combinator) nodes = this[combinator](nodes);
      var handler = Selector.operators[operator], results = [];
      for (var i = 0, node; node = nodes[i]; i++) {
        var nodeValue = Element.readAttribute(node, attr);
        if (nodeValue === null) continue;
        if (handler(nodeValue, value)) results.push(node);
      }
      return results;
    },

    pseudo: function(nodes, name, value, root, combinator) {
      if (nodes && combinator) nodes = this[combinator](nodes);
      if (!nodes) nodes = root.getElementsByTagName("*");
      return Selector.pseudos[name](nodes, value, root);
    }
  },

  pseudos: {
    'first-child': function(nodes, value, root) {
      for (var i = 0, results = [], node; node = nodes[i]; i++) {
        if (Selector.handlers.previousElementSibling(node)) continue;
          results.push(node);
      }
      return results;
    },
    'last-child': function(nodes, value, root) {
      for (var i = 0, results = [], node; node = nodes[i]; i++) {
        if (Selector.handlers.nextElementSibling(node)) continue;
          results.push(node);
      }
      return results;
    },
    'only-child': function(nodes, value, root) {
      var h = Selector.handlers;
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        if (!h.previousElementSibling(node) && !h.nextElementSibling(node))
          results.push(node);
      return results;
    },
    'nth-child':        function(nodes, formula, root) {
      return Selector.pseudos.nth(nodes, formula, root);
    },
    'nth-last-child':   function(nodes, formula, root) {
      return Selector.pseudos.nth(nodes, formula, root, true);
    },
    'nth-of-type':      function(nodes, formula, root) {
      return Selector.pseudos.nth(nodes, formula, root, false, true);
    },
    'nth-last-of-type': function(nodes, formula, root) {
      return Selector.pseudos.nth(nodes, formula, root, true, true);
    },
    'first-of-type':    function(nodes, formula, root) {
      return Selector.pseudos.nth(nodes, "1", root, false, true);
    },
    'last-of-type':     function(nodes, formula, root) {
      return Selector.pseudos.nth(nodes, "1", root, true, true);
    },
    'only-of-type':     function(nodes, formula, root) {
      var p = Selector.pseudos;
      return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);
    },

    // handles the an+b logic
    getIndices: function(a, b, total) {
      if (a == 0) return b > 0 ? [b] : [];
      return $R(1, total).inject([], function(memo, i) {
        if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);
        return memo;
      });
    },

    // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type
    nth: function(nodes, formula, root, reverse, ofType) {
      if (nodes.length == 0) return [];
      if (formula == 'even') formula = '2n+0';
      if (formula == 'odd')  formula = '2n+1';
      var h = Selector.handlers, results = [], indexed = [], m;
      h.mark(nodes);
      for (var i = 0, node; node = nodes[i]; i++) {
        if (!node.parentNode._countedByPrototype) {
          h.index(node.parentNode, reverse, ofType);
          indexed.push(node.parentNode);
        }
      }
      if (formula.match(/^\d+$/)) { // just a number
        formula = Number(formula);
        for (var i = 0, node; node = nodes[i]; i++)
          if (node.nodeIndex == formula) results.push(node);
      } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
        if (m[1] == "-") m[1] = -1;
        var a = m[1] ? Number(m[1]) : 1;
        var b = m[2] ? Number(m[2]) : 0;
        var indices = Selector.pseudos.getIndices(a, b, nodes.length);
        for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {
          for (var j = 0; j < l; j++)
            if (node.nodeIndex == indices[j]) results.push(node);
        }
      }
      h.unmark(nodes);
      h.unmark(indexed);
      return results;
    },

    'empty': function(nodes, value, root) {
      for (var i = 0, results = [], node; node = nodes[i]; i++) {
        // IE treats comments as element nodes
        if (node.tagName == '!' || node.firstChild) continue;
        results.push(node);
      }
      return results;
    },

    'not': function(nodes, selector, root) {
      var h = Selector.handlers, selectorType, m;
      var exclusions = new Selector(selector).findElements(root);
      h.mark(exclusions);
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        if (!node._countedByPrototype) results.push(node);
      h.unmark(exclusions);
      return results;
    },

    'enabled': function(nodes, value, root) {
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        if (!node.disabled && (!node.type || node.type !== 'hidden'))
          results.push(node);
      return results;
    },

    'disabled': function(nodes, value, root) {
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        if (node.disabled) results.push(node);
      return results;
    },

    'checked': function(nodes, value, root) {
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        if (node.checked) results.push(node);
      return results;
    }
  },

  operators: {
    '=':  function(nv, v) { return nv == v; },
    '!=': function(nv, v) { return nv != v; },
    '^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); },
    '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); },
    '*=': function(nv, v) { return nv == v || nv && nv.include(v); },
    '$=': function(nv, v) { return nv.endsWith(v); },
    '*=': function(nv, v) { return nv.include(v); },
    '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },
    '|=': function(nv, v) { return ('-' + (nv || "").toUpperCase() +
     '-').include('-' + (v || "").toUpperCase() + '-'); }
  },

  split: function(expression) {
    var expressions = [];
    expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
      expressions.push(m[1].strip());
    });
    return expressions;
  },

  matchElements: function(elements, expression) {
    var matches = $$(expression), h = Selector.handlers;
    h.mark(matches);
    for (var i = 0, results = [], element; element = elements[i]; i++)
      if (element._countedByPrototype) results.push(element);
    h.unmark(matches);
    return results;
  },

  findElement: function(elements, expression, index) {
    if (Object.isNumber(expression)) {
      index = expression; expression = false;
    }
    return Selector.matchElements(elements, expression || '*')[index || 0];
  },

  findChildElements: function(element, expressions) {
    expressions = Selector.split(expressions.join(','));
    var results = [], h = Selector.handlers;
    for (var i = 0, l = expressions.length, selector; i < l; i++) {
      selector = new Selector(expressions[i].strip());
      h.concat(results, selector.findElements(element));
    }
    return (l > 1) ? h.unique(results) : results;
  }
});

if (Prototype.Browser.IE) {
  Object.extend(Selector.handlers, {
    // IE returns comment nodes on getElementsByTagName("*").
    // Filter them out.
    concat: function(a, b) {
      for (var i = 0, node; node = b[i]; i++)
        if (node.tagName !== "!") a.push(node);
      return a;
    },

    // IE improperly serializes _countedByPrototype in (inner|outer)HTML.
    unmark: function(nodes) {
      for (var i = 0, node; node = nodes[i]; i++)
        node.removeAttribute('_countedByPrototype');
      return nodes;
    }
  });
}

function $$() {
  return Selector.findChildElements(document, $A(arguments));
}
var Form = {
  reset: function(form) {
    $(form).reset();
    return form;
  },

  serializeElements: function(elements, options) {
    if (typeof options != 'object') options = { hash: !!options };
    else if (Object.isUndefined(options.hash)) options.hash = true;
    var key, value, submitted = false, submit = options.submit;

    var data = elements.inject({ }, function(result, element) {
      if (!element.disabled && element.name) {
        key = element.name; value = $(element).getValue();
        if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted &&
            submit !== false && (!submit || key == submit) && (submitted = true)))) {
          if (key in result) {
            // a key is already present; construct an array of values
            if (!Object.isArray(result[key])) result[key] = [result[key]];
            result[key].push(value);
          }
          else result[key] = value;
        }
      }
      return result;
    });

    return options.hash ? data : Object.toQueryString(data);
  }
};

Form.Methods = {
  serialize: function(form, options) {
    return Form.serializeElements(Form.getElements(form), options);
  },

  getElements: function(form) {
    return $A($(form).getElementsByTagName('*')).inject([],
      function(elements, child) {
        if (Form.Element.Serializers[child.tagName.toLowerCase()])
          elements.push(Element.extend(child));
        return elements;
      }
    );
  },

  getInputs: function(form, typeName, name) {
    form = $(form);
    var inputs = form.getElementsByTagName('input');

    if (!typeName && !name) return $A(inputs).map(Element.extend);

    for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {
      var input = inputs[i];
      if ((typeName && input.type != typeName) || (name && input.name != name))
        continue;
      matchingInputs.push(Element.extend(input));
    }

    return matchingInputs;
  },

  disable: function(form) {
    form = $(form);
    Form.getElements(form).invoke('disable');
    return form;
  },

  enable: function(form) {
    form = $(form);
    Form.getElements(form).invoke('enable');
    return form;
  },

  findFirstElement: function(form) {
    var elements = $(form).getElements().findAll(function(element) {
      return 'hidden' != element.type && !element.disabled;
    });
    var firstByIndex = elements.findAll(function(element) {
      return element.hasAttribute('tabIndex') && element.tabIndex >= 0;
    }).sortBy(function(element) { return element.tabIndex }).first();

    return firstByIndex ? firstByIndex : elements.find(function(element) {
      return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
    });
  },

  focusFirstElement: function(form) {
    form = $(form);
    form.findFirstElement().activate();
    return form;
  },

  request: function(form, options) {
    form = $(form), options = Object.clone(options || { });

    var params = options.parameters, action = form.readAttribute('action') || '';
    if (action.blank()) action = window.location.href;
    options.parameters = form.serialize(true);

    if (params) {
      if (Object.isString(params)) params = params.toQueryParams();
      Object.extend(options.parameters, params);
    }

    if (form.hasAttribute('method') && !options.method)
      options.method = form.method;

    return new Ajax.Request(action, options);
  }
};

/*--------------------------------------------------------------------------*/

Form.Element = {
  focus: function(element) {
    $(element).focus();
    return element;
  },

  select: function(element) {
    $(element).select();
    return element;
  }
};

Form.Element.Methods = {
  serialize: function(element) {
    element = $(element);
    if (!element.disabled && element.name) {
      var value = element.getValue();
      if (value != undefined) {
        var pair = { };
        pair[element.name] = value;
        return Object.toQueryString(pair);
      }
    }
    return '';
  },

  getValue: function(element) {
    element = $(element);
    var method = element.tagName.toLowerCase();
    return Form.Element.Serializers[method](element);
  },

  setValue: function(element, value) {
    element = $(element);
    var method = element.tagName.toLowerCase();
    Form.Element.Serializers[method](element, value);
    return element;
  },

  clear: function(element) {
    $(element).value = '';
    return element;
  },

  present: function(element) {
    return $(element).value != '';
  },

  activate: function(element) {
    element = $(element);
    try {
      element.focus();
      if (element.select && (element.tagName.toLowerCase() != 'input' ||
          !['button', 'reset', 'submit'].include(element.type)))
        element.select();
    } catch (e) { }
    return element;
  },

  disable: function(element) {
    element = $(element);
    element.disabled = true;
    return element;
  },

  enable: function(element) {
    element = $(element);
    element.disabled = false;
    return element;
  }
};

/*--------------------------------------------------------------------------*/

var Field = Form.Element;
var $F = Form.Element.Methods.getValue;

/*--------------------------------------------------------------------------*/

Form.Element.Serializers = {
  input: function(element, value) {
    switch (element.type.toLowerCase()) {
      case 'checkbox':
      case 'radio':
        return Form.Element.Serializers.inputSelector(element, value);
      default:
        return Form.Element.Serializers.textarea(element, value);
    }
  },

  inputSelector: function(element, value) {
    if (Object.isUndefined(value)) return element.checked ? element.value : null;
    else element.checked = !!value;
  },

  textarea: function(element, value) {
    if (Object.isUndefined(value)) return element.value;
    else element.value = value;
  },

  select: function(element, value) {
    if (Object.isUndefined(value))
      return this[element.type == 'select-one' ?
        'selectOne' : 'selectMany'](element);
    else {
      var opt, currentValue, single = !Object.isArray(value);
      for (var i = 0, length = element.length; i < length; i++) {
        opt = element.options[i];
        currentValue = this.optionValue(opt);
        if (single) {
          if (currentValue == value) {
            opt.selected = true;
            return;
          }
        }
        else opt.selected = value.include(currentValue);
      }
    }
  },

  selectOne: function(element) {
    var index = element.selectedIndex;
    return index >= 0 ? this.optionValue(element.options[index]) : null;
  },

  selectMany: function(element) {
    var values, length = element.length;
    if (!length) return null;

    for (var i = 0, values = []; i < length; i++) {
      var opt = element.options[i];
      if (opt.selected) values.push(this.optionValue(opt));
    }
    return values;
  },

  optionValue: function(opt) {
    // extend element because hasAttribute may not be native
    return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
  }
};

/*--------------------------------------------------------------------------*/

Abstract.TimedObserver = Class.create(PeriodicalExecuter, {
  initialize: function($super, element, frequency, callback) {
    $super(callback, frequency);
    this.element   = $(element);
    this.lastValue = this.getValue();
  },

  execute: function() {
    var value = this.getValue();
    if (Object.isString(this.lastValue) && Object.isString(value) ?
        this.lastValue != value : String(this.lastValue) != String(value)) {
      this.callback(this.element, value);
      this.lastValue = value;
    }
  }
});

Form.Element.Observer = Class.create(Abstract.TimedObserver, {
  getValue: function() {
    return Form.Element.getValue(this.element);
  }
});

Form.Observer = Class.create(Abstract.TimedObserver, {
  getValue: function() {
    return Form.serialize(this.element);
  }
});

/*--------------------------------------------------------------------------*/

Abstract.EventObserver = Class.create({
  initialize: function(element, callback) {
    this.element  = $(element);
    this.callback = callback;

    this.lastValue = this.getValue();
    if (this.element.tagName.toLowerCase() == 'form')
      this.registerFormCallbacks();
    else
      this.registerCallback(this.element);
  },

  onElementEvent: function() {
    var value = this.getValue();
    if (this.lastValue != value) {
      this.callback(this.element, value);
      this.lastValue = value;
    }
  },

  registerFormCallbacks: function() {
    Form.getElements(this.element).each(this.registerCallback, this);
  },

  registerCallback: function(element) {
    if (element.type) {
      switch (element.type.toLowerCase()) {
        case 'checkbox':
        case 'radio':
          Event.observe(element, 'click', this.onElementEvent.bind(this));
          break;
        default:
          Event.observe(element, 'change', this.onElementEvent.bind(this));
          break;
      }
    }
  }
});

Form.Element.EventObserver = Class.create(Abstract.EventObserver, {
  getValue: function() {
    return Form.Element.getValue(this.element);
  }
});

Form.EventObserver = Class.create(Abstract.EventObserver, {
  getValue: function() {
    return Form.serialize(this.element);
  }
});
if (!window.Event) var Event = { };

Object.extend(Event, {
  KEY_BACKSPACE: 8,
  KEY_TAB:       9,
  KEY_RETURN:   13,
  KEY_ESC:      27,
  KEY_LEFT:     37,
  KEY_UP:       38,
  KEY_RIGHT:    39,
  KEY_DOWN:     40,
  KEY_DELETE:   46,
  KEY_HOME:     36,
  KEY_END:      35,
  KEY_PAGEUP:   33,
  KEY_PAGEDOWN: 34,
  KEY_INSERT:   45,

  cache: { },

  relatedTarget: function(event) {
    var element;
    switch(event.type) {
      case 'mouseover': element = event.fromElement; break;
      case 'mouseout':  element = event.toElement;   break;
      default: return null;
    }
    return Element.extend(element);
  }
});

Event.Methods = (function() {
  var isButton;

  if (Prototype.Browser.IE) {
    var buttonMap = { 0: 1, 1: 4, 2: 2 };
    isButton = function(event, code) {
      return event.button == buttonMap[code];
    };

  } else if (Prototype.Browser.WebKit) {
    isButton = function(event, code) {
      switch (code) {
        case 0: return event.which == 1 && !event.metaKey;
        case 1: return event.which == 1 && event.metaKey;
        default: return false;
      }
    };

  } else {
    isButton = function(event, code) {
      return event.which ? (event.which === code + 1) : (event.button === code);
    };
  }

  return {
    isLeftClick:   function(event) { return isButton(event, 0) },
    isMiddleClick: function(event) { return isButton(event, 1) },
    isRightClick:  function(event) { return isButton(event, 2) },

    element: function(event) {
      event = Event.extend(event);

      var node          = event.target,
          type          = event.type,
          currentTarget = event.currentTarget;

      if (currentTarget && currentTarget.tagName) {
        // Firefox screws up the "click" event when moving between radio buttons
        // via arrow keys. It also screws up the "load" and "error" events on images,
        // reporting the document as the target instead of the original image.
        if (type === 'load' || type === 'error' ||
          (type === 'click' && currentTarget.tagName.toLowerCase() === 'input'
            && currentTarget.type === 'radio'))
              node = currentTarget;
      }
      if (node.nodeType == Node.TEXT_NODE) node = node.parentNode;
      return Element.extend(node);
    },

    findElement: function(event, expression) {
      var element = Event.element(event);
      if (!expression) return element;
      var elements = [element].concat(element.ancestors());
      return Selector.findElement(elements, expression, 0);
    },

    pointer: function(event) {
      var docElement = document.documentElement,
      body = document.body || { scrollLeft: 0, scrollTop: 0 };
      return {
        x: event.pageX || (event.clientX +
          (docElement.scrollLeft || body.scrollLeft) -
          (docElement.clientLeft || 0)),
        y: event.pageY || (event.clientY +
          (docElement.scrollTop || body.scrollTop) -
          (docElement.clientTop || 0))
      };
    },

    pointerX: function(event) { return Event.pointer(event).x },
    pointerY: function(event) { return Event.pointer(event).y },

    stop: function(event) {
      Event.extend(event);
      event.preventDefault();
      event.stopPropagation();
      event.stopped = true;
    }
  };
})();

Event.extend = (function() {
  var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {
    m[name] = Event.Methods[name].methodize();
    return m;
  });

  if (Prototype.Browser.IE) {
    Object.extend(methods, {
      stopPropagation: function() { this.cancelBubble = true },
      preventDefault:  function() { this.returnValue = false },
      inspect: function() { return "[object Event]" }
    });

    return function(event) {
      if (!event) return false;
      if (event._extendedByPrototype) return event;

      event._extendedByPrototype = Prototype.emptyFunction;
      var pointer = Event.pointer(event);
      Object.extend(event, {
        target: event.srcElement,
        relatedTarget: Event.relatedTarget(event),
        pageX:  pointer.x,
        pageY:  pointer.y
      });
      return Object.extend(event, methods);
    };

  } else {
    Event.prototype = Event.prototype || document.createEvent("HTMLEvents")['__proto__'];
    Object.extend(Event.prototype, methods);
    return Prototype.K;
  }
})();

Object.extend(Event, (function() {
  var cache = Event.cache;

  function getEventID(element) {
    if (element._prototypeEventID) return element._prototypeEventID[0];
    arguments.callee.id = arguments.callee.id || 1;
    return element._prototypeEventID = [++arguments.callee.id];
  }

  function getDOMEventName(eventName) {
    if (eventName && eventName.include(':')) return "dataavailable";
    return eventName;
  }

  function getCacheForID(id) {
    return cache[id] = cache[id] || { };
  }

  function getWrappersForEventName(id, eventName) {
    var c = getCacheForID(id);
    return c[eventName] = c[eventName] || [];
  }

  function createWrapper(element, eventName, handler) {
    var id = getEventID(element);
    var c = getWrappersForEventName(id, eventName);
    if (c.pluck("handler").include(handler)) return false;

    var wrapper = function(event) {
      if (!Event || !Event.extend ||
        (event.eventName && event.eventName != eventName))
          return false;

      Event.extend(event);
      handler.call(element, event);
    };

    wrapper.handler = handler;
    c.push(wrapper);
    return wrapper;
  }

  function findWrapper(id, eventName, handler) {
    var c = getWrappersForEventName(id, eventName);
    return c.find(function(wrapper) { return wrapper.handler == handler });
  }

  function destroyWrapper(id, eventName, handler) {
    var c = getCacheForID(id);
    if (!c[eventName]) return false;
    c[eventName] = c[eventName].without(findWrapper(id, eventName, handler));
  }

  function destroyCache() {
    for (var id in cache)
      for (var eventName in cache[id])
        cache[id][eventName] = null;
  }


  // Internet Explorer needs to remove event handlers on page unload
  // in order to avoid memory leaks.
  if (window.attachEvent) {
    window.attachEvent("onunload", destroyCache);
  }

  // Safari has a dummy event handler on page unload so that it won't
  // use its bfcache. Safari <= 3.1 has an issue with restoring the "document"
  // object when page is returned to via the back button using its bfcache.
  if (Prototype.Browser.WebKit) {
    window.addEventListener('unload', Prototype.emptyFunction, false);
  }

  return {
    observe: function(element, eventName, handler) {
      element = $(element);
      var name = getDOMEventName(eventName);

      var wrapper = createWrapper(element, eventName, handler);
      if (!wrapper) return element;

      if (element.addEventListener) {
        element.addEventListener(name, wrapper, false);
      } else {
        element.attachEvent("on" + name, wrapper);
      }

      return element;
    },

    stopObserving: function(element, eventName, handler) {
      element = $(element);
      var id = getEventID(element), name = getDOMEventName(eventName);

      if (!handler && eventName) {
        getWrappersForEventName(id, eventName).each(function(wrapper) {
          element.stopObserving(eventName, wrapper.handler);
        });
        return element;

      } else if (!eventName) {
        Object.keys(getCacheForID(id)).each(function(eventName) {
          element.stopObserving(eventName);
        });
        return element;
      }

      var wrapper = findWrapper(id, eventName, handler);
      if (!wrapper) return element;

      if (element.removeEventListener) {
        element.removeEventListener(name, wrapper, false);
      } else {
        element.detachEvent("on" + name, wrapper);
      }

      destroyWrapper(id, eventName, handler);

      return element;
    },

    fire: function(element, eventName, memo) {
      element = $(element);
      if (element == document && document.createEvent && !element.dispatchEvent)
        element = document.documentElement;

      var event;
      if (document.createEvent) {
        event = document.createEvent("HTMLEvents");
        event.initEvent("dataavailable", true, true);
      } else {
        event = document.createEventObject();
        event.eventType = "ondataavailable";
      }

      event.eventName = eventName;
      event.memo = memo || { };

      if (document.createEvent) {
        element.dispatchEvent(event);
      } else {
        element.fireEvent(event.eventType, event);
      }

      return Event.extend(event);
    }
  };
})());

Object.extend(Event, Event.Methods);

Element.addMethods({
  fire:          Event.fire,
  observe:       Event.observe,
  stopObserving: Event.stopObserving
});

Object.extend(document, {
  fire:          Element.Methods.fire.methodize(),
  observe:       Element.Methods.observe.methodize(),
  stopObserving: Element.Methods.stopObserving.methodize(),
  loaded:        false
});

(function() {
  /* Support for the DOMContentLoaded event is based on work by Dan Webb,
     Matthias Miller, Dean Edwards and John Resig. */

  var timer;

  function fireContentLoadedEvent() {
    if (document.loaded) return;
    if (timer) window.clearInterval(timer);
    document.fire("dom:loaded");
    document.loaded = true;
  }

  if (document.addEventListener) {
    if (Prototype.Browser.WebKit) {
      timer = window.setInterval(function() {
        if (/loaded|complete/.test(document.readyState))
          fireContentLoadedEvent();
      }, 0);

      Event.observe(window, "load", fireContentLoadedEvent);

    } else {
      document.addEventListener("DOMContentLoaded",
        fireContentLoadedEvent, false);
    }

  } else {
    document.write("<script id=__onDOMContentLoaded defer src=//:><\/script>");
    $("__onDOMContentLoaded").onreadystatechange = function() {
      if (this.readyState == "complete") {
        this.onreadystatechange = null;
        fireContentLoadedEvent();
      }
    };
  }
})();
/*------------------------------- DEPRECATED -------------------------------*/

Hash.toQueryString = Object.toQueryString;

var Toggle = { display: Element.toggle };

Element.Methods.childOf = Element.Methods.descendantOf;

var Insertion = {
  Before: function(element, content) {
    return Element.insert(element, {before:content});
  },

  Top: function(element, content) {
    return Element.insert(element, {top:content});
  },

  Bottom: function(element, content) {
    return Element.insert(element, {bottom:content});
  },

  After: function(element, content) {
    return Element.insert(element, {after:content});
  }
};

var $continue = new Error('"throw $continue" is deprecated, use "return" instead');

// This should be moved to script.aculo.us; notice the deprecated methods
// further below, that map to the newer Element methods.
var Position = {
  // set to true if needed, warning: firefox performance problems
  // NOT neeeded for page scrolling, only if draggable contained in
  // scrollable elements
  includeScrollOffsets: false,

  // must be called before calling withinIncludingScrolloffset, every time the
  // page is scrolled
  prepare: function() {
    this.deltaX =  window.pageXOffset
                || document.documentElement.scrollLeft
                || document.body.scrollLeft
                || 0;
    this.deltaY =  window.pageYOffset
                || document.documentElement.scrollTop
                || document.body.scrollTop
                || 0;
  },

  // caches x/y coordinate pair to use with overlap
  within: function(element, x, y) {
    if (this.includeScrollOffsets)
      return this.withinIncludingScrolloffsets(element, x, y);
    this.xcomp = x;
    this.ycomp = y;
    this.offset = Element.cumulativeOffset(element);

    return (y >= this.offset[1] &&
            y <  this.offset[1] + element.offsetHeight &&
            x >= this.offset[0] &&
            x <  this.offset[0] + element.offsetWidth);
  },

  withinIncludingScrolloffsets: function(element, x, y) {
    var offsetcache = Element.cumulativeScrollOffset(element);

    this.xcomp = x + offsetcache[0] - this.deltaX;
    this.ycomp = y + offsetcache[1] - this.deltaY;
    this.offset = Element.cumulativeOffset(element);

    return (this.ycomp >= this.offset[1] &&
            this.ycomp <  this.offset[1] + element.offsetHeight &&
            this.xcomp >= this.offset[0] &&
            this.xcomp <  this.offset[0] + element.offsetWidth);
  },

  // within must be called directly before
  overlap: function(mode, element) {
    if (!mode) return 0;
    if (mode == 'vertical')
      return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
        element.offsetHeight;
    if (mode == 'horizontal')
      return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
        element.offsetWidth;
  },

  // Deprecation layer -- use newer Element methods now (1.5.2).

  cumulativeOffset: Element.Methods.cumulativeOffset,

  positionedOffset: Element.Methods.positionedOffset,

  absolutize: function(element) {
    Position.prepare();
    return Element.absolutize(element);
  },

  relativize: function(element) {
    Position.prepare();
    return Element.relativize(element);
  },

  realOffset: Element.Methods.cumulativeScrollOffset,

  offsetParent: Element.Methods.getOffsetParent,

  page: Element.Methods.viewportOffset,

  clone: function(source, target, options) {
    options = options || { };
    return Element.clonePosition(target, source, options);
  }
};

/*--------------------------------------------------------------------------*/

if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){
  function iter(name) {
    return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]";
  }

  instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ?
  function(element, className) {
    className = className.toString().strip();
    var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className);
    return cond ? document._getElementsByXPath('.//*' + cond, element) : [];
  } : function(element, className) {
    className = className.toString().strip();
    var elements = [], classNames = (/\s/.test(className) ? $w(className) : null);
    if (!classNames && !className) return elements;

    var nodes = $(element).getElementsByTagName('*');
    className = ' ' + className + ' ';

    for (var i = 0, child, cn; child = nodes[i]; i++) {
      if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) ||
          (classNames && classNames.all(function(name) {
            return !name.toString().blank() && cn.include(' ' + name + ' ');
          }))))
        elements.push(Element.extend(child));
    }
    return elements;
  };

  return function(className, parentElement) {
    return $(parentElement || document.body).getElementsByClassName(className);
  };
}(Element.Methods);

/*--------------------------------------------------------------------------*/

Element.ClassNames = Class.create();
Element.ClassNames.prototype = {
  initialize: function(element) {
    this.element = $(element);
  },

  _each: function(iterator) {
    this.element.className.split(/\s+/).select(function(name) {
      return name.length > 0;
    })._each(iterator);
  },

  set: function(className) {
    this.element.className = className;
  },

  add: function(classNameToAdd) {
    if (this.include(classNameToAdd)) return;
    this.set($A(this).concat(classNameToAdd).join(' '));
  },

  remove: function(classNameToRemove) {
    if (!this.include(classNameToRemove)) return;
    this.set($A(this).without(classNameToRemove).join(' '));
  },

  toString: function() {
    return $A(this).join(' ');
  }
};

Object.extend(Element.ClassNames.prototype, Enumerable);

/*--------------------------------------------------------------------------*/

Element.addMethods();

/* popup */

function pc3PopupManagement(){
	var self = this;
	
	this.designs = $H({});
	this.popups = $H({});
	this.hiddenSelects = new Array();
	this.triggers = $H({});
	this.openPopups = $H({});
	this.popup = $(pc3Widget.body);
	this.content = $(pc3Widget.body);
	this.id = 'management';
	this.parentNode	= '';
	
	this.init = function(){
		this.selects = this.content.select('select');
		this.initPopups();
	}

	this.initPopups = function(){
		var popupData = pc3PopupData;
		if ( !popupData.size() ) return;
		var popupDesignData = $H(pc3PopupDesignData);

		popupData.each(function(popup){
			var designId = popup.designId;
			if ( !self.designs.get(designId) && ( designId && $(designId) && popupDesignData.get(designId) )){
				self.designs.set(designId, new pc3PopupDesign(designId, popupDesignData.get(designId), self));
			}
			var trigger = $(popup.triggerId);
			if ( self.designs.get(designId) && trigger ){
				var popupId = popup.triggerId+"_"+designId;
				if ( !self.popups.get(popupId) ){
					self.popups.set(popupId, new pc3Popup(popupId, popup.triggerId, popup, self.designs.get(designId), self));
					self.triggers.set(popup.triggerId, self.popups.get(popupId));
				} else {
					self.popups.get(popupId).init();
				}
				self.popups.get(popupId).openInitially();
			}
		});
		pc3PopupData = new Array();
	}

	this.getParent = function(popup){
		if ( popup.parent ) return;
		var dataNode = $(popup.id+'Data');
		if ( !dataNode ) return;
		var parent = dataNode.up('.pc3popupdata');
		if ( parent ){
			parent = self.popups.get(parent.id.slice(0,(parent.id.length-4)));
		} else {
			parent = dataNode.up('[pc3popup]');
			if ( parent ) parent = parent.popup;
		}
		if ( !parent ) parent = this;
		return parent;
	}
		
	this.addOpenPopup = function(popup){
		if ( !this.openPopups.size() && Prototype.Browser.IE6 && this.selects ) {
			this.selects.each(function(select){
				if ( select.visible() ){
					self.hiddenSelects.push(select);
					select.style.visibility = "hidden";
				}
			});
		}
		this.openPopups.set(popup.id, popup);
	}

	this.removeOpenPopup = function(popup){
		var zIndex = 1000;
		this.openPopups.unset(popup.id);
		this.openPopups.each(function(currentPopup){
			currentPopup.value.setZIndex(zIndex);
			zIndex++;
		});
		if ( !this.openPopups.size() && Prototype.Browser.IE6 ) {
			this.hiddenSelects.each(function(select){ select.style.visibility = "visible"; });
			this.hiddenSelects = new Array();
		}
	}

	this.removePopup = function(popupId){
		this.popups.unset(popupId);
	}

	this.removeAjaxPopups = function(ajaxContent){
		this.triggers.each(function(currentTrigger){
			var ancestors = currentTrigger.value.trigger.ancestors();
			if ( !ancestors.size() || ancestors.last().tagName != 'HTML' ){
				if ( currentTrigger.value.parent ) currentTrigger.value.parent.removePopup(currentTrigger.value.id);
				else self.popups.unset(currentTrigger.value.id);
			}
		});
	}

	this.closeOtherOpenPopups = function(popup){
		if ( popup.groupname == 'pc3DefaultPopupGroup' ) return;
		this.openPopups.each(function(currentPopup){ if ( popup.id != currentPopup.key && popup.groupname == currentPopup.value.groupname ) currentPopup.value.close(''); });		
	}

	this.addPopup = function(popup){ if ( !this.popups.get(popup.id) ) this.popups.set(popup.id, popup); }
	
	this.doForAllOpenPopups = function(func){ this.openPopups.each(function(currentPopup){ func.bind(currentPopup.value)(); }); }

	this.doForAllPopups = function(func){ this.popups.each(function(currentPopup){ func.bind(currentPopup.value)(); }); }
	
	this.getZIndex = function(){ return (1000 + this.openPopups.size()); }
		
	this.isOpen = function(){ return true; }
	
	this.close = function(){ return false; }

	this.openPopup = function(trigger){
		var element = $(trigger);
		if ( typeof(trigger) == 'string' ){
			if ( element && element.popup ) element.popup.open('', '');
		} else {		
			var parent = element.up('[pc3Popup]');
			if ( parent ) parent = parent.popup;
			if ( parent ) parent.open('', '');
		}
	}


	this.closePopup = function(trigger){
		var element = $(trigger);
		if ( typeof(trigger) == 'string' ){
			if ( element && element.popup ) element.popup.close('');
		} else {		
			var parent = element.up('[pc3Popup]');
			if ( parent ) parent = parent.popup;
			if ( parent ) parent.close('');
		}
	}

	this.updatePopup = function(trigger){
		var element = $(trigger);
		if ( typeof(trigger) == 'string' ){
			if ( element && element.popup ) element.popup.update();
		} else {		
			var parent = element.up('[pc3Popup]');
			if ( parent ) parent = parent.popup;
			if ( parent ) parent.update();
		}
	}

	this.setClosePopupFunction = function(trigger, callBack){
		var element = $(trigger);
		if ( typeof(trigger) == 'string' ){
			if ( element && element.popup ) element.popup.setCloseFunction(callBack);
		} else {		
			var parent = element.up('[pc3Popup]');
			if ( parent ) parent = parent.popup;
			if ( parent ) parent.setCloseFunction(callBack);
		}
	}

	// Event handlers
		this.handleMouseMove = function(event){ this.doForAllOpenPopups(function(){ this.handleMouseMove(); }); }
	
		this.handleClick = function(event){ this.doForAllOpenPopups(function(){ this.handleClick(event); }); }
	
		this.handleResize = function(event){ this.doForAllOpenPopups(function(){ this.handleResize(); }); }
	
		this.handleScroll = function(event){ this.doForAllOpenPopups(function(){ this.handleScroll(); }); }
	//---------------
		
	this.init();
	
}


function pc3PopupDesign(designId, designData, management){
	var self = this;
	this.id = designId;
	this.management = management;
	
	//"behaviour","openTrigger","openTriggerDelay","openSizeAnimation","openAnimation","openPathAnimation",
	//"openEffect","openDuration","originPosition","closedPosition","closeTriggers","closeTriggerDelay","closeSizeAnimation","closeAnimation",
	//"closePathAnimation","closeEffect","closeDuration","endPosition","backgroundColor","backgroundOpacity","backgroundOpenAnimation",
	//"backgroundOpenEffect","backgroundCloseAnimation","backgroundCloseEffect","displayShadow","placeholders",
	//"cornerSize","shadowOffsetTop","shadowOffsetRight","shadowOffsetBottom","shadowOffsetLeft","shadowTop","shadowRight","shadowBottom","shadowLeft"

	this.init = function(designData){
		var designData = $H(designData);
		designData.each(function(attribute){ self[attribute.key] = attribute.value; });
		if ( this.shadow.enabled ){
			this.shadow.blurRadius = parseInt(this.shadow.blurRadius);
			this.shadow.marginLeft = parseInt(this.shadow.marginLeft);
			this.shadow.marginRight = parseInt(this.shadow.marginRight);
			this.shadow.marginTop = parseInt(this.shadow.marginTop);
			this.shadow.marginBottom = parseInt(this.shadow.marginBottom);
			this.shadow.cornerSize = parseInt(this.shadow.cornerSize);
			this.shadowImages = {};
			['Left','Top','Right','Bottom'].each(function(location){
				if ( !self.shadow['image'+location] ) return;
				self.shadowImages[location.toLowerCase()] = self.shadow['image'+location];
				var loader = new Image();
				loader.src = self.shadow['image'+location];
			});
		}
		this.template = $(this.id);
		this.template.id = '';
		this.width = this.template.getWidth();
		this.height = this.template.getHeight();
		this.opacity = pc3Widget.getOpacity(this.template) * 100;
		this.template.remove();
	}	

	this.init(designData);
}

function pc3Popup(popupId, triggerId, popupData, design, management){
	var self = this;
	this.id = popupId;
	this.design = design;
	
	this.management = management;
	this.popupData = $H(popupData);
	this.parent = '';
	this.waitingToOpen = '';
	this.waitingToClose = '';
	this.closeAfterOpen = '';
	this.openAfterClose = '';
	this.popup = '';
	this.hiddenSelects = new Array();
	this.closeCallbackFunction = '';	
	
	this.openedPosition = {x:0,y:0,width:0,height:0,marginLeft:0,marginRight:0,marginTop:0,marginBottom:0,factorWidth:0,factorHeight:0};
	this.closedPosition = {x:0,y:0,width:0,height:0,marginLeft:0,marginRight:0,marginTop:0,marginBottom:0,factorWidth:0,factorHeight:0};
	this.currentPosition = {x:0,y:0,width:0,height:0};
	this.lastSize = {width:0,height:0};
	this.effects = $H({});
	this.shadow = '';
	this.shadowOffsets = {offsetLeft:0, offsetTop:0, offsetRight:0, offsetBottom:0, offsetsX:0, offsetsY:0};
	this.eventLayer = '';
	this.popups = $H({});
	this.openPopups = $H({});
	this.background = '';
	this.ajaxRequest = false;
	this.triggerId = triggerId;
	this.postUpdate = false;

	this.flashvars = '';
		
	this.init = function(){
		this.trigger = $(this.triggerId);
		this.trigger[this.id] = 'closed';
		this.trigger.popup = this;
		this.groupname = this.popupData.get('groupName');
		if ( !this.groupname ) this.groupname = 'pc3DefaultPopupGroup';
		this.initTriggerEvents();		
		this.openTriggerDelay = parseFloat( this.design.open.delay ) || 0;
		this.closeTriggerDelay = parseFloat(this.design.close.delay) || 0;
		
		this.initialState = this.popupData.get('initialState');
		this.isFullscreen = this.popupData.get('fullscreen');
	}

	this.initTriggerEvents = function(){
		if ( this.popupData.get('application') != 'default' ) return;
		var openHandler = this.open.bindAsEventListener(this);
		if ( this.design.open.trigger == 'click' ) openHandler = this.handleClickOnTrigger.bindAsEventListener(this);
		Event.observe(this.trigger, this.design.open.trigger, openHandler);
		if ( this.design.close.trigger && this.design.close.trigger.indexOf('mouseout') >= 0 ) {
			Event.observe(this.trigger, 'mouseout', this.close.bindAsEventListener(this));
		}
	}

	this.initPopupEvents = function(){
		if ( this.popupData.get('application') != 'default' ) return;
		if ( this.design.close.trigger && this.design.close.trigger.indexOf('mouseout') >= 0 ) {
			Event.observe(this.popup, 'mouseout', this.close.bindAsEventListener(this));
			Event.observe(this.content, 'mouseout', this.close.bindAsEventListener(this));
		}
		
		if ( this.design.close.trigger && this.design.close.trigger.indexOf('clickInsidePopup') >= 0 ) {
			this.eventLayer = new Element('div').setStyle({position:'absolute',zIndex:3});
			Event.observe(this.eventLayer, 'click', this.close.bindAsEventListener(this));
			this.innerBox.appendChild(this.eventLayer);
		}
	}

	this.bindToParent = function(){
		this.parent = this.management.getParent(this);
		this.parent.addPopup(this);
	}
	
	this.initPopup = function(){
		this.outerBox = new Element('div').setStyle({position:'absolute', overflow:'hidden'}).hide();
		this.outerBox.popup = this;
		this.outerBox.widget = this;
		this.outerBox.writeAttribute({pc3Popup:1});
		this.outerBox.writeAttribute({pc3Widget:1});
		this.innerBox = new Element('div').setStyle({position:'relative'});
		this.outerBox.appendChild(this.innerBox);
		this.popup = this.design.template.cloneNode(true).removeClassName('pc3popupdesign').setStyle({position:'absolute',zIndex:1});
		this.innerBox.appendChild(this.popup);
		this.popup.outerDimension = pc3Widget.getOuterDimension(this.popup, false);
		this.content = new Element('div').setStyle({position:'relative'});

		this.contentBox = this.content.wrap('div').setStyle({position:'absolute', overflow:'hidden',zIndex:2});
		this.innerBox.appendChild(this.contentBox);

		//transfer childs of pupup design to content
		this.popup.childElements().each( function(item) {
			Element.insert(self.content, item); 
		});

		this.initPopupEvents();

		//populate popup
		var dataNode = $(this.id+'Data');
		var topDataNode = dataNode;
		
		if ( this.design.placeholders != null ){
			this.design.placeholders.each(function(name){ //loop through placeholders
				var dataElement = '';
				dataNode.childElements().each(function(child){ if ( child.className == name ) dataElement = child; });		
				var targetElement = self.content.down('.'+name);
				if ( targetElement ) targetElement.removeClassName(name);
				if ( targetElement && dataElement ) dataElement.childElements().each(function(child){ targetElement.appendChild(child); });
				else if ( targetElement ) targetElement.remove();
			});
		}
		
		if ( this.design.positionChilds == 'absolute' ){
			if ( this.parent.parentNode ){
				this.parentNode = this.parent.parentNode;
			} else {
				dataNode.ancestors().each(function(element){
					if ( element.className == 'pc3popupwrapper' ) topDataNode = element;
					else if ( !self.parentNode && element.tagName != 'TD' && element.tagName != 'TR' && element.tagName != 'TBODY' && element.tagName != 'TABLE' ) self.parentNode = element;
				});
			}
		} else {
			this.parentNode = this.parent.content;
		}

		topDataNode.remove();
		this.parentNode.appendChild(this.outerBox);

		this.settings = {};
		['origin','opened','closed'].each(function(state){
			var values = {};
			var definition = self.popupData.get(state);
			values['setting'] = definition.element;
			values['element'] = '';
			switch( self.popupData.get('application') ){
				case 'e-paper':
				case 'mediaplayer':
					if ( (values.setting == 'triggerElement' || values.setting == 'customElement') && $(self.triggerId+"_wrapper") ) values['element'] = $(self.triggerId+"_wrapper");
					else if ( values.setting == 'parentElement' ) values['element'] = self.parent.popup;
					break;
				
				default:
					if ( values.setting == 'triggerElement' ) values['element'] = self.trigger;
					else if ( values.setting == 'parentElement' ) values['element'] = self.parent.popup;
					else if ( values.setting == 'customElement' && definition.custom && $(definition.custom) ) values['element'] = $(definition.custom);
					break;
			}

			values['position'] = definition.position;
			values['margins'] = {x:(definition.marginLeft?parseInt(definition.marginLeft):0),y:(definition.marginTop?parseInt(definition.marginTop):0)};

			if ( values['setting'] == 'atMouse' || !values['element'] ){
				if ( !values['margins'].x ) values['margins'].x = 1;
				if ( !values['margins'].y ) values['margins'].y = 1;
			}

			self.settings[state] = values;
			
		});
	}
		
	this.openInitially = function(){
		if ( this.initialState == 'opened' ) this.open('', '');
	}
	
	this.open = function(event, delay){
		if ( event ) event.stop();
		if ( !this.parent ) this.bindToParent();
		
		if ( !this.parent.isOpen() ) return;
		if ( this.waitingToOpen ) return;
		if ( this.waitingForClose ) {
			window.clearTimeout(this.waitingForClose);		
			this.waitingForClose = '';
			return;
		}
		if ( this.state == 'opened' || this.state == 'opening' ) return;
		if ( this.state == 'closing' ){
			this.openAfterClose = this.open.bind(this);
			return;
		}

		if ( !this.popup ) {
			this.initPopup();
			if ( this.design.shadow.enabled ){
				this.shadow = new pc3Shadow(
					this.popup,
					{left:this.design.shadow.marginLeft,top:this.design.shadow.marginTop,right:this.design.shadow.marginRight,bottom:this.design.shadow.marginBottom},
					this.design.shadowImages,
					this.design.shadow.cornerSize,
					this.design.shadow.color,
					this.design.shadow.opacity,
					this.design.shadow.blurRadius,
					(this.settings.origin.setting != 'atEndPosition' || this.settings.closed.setting != 'atEndPosition' || this.design.open.animationSize != '' || this.design.close.animationSize != '' ),
					(this.design.open.effect == 'fadeIn' || this.design.close.effect == 'fadeOut' )
				);
			}
		}
		
		if ( this.design.ajaxContentId ){
			var ajaxContent = pc3Widget.findElementById(this.content, this.design.ajaxContentId);

			if ( ajaxContent && this.popupData.get('ajaxContentId') && window.pc3AjaxContentObjects && window.pc3AjaxContentObjects[this.design.ajaxContentId] && this.popupData.get('ajaxLink') ){
				if ( this.isFullscreen ) this.setOpenedDimension();
				var dimension = {width:parseInt(this.isFullscreen?this.openedPosition.width:this.popupData.get('ajaxContentCustomWidth')),height:parseInt(this.isFullscreen?this.openedPosition.height:this.popupData.get('ajaxContentCustomHeight'))};
				this.ajaxRequest = new pc3PopupAjaxRequest(this.design.ajaxContentId, this.popupData.get('ajaxContentId'), this.popupData.get('ajaxLink'), ajaxContent, dimension, this.isFullscreen);
				var duration = (this.design.open.duration?parseFloat(this.design.open.duration):0) + (this.openTriggerDelay?parseFloat(this.openTriggerDelay):0);
				this.ajaxRequest.open(duration);
			}
		}
		
		this.setOpenedDimension();
		if ( typeof( this.design.background ) != "undefined" && this.design.background.enabled ) this.background = pc3Widget.addBackground(this.parent.popup, this.design.background.color, this.design.background.opacity);
		if ( this.background ) this.background.open(this.design.background.openDelay, this.design.background.openEffect, this.design.background.openDuration);
		
		this.parent.closeOtherOpenPopups(this);
		this.trigger[this.id] = 'opened';
		
		if ( this.openTriggerDelay > 0 && (event || delay) ) this.waitingToOpen = this.openPopup.bind(this).delay(this.openTriggerDelay);
		else this.openPopup();
	}

	
	this.openPopup = function(){

		window.clearTimeout(this.waitingToOpen);
		this.waitingToOpen = '';
		this.state = 'opening';
		this.parent.addOpenPopup(this);

		this.setZIndex(this.management.getZIndex());
		this.setClosedDimension(this.design.open.animationSize, this.settings.origin.element, this.settings.origin.setting);

		if ( this.closedPosition.width != this.openedPosition.width || this.closedPosition.height != this.openedPosition.height ) this.setDimension({value:this.closedPosition});
		else this.setDimension({value:this.openedPosition});

		this.setPositions('opened', this.openedPosition);
		this.setPositions('origin', this.closedPosition);
		if ( this.closedPosition.x != this.openedPosition.x || this.closedPosition.y != this.openedPosition.y ) this.setPosition({value:this.closedPosition});
		else this.setPosition({value:this.openedPosition})

		this.boundingBox = this.getBoundingBox(this.design.open.animationType);	

		var duration = (this.design.open.duration?parseFloat(this.design.open.duration)*1000:0);
		
		// fadeIn
		if ( this.design.open.effect == 'fadeIn' ){
			this.setOpacity({value:0});
			this.effects.set('opacity', new pc3Tween('opacity', 0, 100, (duration*0.8).round(), 'EaseInQuad', this.setOpacity.bind(self), this.removeEffect.bind(self)));
		} else {
			this.setOpacity({value:100});
		}
		
		// pathAnimation
		var pathAnimationTransition = '';
		if ( this.closedPosition.x != this.openedPosition.x || this.closedPosition.y != this.openedPosition.y ){
			pathAnimationTransition = this.getPathAnimationTransition(this.design.open.animation, 'open');
			this.effects.set('path', new pc3PopupPathAnimation('path', this.getPath(this.design.open.animationPath, 'open'), 'open', (duration*1 ).round(), pathAnimationTransition, this.setPosition.bind(self), this.removeEffect.bind(self)));
		}
		
		// sizeAnimation
		if ( this.closedPosition.width != this.openedPosition.width || this.closedPosition.height != this.openedPosition.height ){
			this.effects.set('size', new pc3PopupSizeAnimation('size', 0, 10000, 'open', this.design.open.animationSize, pathAnimationTransition, duration, this.setDimension.bind(self), this.removeEffect.bind(self)));
		}
		
		this.outerBox.show();
		pc3Widget.updateScrollers();
		this.animate();
	}
	
	this.close = function(event){
		if ( !this.popup ) return;
		if ( this.waitingForClose ) return;
		if ( event && event.type == 'mouseout' ){
			event.stop();
			if ( this.mouseIsInside(event) ) return;
			this.parent.close(event);
		}
		if ( this.waitingToOpen ) {
			window.clearTimeout(this.waitingToOpen);		
			this.waitingToOpen = '';
			return;
		}
		if ( this.state == 'opening' ){
			this.closeAfterOpen = this.close.bind(this);
			return;
		}
		if ( this.state == 'closed' || this.state == 'closing' ) return;

		this.trigger[this.id] = 'closed';
		if ( this.background ) this.background.close(this.design.background.closeDelay, this.design.background.closeEffect, this.design.background.closeDuration);
		this.background = '';
		this.state = 'closing';

		if ( this.closeTriggerDelay > 0 && event ) this.waitingForClose = this.closeChildPopups.bind(this).delay(this.closeTriggerDelay);
		else this.closeChildPopups();
	
	}

	this.closeChildPopups = function(){
		window.clearTimeout(this.waitingForClose);
		this.waitingForClose = '';
		this.doForAllOpenPopups(function(){ this.close(''); });
		this.waitForChildsToClose();
	}

	this.waitForChildsToClose = function(){
		if ( this.openPopups.size() ){
			this.doForAllOpenPopups(function(){ this.close(''); });
			this.waitForChildsToClose.bind(this).delay(0.02);
		} else {
			this.closePopup();
		}
	}
	
	this.closePopup = function(){
		this.setClosedDimension(this.design.close.animationSize, this.settings.closed.element, this.settings.closed.setting);
		
		this.openedPosition.x = this.currentPosition.x;
		this.openedPosition.y = this.currentPosition.y;
		
		this.setPositions('closed', this.closedPosition);
		this.boundingBox = this.getBoundingBox(this.design.close.animationType);	
				
		var duration = (this.design.close.duration?parseFloat(this.design.close.duration)*1000:0);
		
		// fadeOut
		if ( this.design.close.effect == 'fadeOut' ){
			this.effects.set('opacity', new pc3Tween('opacity', 100, 0, (duration*1.1).round(), 'EaseOutQuad', this.setOpacity.bind(self), this.removeEffect.bind(self)));
		}
		
		// pathAnimation
		var pathAnimationTransition = '';
		if ( this.closedPosition.x != this.openedPosition.x || this.closedPosition.y != this.openedPosition.y ){
			pathAnimationTransition = this.getPathAnimationTransition(this.design.close.animation, 'close');
			this.effects.set('path', new pc3PopupPathAnimation('path', this.getPath(this.design.open.animationPath, 'close'), 'close', duration, pathAnimationTransition, this.setPosition.bind(self), this.removeEffect.bind(self)));
		}
		
		// sizeAnimation
		if ( this.closedPosition.width != this.openedPosition.width || this.closedPosition.height != this.openedPosition.height ){
			this.effects.set('size', new pc3PopupSizeAnimation('size', 10000, 0, 'close', this.design.close.animationSize, pathAnimationTransition, duration, this.setDimension.bind(self), this.removeEffect.bind(self)));
		}
		
		this.animate();
	}

	this.handleClick = function(event){
		if ( this.popupData.get('application') != 'default' ) return;
		if (!this.design.close.trigger) return;
		if ( this.design.close.trigger.indexOf('clickOutsidePopup') >= 0 && !this.mouseIsInside(event)){
			this.trigger[this.id] = 'closed';
			this.close('');
		}
		this.doForAllOpenPopups(function(){ this.handleClick(event)});
	}


	this.handleClickOnTrigger = function(event){
		if ( this.popupData.get('application') != 'default' ) return;
		if ( this.design.close.trigger && this.design.close.trigger.indexOf('clickOnTrigger') >= 0 ){
			if ( this.trigger[this.id] == 'opened' ){
				this.close(event);
			} else {
				this.open(event);
			}
		} else {
			this.open(event);
		}
	}

	this.handleMouseMove = function(){
		if ( this.design.behaviour == 'attachedToMouse' ){
			position = this.getPositionAt(this.currentPosition, this.settings.opened.margins, '');
			this.positionPopup(position);
		}
		this.doForAllOpenPopups(function(){ this.handleMouseMove()});
	}

	this.handleResize = function(){
		if ( this.design.behaviour == 'attachedToScreen' || this.popupData.get('application') != 'default' || this.isFullscreen ){
			if ( this.popupData.get('application') != 'default' || this.isFullscreen ) this.setOpenedDimension();
			this.setPositions('opened', this.openedPosition);
			this.setDimension({value:this.openedPosition});
			this.setCurrentPosition(this.openedPosition);
			this.boundingBox = this.getBoundingBox(this.design.open.animationType);
			this.positionPopup(this.openedPosition);
			if ( this.isFullscreen && this.ajaxRequest ) this.ajaxRequest.setDimension(this.openedPosition);
		}
		this.doForAllOpenPopups(function(){ this.handleResize()});
	}

	this.handleScroll = function(){
		if ( this.design.behaviour == 'attachedToScreen' || this.popupData.get('application') != 'default' || this.isFullscreen ){
			this.setPositions('opened', this.openedPosition);
			this.setCurrentPosition(this.openedPosition);
			this.boundingBox = this.getBoundingBox(this.design.open.animationType);
			this.positionPopup(this.openedPosition);
		}
		this.doForAllOpenPopups(function(){ this.handleScroll()});
	}

	this.update = function(){
		if ( this.state != 'opened' ){
			this.postUpdate = true;
			return;
		}
		this.setOpenedDimension();
		this.setPositions('opened', this.openedPosition);
		this.setDimension({value:this.openedPosition});
		this.setCurrentPosition(this.openedPosition);
		this.boundingBox = this.getBoundingBox(this.design.open.animationType);
		this.positionPopup(this.openedPosition);
	}

	this.setClosePopupFunction = function(callBack){
		this.closeCallbackFunction = callBack;
	}

	this.setCurrentPosition = function(values){
		this.currentPosition.x = values.x;
		this.currentPosition.y = values.y;
	}
	
	
	this.mouseIsInside = function(event){
		var pointerX = event.pointerX();
		var pointerY = event.pointerY();
		if ( Prototype.Browser.IE ){
			pointerX = pointerX - 2;
			pointerY = pointerY - 2;
		}
		var position = this.trigger.cumulativeOffset();
		var triggerArea = {left:position.left,top:position.top,right:(position.left + this.trigger.getWidth()),bottom:(position.top + this.trigger.getHeight())};
		if ( (pointerX >= triggerArea.left && pointerX < triggerArea.right) && (pointerY >= triggerArea.top && pointerY < triggerArea.bottom) ) return true;
		if ( this.design.behaviour == 'attachedToMouse' ) return false;
		position = this.popup.cumulativeOffset();
		var popupArea = {left:position.left,top:position.top,right:(position.left + this.popup.getWidth()),bottom:(position.top + this.popup.getHeight())};
		if ( (pointerX >= popupArea.left && pointerX < popupArea.right) && (pointerY >= popupArea.top && pointerY < popupArea.bottom) ) return true;

		if ( this.design.close.trigger.indexOf('mouseout') >= 0 ){
			var connectingArea = {};
			if ( triggerArea.left > popupArea.left ){
				if ( popupArea.right > triggerArea.left ){
					connectingArea['left'] = triggerArea.left;
					connectingArea['right'] = popupArea.right;
				} else {
					connectingArea['left'] = popupArea.right;
					connectingArea['right'] = triggerArea.left;
				}
			} else {
				if ( triggerArea.right > popupArea.left ){
					connectingArea['left'] = popupArea.left;
					connectingArea['right'] = triggerArea.right;
				} else {
					connectingArea['left'] = triggerArea.right;
					connectingArea['right'] = popupArea.left;
				}
			}
			if ( triggerArea.top > popupArea.top ){
				if ( popupArea.bottom > triggerArea.top ){
					connectingArea['top'] = triggerArea.top;
					connectingArea['bottom'] = popupArea.bottom;
				} else {
					connectingArea['top'] = popupArea.bottom;
					connectingArea['bottom'] = triggerArea.top;
				}
			} else {
				if ( triggerArea.bottom > popupArea.top ){
					connectingArea['top'] = popupArea.top;
					connectingArea['bottom'] = triggerArea.bottom;
				} else {
					connectingArea['top'] = triggerArea.bottom;
					connectingArea['bottom'] = popupArea.top;
				}
			}
	
			if ( (pointerX >= connectingArea.left && pointerX < connectingArea.right) && (pointerY >= connectingArea.top && pointerY < connectingArea.bottom) ) return true;
		}

		if ( this.settings.opened.setting != 'triggerElement' ) return false;
		
		var mouseIsInside = false;
		this.openPopups.each(function(currentPopup){ if ( currentPopup.value.mouseIsInside(event) ) mouseIsInside = true; });		
		return mouseIsInside;
	}

	this.setOpenedDimension = function(){
		switch( self.popupData.get('application') ){
			case 'e-paper':
				var dimension = {
					'width':document.viewport.getWidth()-(2*this.settings.opened.margins.x)-this.popup.outerDimension.offsetLeft-this.popup.outerDimension.offsetRight,
					'height':document.viewport.getHeight()-(2*this.settings.opened.margins.y)-this.popup.outerDimension.offsetTop-this.popup.outerDimension.offsetBottom
				};
				break;
			case 'mediaplayer':
				var originalDimension = $(self.triggerId+"_wrapper").getDimensions();
				
				var windowDimension = {
					'width':document.viewport.getWidth()-(2*this.settings.opened.margins.x)-this.popup.outerDimension.offsetLeft-this.popup.outerDimension.offsetRight,
					'height':document.viewport.getHeight()-(2*this.settings.opened.margins.y)-this.popup.outerDimension.offsetTop-this.popup.outerDimension.offsetBottom
				};
				if ( windowDimension.width/originalDimension.width > windowDimension.height/originalDimension.height ){
					var dimension = {'width':(windowDimension.height * originalDimension.width) / originalDimension.height,'height':windowDimension.height};
				} else {
					var dimension = {'width':windowDimension.width,'height':(windowDimension.width * originalDimension.height) / originalDimension.width};
				}
				break;				
			default:
				if ( !this.isFullscreen ) {
					var dimension = pc3Widget.getElementDimension(this.content);
				} else {
					var dimension = {
						'width':document.viewport.getWidth()-(2*this.settings.opened.margins.x)-this.popup.outerDimension.offsetLeft-this.popup.outerDimension.offsetRight,
						'height':document.viewport.getHeight()-(2*this.settings.opened.margins.y)-this.popup.outerDimension.offsetTop-this.popup.outerDimension.offsetBottom
					};
				}
				break;
		}
		this.openedPosition.width = (dimension.width>0?dimension.width:1);
		this.openedPosition.height = (dimension.height>0?dimension.height:1);
		this.openedPosition.factorWidth = 10000;
		this.openedPosition.factorHeight = 10000;
	}

	this.setClosedDimension = function(sizeAnimation, offsetElement, elementSetting){
		switch( sizeAnimation ){
			case 'zoomIn':
			case 'zoomOut':
			case 'bubble':
			case 'growBoth':
			case 'shrinkBoth':
				if ( (elementSetting == 'triggerElement' || elementSetting == 'customElement') && offsetElement ){
					this.closedPosition.width = offsetElement.getWidth() - this.popup.outerDimension.offsetsX;
					this.closedPosition.height = offsetElement.getHeight() - this.popup.outerDimension.offsetsY;
				} else {
					this.closedPosition.width = (this.popup.outerDimension.offsetsX?0:3);
					this.closedPosition.height = (this.popup.outerDimension.offsetsY?0:3);
				}
				break;
				
			case 'growHorizontal':
			case 'shrinkHorizontal':
				this.closedPosition.width = (this.popup.outerDimension.offsetsX?0:1);
				this.closedPosition.height = this.openedPosition.height;
				break;
				
			case 'growVertical':
			case 'shrinkVertical':
				this.closedPosition.width = this.openedPosition.width;
				this.closedPosition.height = (this.popup.outerDimension.offsetsY?0:1);
				break;
				
			default:
				this.closedPosition.width = this.openedPosition.width;
				this.closedPosition.height = this.openedPosition.height;
				break;
		}
		this.closedPosition.factorWidth = 0;
		this.closedPosition.factorHeight = 0;
	}

	this.setPositions = function(state, dimensions){
		var newPosition = $H({x:0,y:0,marginLeft:0,marginTop:0,marginRight:0,marginBottom:0});
		
		var offsetElement = this.settings[state].element;
		var elementSetting = this.settings[state].setting;
		var positionSetting = this.settings[state].position;
		var margins = this.settings[state].margins;
		if ( elementSetting == 'atEndPosition' ){
			newPosition.each(function(pair){ newPosition.set(pair.key, self.openedPosition[pair.key]) });
		} else if ( elementSetting == 'atMouse' || !offsetElement ){
			var position = self.getPositionAt(dimensions, margins, '');
			newPosition.each(function(pair){ newPosition.set(pair.key, position[pair.key]) });			
		} else if ( positionSetting == 'atElement' ){
			var boxDimension = { left:offsetElement.cumulativeOffset().left, top:offsetElement.cumulativeOffset().top, width:offsetElement.getWidth(), height:offsetElement.getHeight() };
			var position = self.getPositionAt(dimensions, margins, boxDimension);
			newPosition.each(function(pair){ newPosition.set(pair.key, position[pair.key]) });
		} else {
			var offsetLeft = document.viewport.getScrollOffsets().left;
			var offsetTop = document.viewport.getScrollOffsets().top;
			var screenWidth = document.viewport.getWidth();
			var screenHeight = document.viewport.getHeight();
			
			if ( offsetElement != pc3Widget.body ){
				if ( !this.positionIsRelative() ){
					offsetLeft = offsetElement.cumulativeOffset().left;
					offsetTop = offsetElement.cumulativeOffset().top;
					screenWidth = offsetElement.getWidth();
					screenHeight = offsetElement.getHeight();
				} else {
					if ( offsetElement != this.parent.popup ){
						offsetLeft = offsetElement.cumulativeOffset().left - this.parent.popup.cumulativeOffset().left;
						offsetTop = offsetElement.cumulativeOffset().top - this.parent.popup.cumulativeOffset().top;
						screenWidth = offsetElement.getWidth();
						screenHeight = offsetElement.getHeight();
					} else {
						offsetLeft = 0;
						offsetTop = 0;
						screenWidth = offsetElement.getWidth();
						screenHeight = offsetElement.getHeight();
					}
				}
			}
			
			
			
			var halfWidth = (dimensions.width / 2).round();
			var halfHeight = (dimensions.height / 2).round();
			switch( positionSetting ){
				case 'leftTopOutside':
				case 'leftCenterOutside':
				case 'leftBottomOutside':
					newPosition.set('marginLeft', 0);
					newPosition.set('marginRight', margins.x);
					newPosition.set('x', offsetLeft - halfWidth - this.popup.outerDimension.offsetRight - newPosition.get('marginRight'));
					break;

				case 'topLeftOutside':
				case 'topLeftInside':
				case 'leftTopInside':
				case 'leftCenterInside':
				case 'leftBottomInside':
				case 'bottomLeftInside':
				case 'bottomLeftOutside':
					newPosition.set('marginLeft', margins.x);
					newPosition.set('marginRight', 0);
					newPosition.set('x', offsetLeft + halfWidth + this.popup.outerDimension.offsetLeft + newPosition.get('marginLeft'));
					break;

				case 'centerCenter':
				case 'topCenterInside':
				case 'bottomCenterInside':
				case 'topCenterOutside':
				case 'bottomCenterOutside':
					newPosition.set('marginLeft', 0);
					newPosition.set('marginRight', 0);
					newPosition.set('x', (offsetLeft + (screenWidth / 2)).round());
					break;

				case 'topRightOutside':
				case 'topRightInside':
				case 'rightTopInside':
				case 'rightCenterInside':
				case 'rightBottomInside':
				case 'bottomRightInside':
				case 'bottomRightOutside':
					newPosition.set('marginLeft', 0);
					newPosition.set('marginRight', margins.x);
					newPosition.set('x', offsetLeft + screenWidth - halfWidth - this.popup.outerDimension.offsetRight - newPosition.get('marginRight'));
					break;
					
				case 'rightTopOutside':
				case 'rightCenterOutside':
				case 'rightBottomOutside':
					newPosition.set('marginLeft', margins.x);
					newPosition.set('marginRight', 0);
					newPosition.set('x', offsetLeft + screenWidth + halfWidth + this.popup.outerDimension.offsetLeft + newPosition.get('marginLeft'));
					break;
			}


			switch( positionSetting ){
				case 'topLeftOutside':
				case 'topCenterOutside':
				case 'topRightOutside':
					newPosition.set('marginTop', 0);
					newPosition.set('marginBottom', margins.y);
					newPosition.set('y', offsetTop - halfHeight - this.popup.outerDimension.offsetTop - newPosition.get('marginBottom'));
					break;

				case 'leftTopOutside':
				case 'leftTopInside':
				case 'topLeftInside':
				case 'topCenterInside':
				case 'topRightInside':
				case 'rightTopInside':
				case 'rightTopOutside':
					newPosition.set('marginTop', margins.y);
					newPosition.set('marginBottom', 0);
					newPosition.set('y', offsetTop + halfHeight + this.popup.outerDimension.offsetTop + newPosition.get('marginTop'));
					break;

				case 'leftCenterOutside':
				case 'leftCenterInside':
				case 'centerCenter':
				case 'rightCenterInside':
				case 'rightCenterOutside':
					newPosition.set('marginTop', 0);
					newPosition.set('marginBottom', 0);
					newPosition.set('y', (offsetTop + (screenHeight / 2)).round());
					break;

				case 'leftBottomOutside':
				case 'leftBottomInside':
				case 'bottomLeftInside':
				case 'bottomCenterInside':
				case 'bottomRightInside':
				case 'rightBottomInside':
				case 'rightBottomOutside':
					newPosition.set('marginTop', 0);
					newPosition.set('marginBottom', margins.y);
					newPosition.set('y', offsetTop + screenHeight - halfHeight - this.popup.outerDimension.offsetBottom - newPosition.get('marginBottom'));
					break;

				case 'bottomLeftOutside':
				case 'bottomCenterOutside':
				case 'bottomRightOutside':
					newPosition.set('marginTop', margins.y);
					newPosition.set('marginBottom', 0);
					newPosition.set('y', offsetTop + screenHeight + halfHeight + this.popup.outerDimension.offsetTop + newPosition.get('marginTop'));
					break;
			}
		}
		if ( state == 'opened' ) newPosition.each(function(pair){ self.openedPosition[pair.key] = pair.value });			
		else newPosition.each(function(pair){ self.closedPosition[pair.key] = pair.value });
		//console.log('setPositions '+(offsetElement?offsetElement.identify():'noOffsetElement')+" "+elementSetting+" "+positionSetting+" {"+dimensions.x+","+dimensions.y+"} {"+dimensions.width+","+dimensions.height+"} {"+margins.x+","+margins.y+"} "+state);
	}

	this.getPath = function(type, direction){
		var x = 0;
		var y = 0;
		var startX = (direction=='open'?this.closedPosition.x:this.openedPosition.x);
		var endX = (direction=='open'?this.openedPosition.x:this.closedPosition.x);
		var startY = (direction=='open'?this.closedPosition.y:this.openedPosition.y);
		var endY = (direction=='open'?this.openedPosition.y:this.closedPosition.y);
		
		switch( type ){
			case 'straight':
				x = (startX + ((endX - startX) / 2)).round();
				y = (startY + ((endY - startY) / 2)).round();
				break;
			
			case 'swish':
				if ( startX < endX ) x = (startX + ((((endX - startX) / 3)) * 2)).round();
				else x = (startX - ((((startX - endX) / 3)) * 2)).round();
				if ( startY < endY ) y = (endY + (((endY - startY) / 4) * 3)).round();
				else y = (endY - (((startY - endY) / 4) * 3)).round();
				break;
			
			case 'swishStrong':
				if ( startX < endX ) x = (startX + ((((endX - startX) / 2)) * 1)).round();
				else x = (startX - ((((startX - endX) / 2)) * 1)).round();
				if ( startY < endY ) y = (endY + ((endY - startY) * 2)).round();
				else y = (endY - ((startY - endY) * 2)).round();
				break;
		}

		return { start:{ x:startX, y:startY }, middle:{ x:x, y:y }, end:{ x:endX, y:endY } };
	}

	this.getPathAnimationTransition = function(type, direction){
		switch( type ){
			case 'easeOut':
				return 'EaseOutQuad';
				
			case 'easeIn':
				return 'EaseInQuad';

			case 'drop':
				if ( direction == 'open' ) return 'EaseOutBounce';
				return 'EaseOutBounce';
				
			case 'elastic':
				if ( direction == 'open' ) return 'EaseOutElastic';
				return 'EaseInElastic';
		}
		
		return 'EaseNone';
	}				

	this.setZIndex = function(zIndex){
		this.outerBox.setStyle({zIndex:zIndex});
	}
	
	this.setOpacity = function(effect){
		this.currentPosition.opacity = effect.value;
		this.innerBox.setOpacity(this.currentPosition.opacity/100);
	}

	this.setPosition = function(effect){
		this.currentPosition.x = (effect.value.x).round();
		this.currentPosition.y = (effect.value.y).round();
	}

	this.setDimension = function(effect){
		this.currentPosition.width = (((this.openedPosition.width - this.closedPosition.width) * effect.value.factorWidth) / this.openedPosition.factorWidth).round() + this.closedPosition.width;
		this.currentPosition.height = (((this.openedPosition.height - this.closedPosition.height) * effect.value.factorHeight) / this.openedPosition.factorHeight).round() + this.closedPosition.height;
	}

	this.positionIsRelative = function(){
		if ( this.design.positionChilds == 'absolute' || (this.design.positionChilds != 'absolute' && this.parent == this.management) ) return false;
		return true;
	}
	
	this.removeEffect = function(effect){ this.effects.unset(effect.name); }

	this.getBoundingBox = function(type){
		var outerBoundingBox = pc3Widget.getDocumentDimension();
		
		var offsetElement = this.settings.opened.element;
		var positionSetting = this.settings.opened.position;

		if (type == 'visible' || !offsetElement || positionSetting == 'atElement' ) return outerBoundingBox;
		
		var left = outerBoundingBox.left;
		var top = outerBoundingBox.top;
		var right = outerBoundingBox.right;
		var bottom = outerBoundingBox.bottom;
		
		if ( positionSetting.endsWith('Inside') || positionSetting == 'centerCenter' || positionSetting == 'atMouse' ){
		
			if ( this.design.positionChilds == 'absolute' || (this.design.positionChilds != 'absolute' && this.parent == this.management) ){
				if ( offsetElement != pc3Widget.body ){
					left = offsetElement.cumulativeOffset().left;
					top = offsetElement.cumulativeOffset().top;
					right = left + offsetElement.getWidth();
					bottom = top + offsetElement.getHeight();
				}
			} else {
				left = offsetElement.cumulativeOffset().left - this.parent.popup.cumulativeOffset().left;
				top = offsetElement.cumulativeOffset().top - this.parent.popup.cumulativeOffset().top;
				right = left + offsetElement.getWidth();
				bottom = top + offsetElement.getHeight();
			}

		} else {
			if ( positionSetting.startsWith('left') ){
				if ( this.positionIsRelative() ) right = offsetElement.cumulativeOffset().left - this.parent.popup.cumulativeOffset().left;
				else right = offsetElement.cumulativeOffset().left;
			} else if ( positionSetting.startsWith('right') ){
				if ( this.positionIsRelative() ) left = offsetElement.cumulativeOffset().left - this.parent.popup.cumulativeOffset().left + offsetElement.getWidth();
				else left = offsetElement.cumulativeOffset().left + offsetElement.getWidth();
			}
			if ( positionSetting.startsWith('top') ){
				if ( this.positionIsRelative() ) bottom = offsetElement.cumulativeOffset().top - this.parent.popup.cumulativeOffset().top;
				bottom = offsetElement.cumulativeOffset().top;
			} else if ( positionSetting.startsWith('bottom') ){
				if ( this.positionIsRelative() ) top = offsetElement.cumulativeOffset().top - this.parent.popup.cumulativeOffset().top + offsetElement.getHeight();
				top = offsetElement.cumulativeOffset().top + offsetElement.getHeight();
			}
		}
		return {left:left,top:top,right:right,bottom:bottom};
	}

	this.fitToBoundingBox = function(contentDimension, boundingBox){
		var offsetX = 0;
		var left = contentDimension.left;
		var width = contentDimension.width;
		if ( left < boundingBox.left ){
			offsetX = left - boundingBox.left;
			if ( (left + width) < boundingBox.left ){
				width = 0;
			} else {
				width = width + offsetX;
			}
			left = boundingBox.left;
		}
		
		if ( left + width > boundingBox.right ){
			if ( left > boundingBox.right ){
				left = boundingBox.right;
				width = 0;
			} else {
				width = boundingBox.right - left;
			}
		}

		var offsetY = 0;
		var top = contentDimension.top;
		var height = contentDimension.height;

		if ( top < boundingBox.top ){
			offsetY = top - boundingBox.top;
			if ( (top + height) < boundingBox.top ){
				height = 0;
			} else {
				height = height + offsetY;
			}
			top = boundingBox.top;
		}
		if ( top + height > boundingBox.bottom ){
			if ( top > boundingBox.bottom ){
				top = boundingBox.bottom;
				height = 0;
			} else {
				height = boundingBox.bottom - top;
			}
		}
		return {left:(left).round(), top:(top).round(), width:(width).round(), height:(height).round(), offsetX:(offsetX).round(), offsetY:(offsetY).round()};
	}

	this.getPositionAt = function(dimension, margins, box){
		if ( !box ) {
			box = {
				left:(pc3Widget.mousePosition.x?pc3Widget.mousePosition.x:0),
				top:(pc3Widget.mousePosition.y?pc3Widget.mousePosition.y:0),
				width:0,
				height:0
			}
		}
		
		if ( this.positionIsRelative() ){
			var offsetLeft = this.parent.popup.cumulativeOffset().left;
			var offsetTop = this.parent.popup.cumulativeOffset().top;
			var screenWidth = this.parent.popup.getWidth();
			var screenHeight = this.parent.popup.getHeight();
		} else {
			var offsetLeft = document.viewport.getScrollOffsets().left;
			var offsetTop = document.viewport.getScrollOffsets().top;
			var screenWidth = document.viewport.getWidth();
			var screenHeight = document.viewport.getHeight();
		}

		var shadowOffsets = this.getShadowOffsets(dimension);

		var width = dimension.width;
		var height = dimension.height;
		
		var position = {x:0, y:0, marginLeft:0, marginTop:0, marginRight:0, marginBottom:0};

		position.marginLeft = margins.x;

		position.x = ((box.left + box.width) + position.marginLeft + this.popup.outerDimension.offsetLeft + shadowOffsets.offsetLeft + (width/2)).ceil();
		var correction = (position.x + (width/2).ceil() + this.popup.outerDimension.offsetRight + shadowOffsets.offsetRight + 10) - (screenWidth + offsetLeft);
		if ( correction > 0 ) position.x = position.x - correction;
		
		if ( position.x - (position.marginLeft + this.popup.outerDimension.offsetLeft + shadowOffsets.offsetLeft + (width/2)).ceil() < offsetLeft ){
			position.x = (offsetLeft + position.marginLeft + this.popup.outerDimension.offsetLeft + shadowOffsets.offsetLeft + (width/2)).ceil();
		}

		position.marginTop = margins.y;
		position.y = ((box.top + box.height) + position.marginTop + this.popup.outerDimension.offsetTop + shadowOffsets.offsetTop + (height/2)).ceil();
		correction = (position.y + (height/2).ceil() + this.popup.outerDimension.offsetBottom + 2) - (screenHeight + offsetTop);
		if ( correction > 0 && box.top - offsetTop >= height + this.popup.outerDimension.offsetsY + shadowOffsets.offsetTop + shadowOffsets.offsetBottom){
			position.marginTop = 0;
			position.marginBottom = margins.y;
			position.y = (box.top - position.marginBottom - this.popup.outerDimension.offsetBottom - shadowOffsets.offsetBottom - (height/2)).ceil();
		}
		
		if ( this.positionIsRelative() ){
			position.x = position.x - this.parent.cumulativeOffset().left;
			position.y = position.y - this.parent.cumulativeOffset().top;
		}
		return position;
		
	}	





	this.animate = function(){
		var position = this.currentPosition;
		if ( this.design.behaviour == 'attachedToMouse' ) position = this.getPositionAt(this.currentPosition, this.settings.opened.margins, '');
		this.positionPopup(position);

		if ( this.effects.size() ){
			this.animate.bind(this).delay(0.02)
		} else {
			if ( this.state == 'closing' ){
				this.state = 'closed';
				this.postUpdate = false;
				if ( this.ajaxRequest ) this.removeAjaxPopups();
				if ( this.popupData.get('application') != 'default' ){
					if ( this.trigger.flashvars ){
						this.trigger.childElements().each(function(element){
							if ( element.name != 'flashvars' ) return;
							element.value = self.trigger.flashvars;
							self.trigger.flashvars = '';
						});
					}
					$(this.triggerId+"_wrapper").appendChild(this.trigger);
				}
				this.parent.removeOpenPopup(this);
				this.outerBox.hide();
				if ( this.closeCallbackFunction ) this.closeCallbackFunction();
			} else {
				this.state = 'opened';
				if ( this.popupData.get('application') != 'default' ){
					if ( !Prototype.Browser.IE ) {
						this.trigger.childElements().each(function(element){
							if ( element.name != 'flashvars' ) return;
							self.trigger.flashvars = element.value;
							element.value = element.value+'&displayType=maximized';
						});
					}
					this.content.appendChild(this.trigger);
				}
				
				if ( this.postUpdate ) this.update();
				this.postUpdate = false;
				this.doForAllPopups( function(){ this.openInitially(); });
			}
			if ( this.closeAfterOpen ){
				var closeAfterOpen = this.closeAfterOpen;
				this.closeAfterOpen = '';
				closeAfterOpen();
			}
			if ( this.openAfterClose ){
				var openAfterClose = this.openAfterClose;
				this.openAfterClose = '';
				openAfterClose();
			}
		}
	}

	this.positionPopup = function(position){
		var endLeft = (position.x - (this.openedPosition.width/2)).round();
		var left = endLeft + ((this.openedPosition.width - this.currentPosition.width)/2).round();

		var endTop = (position.y - (this.openedPosition.height/2)).round();
		var top = endTop + ((this.openedPosition.height - this.currentPosition.height)/2).round();

		var shadowOffsets = this.getShadowOffsets(this.currentPosition);
		
		var popupOffsetLeft = shadowOffsets.offsetLeft;
		var popupOffsetTop = shadowOffsets.offsetTop;

		var innerBoxDimension = {};

		innerBoxDimension['left'] = left - this.popup.outerDimension.offsetLeft - popupOffsetLeft;
		innerBoxDimension['top'] = top - this.popup.outerDimension.offsetTop - popupOffsetTop;
		innerBoxDimension['width'] = this.currentPosition.width + (left - innerBoxDimension.left) + this.popup.outerDimension.offsetRight + shadowOffsets.offsetRight;
		innerBoxDimension['height'] = this.currentPosition.height + (top - innerBoxDimension.top) + this.popup.outerDimension.offsetBottom + shadowOffsets.offsetBottom;

		var boxDimension = this.fitToBoundingBox(innerBoxDimension, this.boundingBox);
		
		

		this.outerBox.setStyle({
			left:boxDimension.left +'px',
			top:boxDimension.top +'px',
			width:boxDimension.width +'px',
			height:boxDimension.height +'px'
		});

		if ( boxDimension.offsetX != 0) popupOffsetLeft = popupOffsetLeft + boxDimension.offsetX;
		if ( boxDimension.offsetY != 0) popupOffsetTop = popupOffsetTop + boxDimension.offsetY;

		this.innerBox.setStyle({
			left:popupOffsetLeft +'px',
			top:popupOffsetTop +'px',
			width:innerBoxDimension.width +'px',
			height:innerBoxDimension.height +'px'
		});


		if ( (this.lastSize && this.lastSize.width != this.currentPosition.width) || (this.lastSize && this.lastSize.height != this.currentPosition.height) ){
			this.popup.setStyle({
				left:'0px',
				top:'0px',
				width:this.currentPosition.width +'px',
				height:this.currentPosition.height +'px'
			});
	
			this.contentBox.setStyle({
				left:this.popup.outerDimension.offsetLeft +'px',
				top:this.popup.outerDimension.offsetTop +'px',
				width:this.currentPosition.width +'px',
				height:this.currentPosition.height +'px'
			});
	
			this.content.setStyle({
				left:-(left - endLeft) +'px',
				top:-(top - endTop) +'px',
				width:this.openedPosition.width +'px',
				height:this.openedPosition.height +'px'
			});
				
			if ( this.eventLayer ){
				this.eventLayer.setStyle({
					left:shadowOffsets.offsetLeft +'px',
					top:shadowOffsets.offsetTop +'px',
					width:(this.currentPosition.width + shadowOffsets.offsetsX) +'px',
					height:(this.currentPosition.height + shadowOffsets.offsetsY) +'px'
				});
			}
			
			if ( this.shadow ){
				this.shadow.setZoomFactor(this.getShadowFactor(this.currentPosition));
				this.shadow.show();
			}
		}


		this.lastSize.width = this.currentPosition.width;
		this.lastSize.height = this.currentPosition.height;

	}

	this.getShadowFactor = function(position){
		if ( this.openedPosition.width < 1 || this.openedPosition.height < 1 ) return 0;
		var shadowFactor = position.width / this.openedPosition.width;
		if ( shadowFactor > position.height / this.openedPosition.height ) shadowFactor = position.height / this.openedPosition.height;
		return (shadowFactor * 100);
	}	
	
	this.getShadowOffsets = function(position){
		if ( this.shadowOffsets && (this.lastSize && this.lastSize.width == position.width) || (this.lastSize && this.lastSize.height == position.height) ) return this.shadowOffsets;
		if ( this.shadow ){
			this.shadow.setZoomFactor(this.getShadowFactor(position));
			this.shadowOffsets = this.shadow.getOuterDimension();
			['Left','Top','Right','Bottom'].each(function(location){ if ( self.shadowOffsets['offset'+location] < 0 ) self.shadowOffsets['offset'+location] = 0;});
		}
		return this.shadowOffsets;
	}
	
	this.addOpenPopup = function(popup){
		if ( !this.openPopups.size() && Prototype.Browser.IE6 && this.selects ) {
			this.selects.each(function(select){
				if ( select.visible() ){
					self.hiddenSelects.push(select);
					select.style.visibility = "hidden";
				}
			});
		}
		this.openPopups.set(popup.id, popup);
	}

	this.removeOpenPopup = function(popup){
		this.openPopups.unset(popup.id);
		if ( !this.openPopups.size() && Prototype.Browser.IE6 ) {
			this.hiddenSelects.each(function(select){ select.style.visibility = "visible"; });
			this.hiddenSelects = new Array();
		}
	}


	this.removePopup = function(popupId){
		this.popups.unset(popupId);
		this.parent.removePopup(popupId);
	}

	this.closeOtherOpenPopups = function(popup){
		if ( popup.groupname == 'pc3DefaultPopupGroup' ) return;
		this.openPopups.each(function(currentPopup){ if ( popup.id != currentPopup.key && popup.groupname == currentPopup.value.groupname ) currentPopup.value.close(''); });		
	}

	this.removeChildPopups = function(){
		this.popups.each(function(currentPopup){
			if ( currentPopup.value.outerBox ) currentPopup.value.outerBox.remove();
			currentPopup.value.parent.removePopup(currentPopup.value.id);
			delete currentPopup;
		});
	}
	
	this.removeAjaxPopups = function(){
		this.ajaxRequest.resetAjaxContent();
		this.management.removeAjaxPopups(this.ajaxRequest.getAjaxContent());
	}

	this.doForAllOpenPopups = function(func){ this.openPopups.each(function(currentPopup){ func.bind(currentPopup.value)(); }); }

	this.doForAllPopups = function(func){ this.popups.each(function(currentPopup){ func.bind(currentPopup.value)(); }); }

	this.addPopup = function(popup){ this.popups.set(popup.id, popup); }

	this.isOpen = function(){ return (this.state == 'opened'); }
	
	this.init();
}



function pc3PopupSizeAnimation(name, startDimension, endDimension, direction, type, pathAnimationTransition, duration, onUpdate, onComplete){
	var self = this;
	this.name = name;
	this.direction = direction;
	
	this.onUpdateFunction = onUpdate;
	this.onCompleteFunction = onComplete;
	
	this.start = {
		factorWidth:startDimension,
		factorHeight:startDimension
	};
	
	this.end = {
		factorWidth:endDimension,
		factorHeight:endDimension
	};

	this.value = $H({
		factorWidth:startDimension,
		factorHeight:startDimension
	});
		
	this.onUpdate = function(effect){
		var factor = effect.value/10000;
		if ( effect.name == 'both' || effect.name == 'width' ) this.value.factorWidth = this.start.factorWidth + ((this.end.factorWidth - this.start.factorWidth) * factor);
 		if ( effect.name == 'both' || effect.name == 'height' ) this.value.factorHeight = this.start.factorHeight + ((this.end.factorHeight - this.start.factorHeight) * factor);
		this.onUpdateFunction(this);
	}

	this.extendEffect = function(name, start, end, duration, transition, onUpdate, onComplete, effect ){
		var nextEffect = new pc3Tween(name, start, end, duration, transition, onUpdate, onComplete);
		delete effect;
	};
	
	this.onComplete = function(effect){
		delete effect;
		this.value = this.end;
		this.onUpdateFunction(this);
		this.onCompleteFunction(this);
	};
	
	
	this.init = function(duration, type){
		var transition = '';
		switch( type ){
			case 'bubble':
				var effect = new pc3Tween('both', 0, 10000, (duration * 0.5).round(), (direction=='open'?'EaseOutBounce':'EaseOutBounce'), this.onUpdate.bind(self), this.onComplete.bind(self));
				break;

			case 'growBoth':
			case 'shrinkBoth':
				if ( pathAnimationTransition ){
					if ( pathAnimationTransition.startsWith('EaseIn') ) transition = 'EaseInQuad';
					else transition = 'EaseOutQuad';
				} else {
					transition = (direction=='open'?'EaseInQuad':'EaseOutQuad');
				}
				var nextEffect = this.extendEffect.bind(self, 'height', 0, 10000, (duration * 0.5).round(), transition, this.onUpdate.bind(self), this.onComplete.bind(self));
				var effect = new pc3Tween('width', 0, 10000, (duration * 0.5).round(), transition, this.onUpdate.bind(self), nextEffect);
				break;

			default:
				if ( pathAnimationTransition ){
					if ( pathAnimationTransition.startsWith('EaseIn') ) transition = 'EaseInQuad';
					else transition = 'EaseOutQuad';
				} else {
					transition = (direction=='open'?'EaseInQuad':'EaseOutQuad');
				}
				var effect = new pc3Tween('both', 0, 10000, (duration * 1).round(), transition, this.onUpdate.bind(self), this.onComplete.bind(self));
				break;		
		}

	}

	this.init(duration, type);
}

function pc3PopupPathAnimation(name, path, direction, duration, transition, onUpdate, onComplete){
	var self = this;
	this.name = name;
	this.path = path;
	this.onUpdateFunction = onUpdate;
	this.onCompleteFunction = onComplete;
	
	this.value = this.path.start;
	this.end = this.path.end;
	
	this.onUpdate = function(effect){
		this.value = this.getBezierPosition(effect.value, this.path);
		this.onUpdateFunction(this);
	}
	
	this.onComplete = function(effect){
		delete effect;
		this.value = this.end;
		this.onUpdateFunction(this);
		this.onCompleteFunction(this);
	};
	
	this.init = function(duration, transition, direction){
		var effect = new pc3Tween('position', 0.0, 1.0, duration, transition, this.onUpdate.bind(self), this.onComplete.bind(self));
	}

	this.getBezierPosition = function(time, path){
		return this.getIntermedianPosition(time, this.getIntermedianPosition(time, path.start, path.middle), this.getIntermedianPosition(time, path.middle, path.end));
	}
	
	this.getIntermedianPosition = function(time, start, end){
		return { x:start.x + (end.x-start.x)*time, y:start.y + (end.y-start.y)*time };
	}

	this.init(duration, transition, direction);
}

function pc3PopupAjaxRequest(originialId, newId, URL, ajaxContent, dimension, isFullscreen){
	var self = this;
	this.url = URL;
	this.ajaxRequest = '';
	this.dimension = dimension;
	this.originialId = originialId;
	this.isFullscreen = isFullscreen;
	
	this.init = function(){
		ajaxContent.id = newId;
		this.ajaxRequest = new pc3AjaxContent('','', window.pc3AjaxContentObjects[originialId].onErrorHTML, '', ajaxContent);
		this.loadContent = window.pc3AjaxContentObjects[originialId].onRequestHTML;
	}
	
	this.getAjaxContent = function(){
		return this.ajaxContent;
	}

	this.resetAjaxContent = function(){
		this.ajaxRequest.replaceDefaultContent('');
		this.ajaxRequest.defaultContent.id = this.originialId;
	}

	this.open = function(delay){
		if ( !this.ajaxRequest ) return;
		this.ajaxRequest.replaceDefaultContent(this.loadContent);
		
		if ( this.isFullscreen ) var ajaxElement = this.ajaxRequest.defaultContent;
		else var ajaxElement = this.ajaxRequest.defaultContent.down();
		
		if ( this.dimension.width ) ajaxElement.setStyle({width:this.dimension.width+'px'});
		if ( this.dimension.height ) ajaxElement.setStyle({height:this.dimension.height+'px'});
		
		this.requestContent.bind(this).delay(delay);
	}

	this.requestContent = function(){
		this.ajaxRequest.requestContent(this.url);
	}

	this.setDimension = function(dimension){
		if ( dimension.width ) this.ajaxRequest.defaultContent.setStyle({width:dimension.width+'px'});
		if ( dimension.height ) this.ajaxRequest.defaultContent.setStyle({height:dimension.height+'px'});
	}
	
	this.init();
}

function pc3Shadow(element, offsets, images, cornerSize, color, opacity, blurRadius, animated, fade){
	var self = this;
	this.element = element;
	this.shadows = {};
	this.zoomFactor = 100;
	this.ready = false;
	this.showAfterInit = false;

	this.maxExpansion = 2000;
		
	this.init = function(){
		var parent = this.element.ancestors()[0];
		var useImages = false;
		if ( images.left && images.top && images.right && images.bottom ) useImages = true;
		if ( Prototype.Browser.IE6 ) useImages = false;
		if ( Prototype.Browser.IE7 && fade ) useImages = false;
		this.status = 'hidden';

		if ( useImages ){
			this.cornerSize = cornerSize;
			var dimension = this.cornerSize;
			this.loadShadowImages(images, parent, dimension, offsets, true);
		} else {
			this.ready = true;
			this.cornerSize = Math.max(offsets.left,offsets.top,offsets.right,offsets.bottom);
			if ( animated && Prototype.Browser.IE6 ) blurRadius = Math.min(2, blurRadius);
			var corner = pc3Widget.blur(blurRadius);
			var dimension = (2*blurRadius)+1;
			opacity = opacity/100;
			this.generateShadows(useImages, parent, dimension, offsets, corner, opacity);
		}
	}
	
	
	this.loadShadowImages = function(images, parent, dimension, offsets, first){
		if ( first ){
			this.shadowImages = $H({});
			['left','top','right','bottom'].each(function(position){
				['element1','element2'].each(function(element){
					self.shadowImages.set(position+"_"+element, $(new Image()));
					self.shadowImages.get(position+"_"+element).src = images[position];
				});
			});
		}
		var isLoaded = true;
		this.shadowImages.each(function(image){
			if ( !image.value.width ) isLoaded = false;
		});
		
		if ( isLoaded ) this.generateShadows(true, parent, dimension, offsets, '', '');
		else this.loadShadowImages.bind(this, '', parent, dimension, offsets).delay(0.02);
	}


	this.generateShadows = function(useImages, parent, dimension, offsets, corner, opacity){
		['left','top','right','bottom'].each(function(position){
			var outerBox = '';
			var shadows = {};
			if ( offsets[position] > 0 ){
				outerBox = new Element('div').setStyle({position:'absolute',overflow:'hidden'}).hide();
				parent.appendChild(outerBox);
				var filler = '';
				if ( position == 'left' && offsets.top < 0 ) filler = new Element('div').setStyle({overflow:'hidden',left:'0px', width:offsets[position]+'px'});
				if ( position == 'top' && offsets.left < 0 ) filler = new Element('div').setStyle({overflow:'hidden',float:'left', top:'0px', height:offsets[position]+'px'});
				if ( position == 'right' && offsets.top < 0 ) filler = new Element('div').setStyle({overflow:'hidden',right:'0px', width:offsets[position]+'px'});
				if ( position == 'bottom' && offsets.left < 0 ) filler = new Element('div').setStyle({overflow:'hidden',float:'left', bottom:'0px', height:offsets[position]+'px'});
				if ( filler ) outerBox.appendChild(filler);
				
				['element1', 'element2'].each( function(element) {
					var columnRange = new Array();
					var rowRange = new Array();
					var expanedWidth = 0;
					var expanedHeight = 0;
					var width = 1;
					var height = 1;
					var addPixels = false;
					var box = new Element('div').setStyle({overflow:'hidden',position:'relative'});
					var shadow = new Element('div').setStyle({overflow:'hidden',position:'absolute'});
					if ( useImages ) var image = self.shadowImages.get(position+"_"+element);
					if ( offsets[position] < 1 ) return;
					switch( position ){
						case 'left':
							switch( element ){
								case 'element1':
									if ( useImages ){
										var top = -Math.max(0,offsets.top);
										shadow.setStyle({width:image.width+'px',height:image.height+'px',top:top+'px'});
										width = offsets[position];
										height = self.cornerSize;
									} else {
										width = offsets[position];
										height = Math.max(0, dimension - Math.max(0,offsets.top));
										columnRange = $A($R(1, Math.min((dimension + 1), width)));
										rowRange = $A($R(1+Math.max(0,offsets.top), dimension));
										expanedWidth = width - dimension;
										expanedHeight = 1;
										shadow.setStyle({width:width+'px',height:height+'px'});
									}
								break;
								case 'element2':
									if ( useImages ){
										var bottom = -Math.max(0,offsets.bottom);
										shadow.setStyle({width:image.width+'px',height:image.height+'px',bottom:bottom+'px'});
										width = offsets[position];
										height = image.height - self.cornerSize;
									} else {
										width = offsets[position];
										height = self.maxExpansion;
										columnRange = $A($R(1, Math.min((dimension + 1), width)));
										var startRange = Math.min((1 + Math.max(0,offsets.bottom)),dimension + 1);
										rowRange = $A($R(startRange, dimension + 1));
										rowRange.reverse();
										expanedWidth = width - dimension;
										expanedHeight = self.maxExpansion - (dimension - startRange);
										shadow.setStyle({width:width+'px',height:height+'px',bottom:'0px'});
									}
								break;
							}
						break;
						case 'top':
							switch( element ){
								case 'element1':
									if ( useImages ){
										shadow.setStyle({width:image.width+'px',height:image.height+'px'});
										width = self.cornerSize;
										height = offsets[position];
									} else {
										width = dimension;
										height = offsets[position];
										columnRange = $A($R(1, Math.min((dimension + 1), width)));
										rowRange = $A($R(1, Math.min((dimension + 1), height)));
										expanedWidth = 1;
										expanedHeight = self.cornerSize - dimension;
										shadow.setStyle({width:width+'px',height:height+'px'});
									}
								break;
								case 'element2':
									if ( useImages ){
										shadow.setStyle({width:image.width+'px',height:image.height+'px', right:'0px'});
										width = self.cornerSize;
										height = offsets[position];
									} else {
										width = self.maxExpansion;
										height = offsets[position];
										columnRange = $A($R(1, dimension+1));
										columnRange.reverse();
										rowRange = $A($R(1, Math.min((dimension + 1), height)));
										expanedWidth = self.maxExpansion - dimension;
										expanedHeight = height - dimension;
										shadow.setStyle({width:width+'px',height:height+'px', right:'0px'});
									}
								break;
							}
						break;
						case 'right':
							switch( element ){
								case 'element1':
									if ( useImages ){
										var top = -Math.max(0,offsets.top);
										shadow.setStyle({width:image.width+'px',height:image.height+'px',top:top+'px',right:'0px'});
										width = offsets[position];
										height = self.cornerSize;
									} else {
										width = offsets[position];
										height = Math.max(0, dimension - Math.max(0,offsets.top));
										columnRange = $A($R(1, Math.min((dimension + 1), width)));
										columnRange.reverse();
										rowRange = $A($R(1+Math.max(0,offsets.top), dimension));
										expanedWidth = width - dimension;
										expanedHeight = 1;
										shadow.setStyle({width:width+'px',height:height+'px'});
									}
								break;
								case 'element2':
									if ( useImages ){
										var bottom = -Math.max(0,offsets.bottom);
										shadow.setStyle({width:image.width+'px',height:image.height+'px',bottom:bottom+'px',right:'0px'});
										width = offsets[position];
										height = image.height - self.cornerSize;
									} else {
										width = offsets[position];
										height = self.maxExpansion;
										columnRange = $A($R(1, Math.min((dimension + 1), width)));
										columnRange.reverse();
										var startRange = Math.min((1 + Math.max(0,offsets.bottom)),dimension + 1);
										rowRange = $A($R(startRange, dimension + 1));
										rowRange.reverse();
										expanedWidth = width - dimension;
										expanedHeight = self.maxExpansion - (dimension - startRange);
										shadow.setStyle({width:width+'px',height:height+'px',bottom:'0px'});
									}
								break;
							}
						break;
						case 'bottom':
							switch( element ){
								case 'element1':
									if ( useImages ){
										shadow.setStyle({width:image.width+'px',height:image.height+'px',bottom:'0px'});
										width = self.cornerSize;
										height = offsets[position];
									} else {
										width = dimension;
										height = offsets[position];
										columnRange = $A($R(1, Math.min((dimension + 1), width)));
										rowRange = $A($R(1, Math.min((dimension + 1), height)));
										rowRange.reverse();
										expanedWidth = width - dimension;
										expanedHeight = height - dimension;
										shadow.setStyle({width:width+'px',height:height+'px', bottom:'0px'});
									}
								break;
								case 'element2':
									if ( useImages ){
										shadow.setStyle({width:image.width+'px',height:image.height+'px', right:'0px', bottom:'0px'});
										width = self.cornerSize;
										height = offsets[position];
									} else {
										width = self.maxExpansion;
										height = offsets[position];
										columnRange = $A($R(1, dimension+1));
										columnRange.reverse();
										rowRange = $A($R(1, Math.min((dimension + 1), height)));
										rowRange.reverse();
										expanedWidth = self.maxExpansion - dimension;
										expanedHeight = height - dimension;
										shadow.setStyle({width:width+'px',height:height+'px', bottom:'0px', right:'0px'});
									}
								break;
							}
						break;
			
					}
					box.setStyle({float:'left',width:width+'px',height:height+'px'});
					outerBox.appendChild(box);
					box.appendChild(shadow);
					shadows[element] = {element:box,width:width,height:height};
					
					if ( useImages ){
						shadow.appendChild(image);
					} else {
						rowRange.each(function(row){
							columnRange.each(function(column){
								var pixelWidth = 1;
								var pixelHeight = 1;
								var pixelOpacity = 0;
								
								if ( column <= dimension ){
									if ( row <= dimension ){
										pixelOpacity = corner[row][column];
									} else {
										pixelOpacity = corner[dimension][column];
										pixelHeight = expanedHeight;
									}
								} else {
									if ( row <= dimension ){
										pixelOpacity = corner[row][dimension];
										pixelWidth = expanedWidth;
									} else {
										pixelOpacity = corner[dimension][dimension];
										pixelWidth = expanedWidth;
										pixelHeight = expanedHeight;
									}
								}
								
								if ( column <= dimension + 1 && row <= dimension + 1 ){
									var pixel = new Element('div').setStyle({float:'left',overflow:'hidden',width:pixelWidth+'px',height:pixelHeight+'px',backgroundColor:color});
									pixel.setOpacity(pixelOpacity * opacity);
									shadow.appendChild(pixel);
								}
							});
						});
					}
				});
			}
			self.shadows[position] = {element:outerBox,style:{},offsets:{},offset:offsets[position],filler:filler,shadows:shadows};
		});
		this.ready = true;
		if ( this.showAfterInit ) this.show();
	}
		
		
	this.hide = function(){
		if ( this.status == 'hidden' ) return;
		['top', 'bottom', 'right', 'left'].each( function(position) { if ( self.shadows[position].element ) self.shadows[position].element.hide(); });
		this.status = 'hidden'
	}

	this.unhide = function(){
		if ( this.status == 'shown' ) return;
		['top', 'bottom', 'right', 'left'].each( function(position) { if ( self.shadows[position].element ) self.shadows[position].element.show(); });
		this.status = 'shown'
	}

	this.setZoomFactor = function(zoomFactor){
		if ( zoomFactor > 100 ) this.zoomFactor = 100;
		else this.zoomFactor = zoomFactor;
	}

	this.getOuterDimension = function(){
		var dimension = {};
		['Left','Top','Right','Bottom'].each(function(location){
			dimension['offset'+location] = (self.shadows[location.toLowerCase()]?((self.shadows[location.toLowerCase()].offset * self.zoomFactor)/100).round():0);
		});
		dimension.offsetsX = dimension.offsetLeft + dimension.offsetRight;
		dimension.offsetsY = dimension.offsetTop + dimension.offsetBottom;
		var outerDimension = pc3Widget.getOuterDimension(this.element, false);
		dimension.width = outerDimension.width + dimension.offsetsX;
		dimension.height = outerDimension.height + dimension.offsetsY;
		dimension.left = outerDimension.left - dimension.offsetLeft;
		dimension.top = outerDimension.top - dimension.offsetTop;
		return dimension;
	}
	
	
	this.show = function(){
		if ( !this.ready ){
			this.showAfterInit = true;
			return;
		}
		if ( this.cornerSize < 1 ) return;
		var outerDimension = this.getOuterDimension();
		var minSizeReached = true;
		if ( outerDimension.width  < (2 * this.cornerSize) ) minSizeReached = false;
		if ( outerDimension.height < (2 * this.cornerSize) ) minSizeReached = false;

		var popupDimension = pc3Widget.getOuterDimension(this.element, false);
		
		if ( !minSizeReached ){
			this.hide();
		} else {
			this.status = 'shown';
			['top', 'bottom', 'right', 'left'].each( function(position) {
				if ( self.shadows[position] ){
					self.shadows[position].style = {display:'none'};
					self.shadows[position].offsets = {x:0,y:0};
				}
			});
			
			

			var getStyle = function(left, top, width, height){ return {display:'block',left:left +'px',top:top +'px',width:width +'px',height:height +'px'}; };

			if ( outerDimension.offsetTop > 0 || outerDimension.offsetBottom > 0 ){
				var shadowWidth = popupDimension.width + Math.max(outerDimension.offsetLeft, 0) + Math.max(outerDimension.offsetRight, 0);

				if ( outerDimension.offsetTop > 0 ){
					var left = Math.min(popupDimension.left, (popupDimension.left - outerDimension.offsetLeft));
					var width = shadowWidth;
					var top = popupDimension.top - outerDimension.offsetTop;
					var height = outerDimension.offsetTop;
					this.shadows.top.style = getStyle(left, top, width, height);
					if ( this.shadows.top.filler ) this.shadows.top.filler.setStyle({width:Math.abs(this.shadows.left.offset)+'px'});
					width = width - (this.shadows.top.filler?Math.abs(this.shadows.left.offset):0) - this.shadows.top.shadows.element1.width + (this.shadows.right.offset<0?this.shadows.right.offset:0);
					this.shadows.top.shadows.element2.element.setStyle({width:width+'px'});
				}

				if ( outerDimension.offsetBottom > 0 ){
					var left = Math.min(popupDimension.left, (popupDimension.left - outerDimension.offsetLeft));
					var width = shadowWidth;
					var top = popupDimension.top + popupDimension.height;
					var height = outerDimension.offsetBottom;
					this.shadows.bottom.style = getStyle(left, top, width, height);
					if ( this.shadows.bottom.filler ) this.shadows.bottom.filler.setStyle({width:Math.abs(outerDimension.offsetLeft)+'px'});
					width = width - (this.shadows.bottom.filler?Math.abs(outerDimension.offsetLeft):0) - this.shadows.bottom.shadows.element1.width + (this.shadows.right.offset<0?this.shadows.right.offset:0);
					this.shadows.bottom.shadows.element2.element.setStyle({width:width+'px'});
				}
			}
			
			if ( outerDimension.offsetRight > 0 || outerDimension.offsetLeft > 0 ){

				if ( outerDimension.offsetLeft > 0 ){
					var left = Math.min(popupDimension.left, (popupDimension.left - outerDimension.offsetLeft));
					var width = outerDimension.offsetLeft;
					var top = popupDimension.top;
					var height = popupDimension.height;
					this.shadows.left.style = getStyle(left, top, width, height);
					if ( this.shadows.left.filler ) this.shadows.left.filler.setStyle({height:Math.abs(outerDimension.offsetTop)+'px'});
					height = height - (this.shadows.left.filler?Math.abs(outerDimension.offsetTop):0) - this.shadows.left.shadows.element1.height + (this.shadows.bottom.offset<0?this.shadows.bottom.offset:0);
					this.shadows.left.shadows.element2.element.setStyle({height:height+'px'});
				}

				if ( outerDimension.offsetRight > 0 ){
					var left = popupDimension.left + popupDimension.width;
					var width = outerDimension.offsetRight;
					var top = popupDimension.top;
					var height = popupDimension.height;
					this.shadows.right.style = getStyle(left, top, width, height);
					if ( this.shadows.right.filler ) this.shadows.right.filler.setStyle({height:Math.abs(outerDimension.offsetTop)+'px'});
					height = height - (this.shadows.right.filler?Math.abs(outerDimension.offsetTop):0) - this.shadows.right.shadows.element1.height + (this.shadows.bottom.offset<0?this.shadows.bottom.offset:0);
					this.shadows.right.shadows.element2.element.setStyle({height:height+'px'});
				}
			}
	
			['top', 'bottom', 'right', 'left'].each( function(position) {
				if ( self.shadows[position].element ){ self.shadows[position].element.setStyle(self.shadows[position].style); }
			});

		}
	}

	this.init();
}

/* tweener */
/*
 | Tweener
 | ------
 |   - this is a javascript port of the flash project Tweener, http://code.google.com/p/tweener/.  
 | 
 | author
 | ------
 |   - mike macmillan(mikejmacmillan@gmail.com)
*/
Tweener = function() {
	var tweenList = [];

	function getTime() {
		return new Date()*1;
	}

	function getTweenByIndex(idx) {
		if(tweenList.length==0) return;

		//** find the tween object at the given index
		for(var i=0;i<tweenList.length;i++)
			if(i==idx)
				return tweenList[i];
	}

	var tweener = {
		Running:false,
		FrameRate:1000/50, //** (50 frames per second by defaul by default...)

		Initialize:function() {
			if(!tweenList)
				tweenList = [];
		},

		AddTween:function(obj, properties, options) {
			options = options||{};

			//** if the object to tween is an id reference, get the object by its id
			if(typeof(obj) === 'string')
				obj = document.getElementById(obj);

			if(!obj || !properties || properties.length==0)
				return;

			//** create the tween object
			var tweenObj = {
				Id:new Date()*1,
				Scope:obj,
				ScopeClean: {},
				Properties:[],

				//** time scale properties
				TimeStart:0,
				TimeComplete:0,
				TimePaused:0,

				//** animation settings
				Transition:tweener.Transitions.EaseOutExpo,
				Delay:0,
				Duration:0,

				//** listeners
				OnStart:null,
				OnUpdate:null,
				OnComplete:null
			};

			//** verify the properties to tween are valid
			var propObj, start, complete;
			for(var prop in properties) {
				if(typeof(obj[prop]) === 'undefined')
					continue;

				//** determine our starting and ending values
				start = parseInt(obj[prop]);
				complete = parseInt(properties[prop]); 

				//** create the property object
				propObj = {
					Name:prop,
					Unit:"px",

					Start:start,
					Complete:complete,
					Change:complete-start,

					//** the current value being used to update the object, and the last value used...helps for eliminating unecessary updates do to the transition returning duplicate values
					Current:0,
					Last:0
				};

				//** add the valid property object to the list
				tweenObj.Properties.push(propObj);
			}

			//** iterate our options object, overwriting any tweenObj properties if necessary
			for(var prop in options) {
				if(typeof(tweenObj[prop]) !== 'undefined' && options[prop])
					tweenObj[prop] = options[prop];
			}


			//** add the tween to the animation loop 
			setTimeout(function() {
				//** timestamp it...
				tweenObj.TimeStart = getTime();
				tweenObj.TimeComplete = tweenObj.TimeStart+tweenObj.Duration;

				//** if there is a start listener, fire it
				if(tweenObj.OnStart)
					tweenObj.OnStart(tweenObj);
				
				//** add the tween to the list
				tweenList.push(tweenObj);
				
				//** start the animation loop
				tweener.Start();

			}, tweenObj.Delay);

			return tweenObj;
		},

		PauseTween:function(idx) {
			//** get the tween at the given index
			var tweenObj = getTweenByIndex(idx);

			if(!tweenObj || tweenObj.TimePaused>0) return;

			//** set the state of the tween to paused, and record the time which it was paused
			tweenObj.TimePaused = getTime();
		},

		PauseAllTweens:function() {
			if(!this.Running) return;

			//** pause each tween by index
			for(var i=0;i<tweenList.length;i++)
				this.PauseTween(i);
		},

		ResumeTween:function(idx) {
			//** get the tween at the given index
			var tweenObj = getTweenByIndex(idx);

			if(!tweenObj || tweenObj.TimePaused==0) return;

			//** set the state of the tween to resumed, and adjust the start/complete times accordingly
			var time = getTime();
			tweenObj.TimeStart += time - tweenObj.TimePaused;
			tweenObj.TimeComplete += time - tweenObj.TimePaused;
			tweenObj.TimePaused = 0;
		},

		ResumeAllTweens:function() {
			if(!this.Running) return;

			//** resume each tween by index
			for(var i=0;i<tweenList.length;i++)
				this.ResumeTween(i);
		},

		RemoveTween:function(idx) {
			//** get the tween at the given index
			var tweenObj = getTweenByIndex(idx);

			//** remove the tween if it exists
			if(tweenObj) tweenList.splice(idx, 1);
		},

		RemoveAllTweens:function() {
			//** pause each tween by index
			for(var i=0;i<tweenList.length;i++)
				this.RemoveTween(i);
		},

		Stop:function() {
			tweenList = null;
			this.Running = false;
		},

		Start:function() {
			//** if the tweening engine isn't running, start it
			if(!this.Running) {
				this.Initialize();
				this.Run();
			}
		},

		Run:function() {
			var tweenObj, runTime, duration, current, property;
	
			function updateProperties(obj, time, dur, transition) {
				var property, value;
	
				//** update the value of each transition-property for the object given with the value given
				for(var i=0;i<obj.Properties.length;i++) {
					property = obj.Properties[i];
	
					//** get the current value for this point the timescale either using the transition algorithm given, or the expected end value
					property.Current = transition?transition(time, property.Start, property.Change, dur):property.Complete;
	
					//** fire the update event before we set the propertys value
					if (obj.OnUpdate) {
						obj.OnUpdate(obj);
					}
	
					//** persist the last used value against the property.  certain transition algorithms might update more than once, 
					//** but not actually change the value of the property they're updating (ex: exponential algo when close to zero).  by
					//** persisting the last used value, objects who handle update can know if the value has changed by comparing it to the
					//** last used value...
					property.Last = property.Current;
	
					//** lastly, set the property's value
					obj.Scope[property.Name] = parseInt(property.Current) + property.Unit;
					obj.ScopeClean[property.Name] = property.Current;
				}
			}
			
			if(!tweenList || tweenList.length == 0) return;
	
			//** in case of any outside updates, every iteration, update the engine status
			this.Running = true;

			//** get the current time in ticks
			var now = getTime();
	
			//** iterate our tween objects and update them based on our current time
			for(var i=0;i<tweenList.length;i++) {
				tweenObj = tweenList[i];
	
				//** if the current animation is paused, skip it
				if(tweenObj.TimePaused>0) continue;
	
				//** get the animations state
				runTime = parseInt(now) - parseInt(tweenObj.TimeStart);
				duration = parseInt(tweenObj.TimeComplete) - parseInt(tweenObj.TimeStart);

	
				//** if the tween is complete, remove our tween object from the collection
				if(runTime >= duration) {
					//** update the tween with its end value
					updateProperties(tweenObj, runTime, duration);
	
					//** remove the tween from the list
					tweenList.splice(i, 1);
	
					//** finally, fire the complete event if implemented
					if(tweenObj.OnComplete)
						tweenObj.OnComplete(tweenObj);
				} else {
					//** update the animation with the current value, given the timeline, and the transition algorith
					updateProperties(tweenObj, runTime, duration, tweenObj.Transition);
				}
			}
	
			if(tweenList.length>0)
				//** if there are more tween objects, keep the animation loop running at the engines framerate
				setTimeout(function() { tweener.Run(); }, tweener.FrameRate);
			else
				//** otherwise, there is nothing left to animate, so flag the engine as not running
				tweener.Running = false;
		},



		Transitions: {
			EaseNone:function(t, b, c, d) {
				return c * t/d + b;
			},
		
		
			EaseInQuad:function(t, b, c, d) {
				return c * (t/=d) * t + b;
			},
		
			EaseOutQuad:function(t, b, c, d) {
				return -c * (t/=d) * (t-2) + b;
			},
		
			EaseInOutQuad:function(t, b, c, d) {
				if((t/=d/2) < 1) 
					return c/2 * t * t + b;
		
				return -c/2 * ((--t) * (t-2) - 1) + b;
			}, 
		
			EaseOutInQuad:function(t, b, c, d) {
				if(t < d/2)
					return Tweener.Transitions.EaseOutQuad(t*2, b, c/2, d);
		
				return Tweener.Transitions.EaseInQuad((t*2)-2, b+c/2, c/2, d);
			}, 
		
		
			EaseInCubic:function(t, b, c, d) {
				return c * (t/=d) * t * t + b;
			},
		
			EaseOutCubic:function(t, b, c, d) {
				return c * ((t=t/d-1) * t * t + 1) + b;
			},
		
			EaseInOutCubic:function(t, b, c, d) {
				if((t/=d/2) < 1) 
					return c/2 * t * t * t + b;
		
				return c/2 * ((t-=2) * t * t + 2) + b;
			}, 
		
			EaseOutInCubic:function(t, b, c, d) {
				if(t < d/2)
					return Tweener.Transitions.EaseOutCubic(t*2, b, c/2, d);
		
				return Tweener.Transitions.EaseInCubic((t*2)-d, b+c/2, c/2, d);
			}, 
		
		
			EaseInQuart:function(t, b, c, d) {
				return c * (t/=d) * t * t * t + b;
			},
		
			EaseOutQuart:function(t, b, c, d) {
				return -c * ((t=t/d-1) * t * t * t - 1) + b;
			},
		
			EaseInOutQuart:function(t, b, c, d) {
				if ((t/=d/2) < 1) 
					return c/2 * t * t * t * t + b;
		
				return -c/2 * ((t-=2) * t * t * t - 2) + b;
			}, 
		
			EaseOutInQuart:function(t, b, c, d) {
				if(t < d/2)
					return Tweener.Transitions.EaseOutQuart(t*2, b, c/2, d);
		
				return Tweener.Transitions.EaseInQuart((t*2)-d, b+c/2, c/2, d);
			}, 
		
		
			EaseInQuint:function(t, b, c, d) {
				return c * (t/=d) * t * t * t * t + b;
			},
		
			EaseOutQuint:function(t, b, c, d) {
				return c * ((t=t/d-1) * t * t * t * t + 1) + b;
			},
		
			EaseInOutQuint:function(t, b, c, d) {
				if ((t/=d/2) < 1) 
					return c/2 * t * t * t * t * t + b;
		
				return c/2 * ((t-=2) * t * t * t * t + 2) + b;
			}, 
		
			EaseOutInQuint:function(t, b, c, d) {
				if(t < d/2)
					return Tweener.Transitions.EaseOutQuint(t*2, b, c/2, d);
		
				return Tweener.Transitions.EaseInQuint((t*2)-d, b+c/2, c/2, d);
			}, 
		
		
			EaseInSine:function(t, b, c, d) {
				return -c * Math.cos(t/d * (Math.PI/2)) + c + b;
			},
		
			EaseOutSine:function(t, b, c, d) {
				return -c * Math.sin(t/d * (Math.PI/2)) + b;
			},
		
			EaseInOutSine:function(t, b, c, d) {
				return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b;
			}, 
		
			EaseOutInSine:function(t, b, c, d) {
				if(t < d/2)
					return Tweener.Transitions.EaseOutSine(t*2, b, c/2, d);
		
				return Tweener.Transitions.EaseInSine((t*2)-d, b+c/2, c/2, d);
			}, 
		
		
			EaseInExpo:function(t, b, c, d) {
				return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b - c * 0.001;
			},
		
			EaseOutExpo:function(t, b, c, d) {
				return(t==d) ? b+c : c * 1.001 *(-Math.pow(2, -10 * t/d) + 1) + b;
			},
		
			EaseInOutExpo:function(t, b, c, d) {
				if (t==0) return b;
				if (t==d) return b+c;
				if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b - c * 0.0005;
				return c/2 * 1.0005 * (-Math.pow(2, -10 * --t) + 2) + b;
			},
		
			EaseOutInExpo:function(t, b, c, d) {
				if (t < d/2) 
					return Tweener.Transitions.EaseOutExpo(t*2, b, c/2, d);
		
				return Tweener.Transitions.EaseInExpo((t*2)-d, b+c/2, c/2, d);
			},
		
		
			EaseInCirc:function(t, b, c, d) {
				return -c * (Math.sqrt(1 - (t/=d) * t) - 1) + b;
			},
		
			EaseOutCirc:function(t, b, c, d) {
				return c * Math.sqrt(1 - (t=t/d-1) * t) + b;
			},
		
			EaseInOutCirc:function(t, b, c, d) {
				if ((t/=d/2) < 1) 
					return -c/2 * (Math.sqrt(1 - t * t) - 1) + b;
		
				return c/2 * (Math.sqrt(1 - (t-=2) * t) + 1) + b;
			},
		
			EaseOutInCirc:function(t, b, c, d) {
				if (t < d/2) 
					return Tweener.Transitions.EaseOutCirc(t*2, b, c/2, d);
		
				return Tweener.Transitions.EaseInCirc((t*2)-d, b+c/2, c/2, d);
			},
		
		
			EaseInElastic:function(t, b, c, d, a, p) {
				if (t==0) return b;
				if ((t/=d)==1) return b + c;
				if(!p) p = d * .3;
		
				var s;
				if(!a || a < Math.abs(c)) {
					a = c;
					s = p/4;
				} else
					s = p / (2 * Math.PI) * Math.asin(c/a);
		
				return -(a * Math.pow(2,10 * (t-=1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b;
			},
		
			EaseOutElastic:function(t, b, c, d, a, p) {
				if (t==0) return b;
				if ((t/=d)==1) return b + c;
				if(!p) p = d * .3;
		
				var s;
				if(!a || a < Math.abs(c)) {
					a = c;
					s = p/4;
				} else
					s = p / (2 * Math.PI) * Math.asin(c/a);
		
				return (a * Math.pow(2,-10 * t) * Math.sin((t * d - s) * (2 * Math.PI) / p) + c + b);
			},
		
			EaseInOutElastic:function(t, b, c, d, a, p) {
				if (t==0) return b;
				if ((t/=d/2)==2) return b + c;
				if(!p) p = d * (.3 * 1.5);
		
				var s;
				if(!a || a < Math.abs(c)) {
					a = c;
					s = p/4;
				} else
					s = p / (2 * Math.PI) * Math.asin(c/a);
		
				if (t < 1) 
					return -.5 * (a * Math.pow(2,10 * (t-=1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b;
		
				return a * Math.pow(2,-10 * (t-=1)) * Math.sin((t * d - s) * (2 * Math.PI) / p) * .5 + c + b;
			},
		
			EaseOutInElastic:function(t, b, c, d, a, p) {
				if (t < d/2) 
					return Tweener.Transitions.EaseOutElastic(t*2, b, c/2, d);
		
				return Tweener.Transitions.EaseInElastic((t*2)-d, b+c/2, c/2, d);
			},
		
		
		
			EaseInBack:function(t, b, c, d, s) {
				if(!s) s = 1.70158;
		
				return c * (t/=d) * t * ((s+1) * t - s) + b;
			},
		
			EaseOutBack:function(t, b, c, d, s) {
				if(!s) s = 1.70158;
		
				return c * ((t=t/d-1) * t * ((s+1) * t + s) + 1) + b;
			},
		
			EaseInOutBack:function(t, b, c, d, s) {
				if(!s) s = 1.70158;
		
				if ((t/=d/2) < 1) 
					return c / 2 * (t * t * (((s*=(1.525)) + 1) * t - s)) + b;
		
				return c / 2 * ((t-=2) * t * (((s*=(1.525)) + 1) * t + s) + 2) + b;
			},
		
			EaseOutInBack:function(t, b, c, d, s) {
				if (t < d/2) 
					return Tweener.Transitions.EaseOutBack(t*2, b, c/2, d);
		
				return Tweener.Transitions.EaseInBack((t*2)-d, b+c/2, c/2, d);
			},
		
		
			EaseInBounce:function(t, b, c, d) {
				return c - Tweener.Transitions.EaseOutBounce(d-t, 0, c, d) + b;
			},
		
			EaseOutBounce:function(t, b, c, d) {
				if ((t/=d) < (1/2.75))
					return c * (7.5625 * t * t) + b;
				else if (t < (2/2.75))
					return c * (7.5625 * (t-=(1.5/2.75)) * t + .75) + b;
				else if (t < (2.5/2.75))
					return c * (7.5625 * (t-=(2.25/2.75)) * t + .9375) + b;
				else
					return c * (7.5625 * (t-=(2.625/2.75)) * t + .984375) + b;
			},
		
			EaseInOutBounce:function(t, b, c, d) {
				if (t < d/2) 
					return Tweener.Transitions.EaseInBounce(t*2, 0, c, d) * .5 + b;
		
				return Tweener.Transitions.EaseOutBounce(t*2-d, 0, c, d) * .5 + c * .5 + b;
			},
		
			EaseOutInBounce:function(t, b, c, d) {
				if (t < d/2) 
					return Tweener.Transitions.EaseOutBounce(t*2, b, c/2, d);
		
				return Tweener.Transitions.EaseInBounce((t*2)-d, b+c/2, c/2, d);
			}
			
		}
	}

	return tweener;
}();

/* widget */
// registry, used to store the design configs
var pc3PopupDesignData = {};
var pc3PopupData = new Array();
var pc3Accordions = {};
var pc3Scrollers = {};

var pc3History = {
	setParam : function( widget, id, params, blind ){
		if ( blind ){
			if ( !pc3History.blindParams[widget] ) pc3History.blindParams[widget] = {};
			if ( pc3History.blindParams[widget][id] && pc3History.blindParams[widget][id] == params ) return;
			pc3History.blindParams[widget][id] = params;
		} else {
			var currentParams = pc3History.getCurrentParams();
			if ( !currentParams[widget] ) currentParams[widget] = {};
			if ( currentParams[widget][id] && currentParams[widget][id] == params ) return;
			currentParams[widget][id] = params;
			location.hash = '#pc3Widget'+$H(currentParams).toJSON();
			pc3History.lastParams = location.hash;
		}
	},
	
	unsetParam : function( widget, id ){
		if ( pc3History.blindParams[widget] && pc3History.blindParams[widget][id] ){
			delete pc3History.blindParams[widget][id];
			if ( pc3History.blindParams[widget].length == 0 ) delete pc3History.blindParams[widget];
		} else {
			var currentParams = $H(pc3History.getCurrentParams());
			var newParams = {};
			currentParams.each(function(currentWidget){
				$H(currentWidget.value).each(function(currentId){
					if ( widget == currentWidget.key && id == currentId.key ) return;
					if ( !newParams[currentWidget.key] ) newParams[currentWidget.key] = {};
					if ( !newParams[currentWidget.key][currentId.key] ) newParams[currentWidget.key][currentId.key] = currentId.value;
				});
			});
			if ( $H(newParams).size() ) location.hash = '#pc3Widget'+$H(newParams).toJSON();
			else location.hash = '#pc3Widget';
			pc3History.lastParams = location.hash;
		}
	},
	
	getParam : function( widget, id ){
		if ( pc3History.blindParams[widget] && pc3History.blindParams[widget][id] ){
			return pc3History.blindParams[widget][id];
		} else {
			var currentParams = pc3History.getCurrentParams();
			if ( !currentParams[widget] ) return false;
			if ( id && !currentParams[widget][id] )  return false;
			if ( !id ) return currentParams[widget];
			return currentParams[widget][id];
		}
	},

	getCurrentParams : function(){
		var currentParams = {};
		if ( location.hash && location.hash.substring(0,10) == '#pc3Widget' && location.hash.substring(10) ){
			currentParams = decodeURI(location.hash.substring(10)).evalJSON();
		}
		return currentParams;
	},

	checkHistoryChange : function(){
		if ( location.hash != pc3History.lastParams ){
			pc3History.lastParams = location.hash;
			var currentParams = pc3History.getCurrentParams();
			if ( currentParams['accordion'] ) pc3Widget.updateAccordions(currentParams['accordion']);
			else pc3Widget.updateAccordions({});
		}
	},
	
	lastParams : location.hash,
	blindParams : {}
	
}
	
function pc3WidgetInit(){
	if ( typeof( pc3Widget ) == "undefined" ){
		pc3Widget = new pc3WidgetManagement();
		pc3Widget.init();
		new PeriodicalExecuter(function(pe) { pc3History.checkHistoryChange(); }, 0.5);
	} else {
		pc3Widget.updateAll();
	}
}

function pc3WidgetManagement(){
	var self = this;
	this.mousePosition = {x:0,y:0};
	this.popupManagement = '';
	this.accordions = $H({});
	this.scrollers = $H({});
	this.body = $(document.body);
	this.backgrounds = $H({});
	this.blurs = $H({});
	this.outerDimensionElements = $H({});
		
	this.init = function(){
		Event.observe(document, "mousemove", this.handleMouseMove.bindAsEventListener(this));
		Event.observe(document, "click", this.handleClick.bindAsEventListener(this));
		Event.observe(document, "mouseup", this.handleMouseUp.bindAsEventListener(this));
		Event.observe(window, "resize", this.handleResize.bindAsEventListener(this));
		Event.observe(window, "scroll", this.handleScroll.bindAsEventListener(this));

		this.extendBrowserInfo();
		
		this.initAccordions();
		this.initScrollers();
		this.initPopupManagement();
	}

	this.updateAll = function(){
		if ( typeof( pc3DatepickerManagement ) != "undefined" ) pc3DatepickerManagement.update();
		this.initAccordions();
		this.updateScrollers();
		this.initScrollers();
		if ( !this.popupManagement ) this.initPopupManagement();
		else this.popupManagement.init();
	}

	this.update = function(){ return true; }

	//Widget managements
		//accordions
			this.initAccordions = function(){
				var accordionSates = $H(pc3History.getParam('accordion',''));
				for( var accordionId in pc3Accordions ){ 
					this.accordions.set(accordionId, new pc3Accordion(accordionId, pc3Accordions[accordionId], (accordionSates && accordionSates.get(accordionId)?accordionSates.get(accordionId)['boxId']:-1)));
				}
				pc3Accordions = {};
			}


			this.updateAccordions = function(accordions){
				this.accordions.each(function(accordion){
					if ( accordions[accordion.key] ) accordion.value.toggleBox(accordions[accordion.key]['boxId']);
					else accordion.value.toggleBox(0);
				
				});
			}
		//-----
		//scrollers
			this.initScrollers = function(){
				for( var scrollerId in pc3Scrollers ){
					var bars = new Array();
					if ( pc3Scrollers[scrollerId]['bars'] ) bars = pc3Scrollers[scrollerId]['bars'];				
					var arrows = new Array();
					if ( pc3Scrollers[scrollerId]['arrows'] ) arrows = pc3Scrollers[scrollerId]['arrows'];
					this.scrollers.set(scrollerId, new pc3Scroller(scrollerId, pc3Scrollers[scrollerId]['scroller'], bars, arrows));
				}
				pc3Scrollers = {};
			}
	
			this.updateScrollers = function(){ this.scrollers.each(function(scroller){ scroller.value.update(); }); }
			
			this.updateScroller = function(scrollerId){ if ( this.scrollers.get(scrollerId) ) this.scrollers.get(scrollerId).update(); }
		
			this.scrollTo = function(scrollerId, x, y, elementId, alignment){
				if ( this.scrollers.get(scrollerId) ) this.scrollers.get(scrollerId).scrollTo({x:x,y:y}, elementId, alignment);
			}
		//-----
		//popups
			this.initPopupManagement = function(){ if ( $H(pc3PopupDesignData).size() && $H(pc3PopupData).size() ) this.popupManagement = new pc3PopupManagement(); }

			this.openPopup = function(trigger){
				if ( this.popupManagement ) this.popupManagement.openPopup(trigger);
			}
			
			this.closePopup = function(trigger){
				if ( this.popupManagement ) this.popupManagement.closePopup(trigger); 
			}

			this.closeParentPopup = function(objectId){
				var popup = this.getParentWidget(objectId);
				if ( popup.triggerId ) this.popupManagement.closePopup(popup.triggerId);
			}
			
			this.updatePopup = function(trigger){ if ( this.popupManagement ) this.popupManagement.updatePopup(trigger); }

			this.setClosePopupFunction = function(trigger, callBack){ if ( this.popupManagement ) this.popupManagement.setClosePopupFunction(trigger, callBack); }
		//-----
		
		
		this.getParentWidget = function(element){
			parentWidget = $(element).up('[pc3Widget]');
			if ( parentWidget ) return parentWidget.popup;		
			else return this;
		}
		
	//---------------


	// Utilities
		this.setDocumentDimension = function( fast ){
		
			if ( window.innerHeight && window.scrollMaxY ) var scrollY = window.innerHeight + window.scrollMaxY;
			else if ( document.body.scrollHeight > document.body.offsetHeight ) var scrollY = document.body.scrollHeight;
			else if ( document.documentElement && document.documentElement.scrollHeight > document.documentElement.offsetHeight ) var scrollY = document.documentElement.scrollHeight;
			else var scrollY = document.body.offsetHeight;

			if ( self.innerHeight ) var windowHeight = self.innerHeight;
			else if ( document.documentElement && document.documentElement.clientHeight ) var windowHeight = document.documentElement.clientHeight;
			else if ( document.body ) var windowHeight = document.body.clientHeight;

			if ( scrollY < windowHeight ) var height = windowHeight;
			else var height = scrollY;
				
			if ( fast && this.documentDimension ){
				var width = this.documentDimension.width;
			} else {
				var dummyContent = new Element('div').setStyle({position:'absolute', left:'0px', top:'0px', width:'auto', height:'auto'});
				var html = this.body.innerHTML;
				dummyContent.innerHTML = html.stripScripts();
				dummyContent.hide();
				this.body.appendChild(dummyContent);
				var width = document.viewport.getScrollOffsets().left + document.viewport.getWidth();
				if (dummyContent.getWidth() > width ) width = dummyContent.getWidth();
				dummyContent.remove();
			}
			this.documentDimension = {left:0,top:0,width:width,height:height,right:width,bottom:height};
		}
	
		this.getDocumentDimension = function(){
			if ( !this.documentDimension ) this.setDocumentDimension(false);
			return this.documentDimension; 
		}
		
		this.getElementDimension = function(element){
			var dummyContent = element.cloneNode(true);
			dummyContent.hide();
			dummyContent.innerHTML = dummyContent.innerHTML.stripScripts();
			this.body.appendChild(dummyContent);
			dummyContent.setStyle({position:'absolute',left:'100000px', top:'100000px', width:'auto', height:'auto'});
			var width = dummyContent.getWidth();
			dummyContent.setStyle({left:'0px', top:'0px'});
			var height = dummyContent.getHeight();
			dummyContent.remove();
			return {width:width,height:height};
		}

		this.getOuterDimension = function(element, inline){
			var dummyElement = element;
			if ( !inline ){
				//var dimension = this.outerDimensionElements.get(element);
				//if ( dimension ) return dimension;
				dummyElement = element.cloneNode(true);
				dummyElement.setStyle({position:'absolute',left:'0px', top:'0px',width:'1000px', height:'1000px'});
				dummyElement.hide();
				dummyElement.innerHTML = dummyElement.innerHTML.stripScripts();
				this.body.appendChild(dummyElement);
			}
			
			var dimension = {};
			['Left','Top','Right','Bottom'].each(function(location){
				dimension['offset'+location] = (dummyElement.getStyle('border'+location+'Style')=='none'?0:parseInt((dummyElement.getStyle('border'+location+'Width')?dummyElement.getStyle('border'+location+'Width'):0))) + parseInt((dummyElement.getStyle('padding'+location)?dummyElement.getStyle('padding'+location):0));
			});
			
			dimension.offsetsX = dimension.offsetLeft + dimension.offsetRight;
			dimension.offsetsY = dimension.offsetTop + dimension.offsetBottom;
			dimension.width = (element.getWidth()<0?0:element.getWidth());
			dimension.innerWidth = dimension.width - dimension.offsetsX;
			dimension.height = (element.getHeight()<0?0:element.getHeight());
			dimension.innerHeight = dimension.height - dimension.offsetsY;
			var positions = element.positionedOffset();
			dimension.left = positions.left;
			dimension.top = positions.top;
			if ( !inline ){
				dummyElement.remove();
				this.outerDimensionElements.set(element, dimension);
			}
			return dimension;
		}

		this.mouseIsInside = function(event, element){
			var pointerX = event.pointerX();
			var pointerY = event.pointerY();
			if ( Prototype.Browser.IE ){
				pointerX = pointerX - 2;
				pointerY = pointerY - 2;
			}
			var position = element.cumulativeOffset();
			var triggerArea = {left:position.left,top:position.top,right:(position.left + element.getWidth()),bottom:(position.top + element.getHeight())};
			if ( (pointerX >= triggerArea.left && pointerX < triggerArea.right) && (pointerY >= triggerArea.top && pointerY < triggerArea.bottom) ) return true;
			return false;
		}

		this.findElementById = function(parent, id){
			var needle = '';
			parent.childElements().each(function(element){
				if ( element.id == id ){
					needle = element;
					throw $break;
				} else {
					needle = self.findElementById(element, id);
					if ( needle ) throw $break;
				}
			});
			return needle;
		}
		
		this.getOpacity = function(element){
			var opacity = element.getStyle('filter');
			if ( opacity && opacity != 'none' ) return opacity.slice((opacity.indexOf('=')+1),(opacity.length-1))/100;
			return element.getStyle('opacity');
		}
		
		this.addBackground = function(parentElement, color, opacity){
			if ( this.backgrounds.get(parentElement) ) return false;
			this.backgrounds.set(parentElement, new pc3Background(this, parentElement, color, opacity));
			return this.backgrounds.get(parentElement);
		}
		
		this.removeBackground = function(parentElement){
			if ( this.backgrounds.get(parentElement) ){
				var element = this.backgrounds.get(parentElement);
				delete element;
				this.backgrounds.unset(parentElement);
			}
		}

		this.extendBrowserInfo = function(){
			Prototype.Browser.IE6 = ( Prototype.Browser.IE && parseInt(navigator.userAgent.substring(navigator.userAgent.indexOf("MSIE")+5)) == 6 );
			Prototype.Browser.IE7 = ( Prototype.Browser.IE && parseInt(navigator.userAgent.substring(navigator.userAgent.indexOf("MSIE")+5)) == 7 );
			Prototype.Browser.IE8 = ( Prototype.Browser.IE && parseInt(navigator.userAgent.substring(navigator.userAgent.indexOf("MSIE")+5)) == 8 );
		}
		
		
		this.blur = function(radius){
			var blur = this.blurs.get(radius);
			if ( blur ) return blur;
			var breakOff = (1+radius);
			var dimension = 2 * breakOff;
			var bluredMatrix = {};
			$A($R(2, dimension)).each(function(row){
				$A($R(2, dimension)).each(function(column){
					var counter = 0;
					var sum = 0;
					$A($R(Math.max(parseInt(row-radius), 1), Math.min(parseInt(row)+radius, dimension))).each(function(sectionRow){
						$A($R(Math.max(parseInt(column-radius), 1), Math.min(parseInt(column)+radius, dimension))).each(function(sectionColumn){
							if ( sectionColumn > breakOff && sectionRow > breakOff ) sum = sum + 1;
							counter++;
						});
					});
					var average = sum/counter;
					if ( column == 2 ) var columns = {1:average};
					else var columns = bluredMatrix[row-1];
					if ( column == 2 ) bluredMatrix[row-1] = columns;
					else columns[column-1] = average;
					
				});
			});
			this.blurs.set(radius, bluredMatrix);
			return bluredMatrix;
		}

		this.getFlashObject = function(id){
			if ( Prototype.Browser.IE && window[id] ) return window[id];
			if ( !Prototype.Browser.IE && document[id] ) return document[id];
			return false;
		}
	//---------------
	


	// Event handlers
		this.handleMouseMove = function(event){
			this.mousePosition.x = event.pointerX();
			this.mousePosition.y = event.pointerY();
			if ( this.popupManagement ) this.popupManagement.handleMouseMove();
			this.scrollers.each(function(scroller){ scroller.value.handleMouseMove(event); });
		}
	
		this.handleClick = function(event){
			if ( this.popupManagement ) this.popupManagement.handleClick(event);
		}

		this.handleMouseUp = function(event){
			this.scrollers.each(function(scroller){ scroller.value.handleMouseUp(event); });
		}
	
		this.handleResize = function(event){
			if ( this.backgrounds.size() > 0 ) this.setDocumentDimension(false);
			this.backgrounds.each(function(background){ background.value.resize() });
			if ( this.popupManagement ) this.popupManagement.handleResize();
		}
	
		this.handleScroll = function(event){
			if ( this.popupManagement ){
				this.setDocumentDimension(true);
				this.popupManagement.handleScroll();
			}
		}
	//---------------
	
}

function pc3Background(management, parent, color, opacity){
	var self = this;
	this.management = management;
	this.parent = parent;
	
	this.background = '';
	this.effects = $H({});
	this.state = '';

	this.init = function(color, opacity){
		this.background = new Element('div').setStyle({position:'absolute', zIndex:999, backgroundColor:(color?color:'none')});
		this.opacity = (opacity?opacity:75);
		this.background.hide();
		this.parent.appendChild(this.background);
	}
	
	this.open = function(delay, effect, duration){
		if ( delay > 0 ) this.waitingToOpen = this.openBackground.bind(this, effect, duration).delay(delay);
		else this.openBackground(effect, duration);
	}

	this.openBackground = function(effect, duration){
		this.waitingToOpen = '';
		this.state = 'opening';
		if ( this.parent == pc3Widget.body ){
			var boundingBox = pc3Widget.getDocumentDimension();
		} else {
			var boundingBox = {
				left:0,
				top:0,
				right:this.parent.getWidth(),
				bottom:this.parent.getHeight()
			};
		}

		this.background.setStyle({
			left:boundingBox.left +'px',
			top:boundingBox.top +'px',
			width:(boundingBox.right - boundingBox.left) +'px',
			height:(boundingBox.bottom - boundingBox.top) +'px'
		});
		
		// fadeIn
		if ( effect == 'fadeIn' ){
			this.setOpacity({value:0});
			var duration = (duration?parseFloat(duration)*1000:0);
			this.effects.set('opacity', new pc3Tween('opacity', 0, this.opacity, duration, 'EaseNone', this.setOpacity.bind(self), this.removeEffect.bind(self)));
		} else {
			this.setOpacity({value:this.opacity});
		}
		this.background.show();
		this.animate();
	}
	
	this.close = function(delay, effect, duration){
		if ( delay > 0 ) this.waitingToClose = this.closeBackground.bind(this, effect, duration).delay(delay);
		else this.closeBackground(effect, duration);
	}
	
	this.closeBackground = function(effect, duration){
		this.waitingToClose = '';
		this.state = 'closing';
		// fadeOut
		if ( effect == 'fadeOut' ){
			var duration = (duration?parseFloat(duration)*1000:0);
			this.effects.set('opacity', new pc3Tween('opacity', this.opacity, 0, duration, 'EaseNone', this.setOpacity.bind(self), this.removeEffect.bind(self)));
		}
		this.animate();
	}
	
	this.removeEffect = function(effect){ this.effects.unset(effect.name); }
	
	this.setOpacity = function(effect){ this.background.setOpacity(effect.value/100); }

	this.animate = function(){
		var finished = true;
		for ( var name in this.effects ) finished = false;
		
		if ( this.effects.size() ){
			this.animate.bind(this).delay(0.02)
		} else {
			if ( this.state == 'closing' ){
				this.state = 'closed';
				this.background.remove();
				this.management.removeBackground(this.parent);
			} else {
				this.state = 'opened';
			}
		}
	}

	this.resize = function(){
		if ( this.parent != pc3Widget.body ) return;
		var boundingBox = this.management.getDocumentDimension();
		this.background.setStyle({
			left:boundingBox.left +'px',
			top:boundingBox.top +'px',
			width:(boundingBox.right - boundingBox.left) +'px',
			height:(boundingBox.bottom - boundingBox.top) +'px'
		
		});
	}
	
	this.init(color, opacity);
}



function pc3Tween(name, start, end, duration, transition, onUpdate, onComplete){
	var self = this;
	this.name = name;
	this.value = start;
	this.end = end;
	this.onCompleteFunction = onComplete;
	this.onUpdateFunction = onUpdate;

	this.onUpdate = function(value){
		this.value = value;
		this.onUpdateFunction(this);
	}
	
	this.onComplete = function(){
		this.value = this.end;
		this.onUpdateFunction(this);
		this.onCompleteFunction(this);
	};
	
	this.init = function(duration, transition){
		Tweener.AddTween(
			{ value:this.value },
			{ value:this.end },
			{
				Duration: duration,
				Transition: Tweener.Transitions[transition],
				OnComplete: this.onComplete.bind(self),
				OnUpdate: function(tweener){
					if ( tweener.ScopeClean.value ) self.onUpdate.bind(self)(tweener.ScopeClean.value); 
				}
			}
		);
	}

	this.init(duration, transition);
}


/* ajax */
function pc3Ajax(url,params,method){
	this.setURL(url);
	this.setParams(params);
	this.setMethod(method);
	
	this.onSuccess = null;
	this.onError = null;
}

pc3Ajax.createRequest = function (){
	if ( window.XMLHttpRequest ) return new XMLHttpRequest();
	if ( window.ActiveXObject ){
		try {
			return new ActiveXObject("MSXML2.XMLHTTP.3.0");
		} catch(e){
			return null;
		}
	}
	return null;
}

pc3Ajax.addParams = function(url,params){
	if ( !params ) return url;
	
	var paramString = '';
	switch ( typeof params ) {
		case 'object':
			for( var name in params ) {
				paramString += (paramString?'&':'') + encodeURIComponent(name) +'='+ encodeURIComponent(params[name]);
			}
			break;
			
		default:
			paramString = params;
			break;
	}
	
	if ( url.indexOf('?') >= 0 ) url += '&';
	else url += '?';
	
	return url + paramString;
}

pc3Ajax.getFormData = function(form){
	var params = '';
	
	for ( var i=0; i<form.elements.length; i++ ) {
		var element = form.elements[i];
		if ( !element.name ) continue;
		if ( element.type == 'checkbox' && !element.checked ) continue;
		if ( element.type == 'radio' && !element.checked ) continue;
		params += (params?'&':'') + encodeURIComponent(element.name) +'='+ encodeURIComponent(element.value);
	}
	
	return params;
}

pc3Ajax.prototype.setURL = function (url){
	this.url = url;
}

pc3Ajax.prototype.setParams = function (params){
	this.params = params;
}

pc3Ajax.prototype.setMethod = function (method){
	this.method = method;
	
	if ( !this.method ) this.method = 'GET';
	else this.method = this.method.toUpperCase();
	
	if ( this.method != 'GET' ) this.method = 'POST';
}

pc3Ajax.prototype.onStateChange = function(request){
	if ( request.readyState == 4 ){
		if ( (request.status == 200 || request.status == 304) ){
			if ( this.onSuccess ) this.onSuccess(request.responseText, request.responseXML, request );
		} else {
			if ( this.onError ) this.onError('Request failed: '+'['+request.status+', '+request.statusText+']', request );
		}
	}
	
	return false;
}



pc3Ajax.prototype.doRequest = function(){
	if ( !this.url ) return false;
	
	var postParams = '';
	
	var url = this.url;
	
	if ( this.method == 'GET' ) {
		url = pc3Ajax.addParams(url,this.params);
	} else {
		postParams = this.params;
	}
	
	var self = this;
	var request = pc3Ajax.createRequest();
	if ( !request ) return false;
	
	request.onreadystatechange = function(){ self.onStateChange(request); }
	
	request.open( this.method, url, true );
	if ( this.method == 'POST' ) request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
	
	if ( this.onRequest ) this.onRequest();
	
	request.send(postParams);
	
	return true;
}

function pc3AjaxContent(id,onRequestHTML,onErrorHTML, parentWidget, element){
	if ( element ) this.defaultContent = element;
	else this.defaultContent = document.getElementById(id);
	if ( !this.defaultContent ) return;

	this.parentWidget = parentWidget;
	if ( !this.parentWidget && typeof( pc3Widget ) != "undefined" ) this.parentWidget = pc3Widget.getParentWidget(this.defaultContent);

	this.id = this.defaultContent.id;
	this.onRequestHTML = onRequestHTML;
	this.onErrorHTML = onErrorHTML;
	
	this.replaceDefaultContent = function(newHTML){
		try {
			var cleanHTML = newHTML.stripScripts();
			this.defaultContent.innerHTML = cleanHTML;
			newHTML.evalScripts();
		} catch( exception ) {
			if ( window.console ) window.console.error(exception);
		}
	}
	
	this.onSuccessHandler = function(responseText,responseXML){
		this.replaceDefaultContent(responseText);
		if ( this.parentWidget ) this.parentWidget.update();

		if ( typeof( pc3Widget ) != "undefined" ) pc3Widget.updateAll();
		
		if ( pc3AjaxContent.onload ) {
			pc3AjaxContent.onload();
			pc3AjaxContent.onload = null;
		}
	}
	
	this.onRequestHandler = function(){
		if ( this.onRequestHTML ) this.replaceDefaultContent(this.onRequestHTML);
		if ( this.parentWidget ) this.parentWidget.update();
	}
	
	this.onErrorHandler = function(errorMsg){
		if ( this.onErrorHTML ){
			this.replaceDefaultContent(this.onErrorHTML);
		}
		if ( this.parentWidget ) this.parentWidget.update();
	}
	
	this.requestContent = function(url,postData){
		url = pc3Ajax.addParams(url,'pc3AjaxContent='+ this.id);
		var ajax = new pc3Ajax(url,postData,(postData?'POST':'GET'));
		
		var self = this;
		ajax.onRequest = function(){ self.onRequestHandler(); }
		ajax.onSuccess = function(responseText,responseXML){ self.onSuccessHandler(responseText,responseXML); }
		ajax.onError = function(errorMsg){ self.onErrorHandler(errorMsg); }
		
		if ( !ajax.doRequest() ) return false;
		
		return true;
	}
}

pc3AjaxContent.requestPost = function(form,id,url){
	if ( !window.pc3AjaxContentObjects[id] ) return true;
	var ajaxContent = window.pc3AjaxContentObjects[id];
	
	var data = pc3Ajax.getFormData(form);
	
	if ( !ajaxContent.requestContent((url?url:form.action),data) ) return true;
	
	return false;
}

pc3AjaxContent.requestLinkOnClick = function(event,link,id,url){
	if ( !window.pc3AjaxContentObjects[id] ) return false;
	
	var ajaxContent = window.pc3AjaxContentObjects[id];
	
	if ( !ajaxContent.requestContent(url) ) return false;
	
	cp(event);
	
	return true;
}

pc3AjaxContent.requestLink = function(event,link,id,url){
	if ( !window.pc3AjaxContentObjects[id] ) return;
	
	if ( !url ) {
		if ( !link.pc3AjaxRequest ) link.pc3AjaxRequest = link.href;
		url = link.pc3AjaxRequest;
	}
	
	var ajaxContent = window.pc3AjaxContentObjects[id];
	
	if ( !ajaxContent.requestContent(url) ) return;
	
	if ( link ) link.href = 'javascript: void(0);';
	
	cp(event);
}

/* scriptaculous/scriptaculous */
// script.aculo.us scriptaculous.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008

// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// 
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// For details, see the script.aculo.us web site: http://script.aculo.us/

var Scriptaculous = {
  Version: '1.8.1',
  require: function(libraryName) {
    // inserting via DOM fails in Safari 2.0, so brute force approach
    document.write('<script type="text/javascript" src="'+libraryName+'"><\/script>');
  },
  REQUIRED_PROTOTYPE: '1.6.0',
  load: function() {
    function convertVersionString(versionString){
      var r = versionString.split('.');
      return parseInt(r[0])*100000 + parseInt(r[1])*1000 + parseInt(r[2]);
    }
 
    if((typeof Prototype=='undefined') || 
       (typeof Element == 'undefined') || 
       (typeof Element.Methods=='undefined') ||
       (convertVersionString(Prototype.Version) < 
        convertVersionString(Scriptaculous.REQUIRED_PROTOTYPE)))
       throw("script.aculo.us requires the Prototype JavaScript framework >= " +
        Scriptaculous.REQUIRED_PROTOTYPE);
    
    $A(document.getElementsByTagName("script")).findAll( function(s) {
      return (s.src && s.src.match(/scriptaculous\.js(\?.*)?$/))
    }).each( function(s) {
      var path = s.src.replace(/scriptaculous\.js(\?.*)?$/,'');
      var includes = s.src.match(/\?.*load=([a-z,]*)/);
      (includes ? includes[1] : 'builder,effects,dragdrop,controls,slider,sound').split(',').each(
       function(include) { Scriptaculous.require(path+include+'.js') });
    });
  }
}

Scriptaculous.load();

/* scriptaculous/effects */
// script.aculo.us effects.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008

// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// Contributors:
//  Justin Palmer (http://encytemedia.com/)
//  Mark Pilgrim (http://diveintomark.org/)
//  Martin Bialasinki
// 
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/ 

// converts rgb() and #xxx to #xxxxxx format,  
// returns self (or first argument) if not convertable  
String.prototype.parseColor = function() {  
  var color = '#';
  if (this.slice(0,4) == 'rgb(') {  
    var cols = this.slice(4,this.length-1).split(',');  
    var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);  
  } else {  
    if (this.slice(0,1) == '#') {  
      if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();  
      if (this.length==7) color = this.toLowerCase();  
    }  
  }  
  return (color.length==7 ? color : (arguments[0] || this));  
};

/*--------------------------------------------------------------------------*/

Element.collectTextNodes = function(element) {  
  return $A($(element).childNodes).collect( function(node) {
    return (node.nodeType==3 ? node.nodeValue : 
      (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
  }).flatten().join('');
};

Element.collectTextNodesIgnoreClass = function(element, className) {  
  return $A($(element).childNodes).collect( function(node) {
    return (node.nodeType==3 ? node.nodeValue : 
      ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? 
        Element.collectTextNodesIgnoreClass(node, className) : ''));
  }).flatten().join('');
};

Element.setContentZoom = function(element, percent) {
  element = $(element);  
  element.setStyle({fontSize: (percent/100) + 'em'});   
  if (Prototype.Browser.WebKit) window.scrollBy(0,0);
  return element;
};

Element.getInlineOpacity = function(element){
  return $(element).style.opacity || '';
};

Element.forceRerendering = function(element) {
  try {
    element = $(element);
    var n = document.createTextNode(' ');
    element.appendChild(n);
    element.removeChild(n);
  } catch(e) { }
};

/*--------------------------------------------------------------------------*/

var Effect = {
  _elementDoesNotExistError: {
    name: 'ElementDoesNotExistError',
    message: 'The specified DOM element does not exist, but is required for this effect to operate'
  },
  Transitions: {
    linear: Prototype.K,
    sinoidal: function(pos) {
      return (-Math.cos(pos*Math.PI)/2) + 0.5;
    },
    reverse: function(pos) {
      return 1-pos;
    },
    flicker: function(pos) {
      var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
      return pos > 1 ? 1 : pos;
    },
    wobble: function(pos) {
      return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
    },
    pulse: function(pos, pulses) { 
      pulses = pulses || 5; 
      return (
        ((pos % (1/pulses)) * pulses).round() == 0 ? 
              ((pos * pulses * 2) - (pos * pulses * 2).floor()) : 
          1 - ((pos * pulses * 2) - (pos * pulses * 2).floor())
        );
    },
    spring: function(pos) { 
      return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); 
    },
    none: function(pos) {
      return 0;
    },
    full: function(pos) {
      return 1;
    }
  },
  DefaultOptions: {
    duration:   1.0,   // seconds
    fps:        100,   // 100= assume 66fps max.
    sync:       false, // true for combining
    from:       0.0,
    to:         1.0,
    delay:      0.0,
    queue:      'parallel'
  },
  tagifyText: function(element) {
    var tagifyStyle = 'position:relative';
    if (Prototype.Browser.IE) tagifyStyle += ';zoom:1';
    
    element = $(element);
    $A(element.childNodes).each( function(child) {
      if (child.nodeType==3) {
        child.nodeValue.toArray().each( function(character) {
          element.insertBefore(
            new Element('span', {style: tagifyStyle}).update(
              character == ' ' ? String.fromCharCode(160) : character), 
              child);
        });
        Element.remove(child);
      }
    });
  },
  multiple: function(element, effect) {
    var elements;
    if (((typeof element == 'object') || 
        Object.isFunction(element)) && 
       (element.length))
      elements = element;
    else
      elements = $(element).childNodes;
      
    var options = Object.extend({
      speed: 0.1,
      delay: 0.0
    }, arguments[2] || { });
    var masterDelay = options.delay;

    $A(elements).each( function(element, index) {
      new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
    });
  },
  PAIRS: {
    'slide':  ['SlideDown','SlideUp'],
    'blind':  ['BlindDown','BlindUp'],
    'appear': ['Appear','Fade']
  },
  toggle: function(element, effect) {
    element = $(element);
    effect = (effect || 'appear').toLowerCase();
    var options = Object.extend({
      queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
    }, arguments[2] || { });
    Effect[element.visible() ? 
      Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
  }
};

Effect.DefaultOptions.transition = Effect.Transitions.sinoidal;

/* ------------- core effects ------------- */

Effect.ScopedQueue = Class.create(Enumerable, {
  initialize: function() {
    this.effects  = [];
    this.interval = null;    
  },
  _each: function(iterator) {
    this.effects._each(iterator);
  },
  add: function(effect) {
    var timestamp = new Date().getTime();
    
    var position = Object.isString(effect.options.queue) ? 
      effect.options.queue : effect.options.queue.position;
    
    switch(position) {
      case 'front':
        // move unstarted effects after this effect  
        this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
            e.startOn  += effect.finishOn;
            e.finishOn += effect.finishOn;
          });
        break;
      case 'with-last':
        timestamp = this.effects.pluck('startOn').max() || timestamp;
        break;
      case 'end':
        // start effect after last queued effect has finished
        timestamp = this.effects.pluck('finishOn').max() || timestamp;
        break;
    }
    
    effect.startOn  += timestamp;
    effect.finishOn += timestamp;

    if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
      this.effects.push(effect);
    
    if (!this.interval)
      this.interval = setInterval(this.loop.bind(this), 15);
  },
  remove: function(effect) {
    this.effects = this.effects.reject(function(e) { return e==effect });
    if (this.effects.length == 0) {
      clearInterval(this.interval);
      this.interval = null;
    }
  },
  loop: function() {
    var timePos = new Date().getTime();
    for(var i=0, len=this.effects.length;i<len;i++) 
      this.effects[i] && this.effects[i].loop(timePos);
  }
});

Effect.Queues = {
  instances: $H(),
  get: function(queueName) {
    if (!Object.isString(queueName)) return queueName;
    
    return this.instances.get(queueName) ||
      this.instances.set(queueName, new Effect.ScopedQueue());
  }
};
Effect.Queue = Effect.Queues.get('global');

Effect.Base = Class.create({
  position: null,
  start: function(options) {
    function codeForEvent(options,eventName){
      return (
        (options[eventName+'Internal'] ? 'this.options.'+eventName+'Internal(this);' : '') +
        (options[eventName] ? 'this.options.'+eventName+'(this);' : '')
      );
    }
    if (options && options.transition === false) options.transition = Effect.Transitions.linear;
    this.options      = Object.extend(Object.extend({ },Effect.DefaultOptions), options || { });
    this.currentFrame = 0;
    this.state        = 'idle';
    this.startOn      = this.options.delay*1000;
    this.finishOn     = this.startOn+(this.options.duration*1000);
    this.fromToDelta  = this.options.to-this.options.from;
    this.totalTime    = this.finishOn-this.startOn;
    this.totalFrames  = this.options.fps*this.options.duration;
    
    eval('this.render = function(pos){ '+
      'if (this.state=="idle"){this.state="running";'+
      codeForEvent(this.options,'beforeSetup')+
      (this.setup ? 'this.setup();':'')+ 
      codeForEvent(this.options,'afterSetup')+
      '};if (this.state=="running"){'+
      'pos=this.options.transition(pos)*'+this.fromToDelta+'+'+this.options.from+';'+
      'this.position=pos;'+
      codeForEvent(this.options,'beforeUpdate')+
      (this.update ? 'this.update(pos);':'')+
      codeForEvent(this.options,'afterUpdate')+
      '}}');
    
    this.event('beforeStart');
    if (!this.options.sync)
      Effect.Queues.get(Object.isString(this.options.queue) ? 
        'global' : this.options.queue.scope).add(this);
  },
  loop: function(timePos) {
    if (timePos >= this.startOn) {
      if (timePos >= this.finishOn) {
        this.render(1.0);
        this.cancel();
        this.event('beforeFinish');
        if (this.finish) this.finish(); 
        this.event('afterFinish');
        return;  
      }
      var pos   = (timePos - this.startOn) / this.totalTime,
          frame = (pos * this.totalFrames).round();
      if (frame > this.currentFrame) {
        this.render(pos);
        this.currentFrame = frame;
      }
    }
  },
  cancel: function() {
    if (!this.options.sync)
      Effect.Queues.get(Object.isString(this.options.queue) ? 
        'global' : this.options.queue.scope).remove(this);
    this.state = 'finished';
  },
  event: function(eventName) {
    if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
    if (this.options[eventName]) this.options[eventName](this);
  },
  inspect: function() {
    var data = $H();
    for(property in this)
      if (!Object.isFunction(this[property])) data.set(property, this[property]);
    return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';
  }
});

Effect.Parallel = Class.create(Effect.Base, {
  initialize: function(effects) {
    this.effects = effects || [];
    this.start(arguments[1]);
  },
  update: function(position) {
    this.effects.invoke('render', position);
  },
  finish: function(position) {
    this.effects.each( function(effect) {
      effect.render(1.0);
      effect.cancel();
      effect.event('beforeFinish');
      if (effect.finish) effect.finish(position);
      effect.event('afterFinish');
    });
  }
});

Effect.Tween = Class.create(Effect.Base, {
  initialize: function(object, from, to) {
    object = Object.isString(object) ? $(object) : object;
    var args = $A(arguments), method = args.last(), 
      options = args.length == 5 ? args[3] : null;
    this.method = Object.isFunction(method) ? method.bind(object) :
      Object.isFunction(object[method]) ? object[method].bind(object) : 
      function(value) { object[method] = value };
    this.start(Object.extend({ from: from, to: to }, options || { }));
  },
  update: function(position) {
    this.method(position);
  }
});

Effect.Event = Class.create(Effect.Base, {
  initialize: function() {
    this.start(Object.extend({ duration: 0 }, arguments[0] || { }));
  },
  update: Prototype.emptyFunction
});

Effect.Opacity = Class.create(Effect.Base, {
  initialize: function(element) {
    this.element = $(element);
    if (!this.element) throw(Effect._elementDoesNotExistError);
    // make this work on IE on elements without 'layout'
    if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
      this.element.setStyle({zoom: 1});
    var options = Object.extend({
      from: this.element.getOpacity() || 0.0,
      to:   1.0
    }, arguments[1] || { });
    this.start(options);
  },
  update: function(position) {
    this.element.setOpacity(position);
  }
});

Effect.Move = Class.create(Effect.Base, {
  initialize: function(element) {
    this.element = $(element);
    if (!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({
      x:    0,
      y:    0,
      mode: 'relative'
    }, arguments[1] || { });
    this.start(options);
  },
  setup: function() {
    this.element.makePositioned();
    this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
    this.originalTop  = parseFloat(this.element.getStyle('top')  || '0');
    if (this.options.mode == 'absolute') {
      this.options.x = this.options.x - this.originalLeft;
      this.options.y = this.options.y - this.originalTop;
    }
  },
  update: function(position) {
    this.element.setStyle({
      left: (this.options.x  * position + this.originalLeft).round() + 'px',
      top:  (this.options.y  * position + this.originalTop).round()  + 'px'
    });
  }
});

// for backwards compatibility
Effect.MoveBy = function(element, toTop, toLeft) {
  return new Effect.Move(element, 
    Object.extend({ x: toLeft, y: toTop }, arguments[3] || { }));
};

Effect.Scale = Class.create(Effect.Base, {
  initialize: function(element, percent) {
    this.element = $(element);
    if (!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({
      scaleX: true,
      scaleY: true,
      scaleContent: true,
      scaleFromCenter: false,
      scaleMode: 'box',        // 'box' or 'contents' or { } with provided values
      scaleFrom: 100.0,
      scaleTo:   percent
    }, arguments[2] || { });
    this.start(options);
  },
  setup: function() {
    this.restoreAfterFinish = this.options.restoreAfterFinish || false;
    this.elementPositioning = this.element.getStyle('position');
    
    this.originalStyle = { };
    ['top','left','width','height','fontSize'].each( function(k) {
      this.originalStyle[k] = this.element.style[k];
    }.bind(this));
      
    this.originalTop  = this.element.offsetTop;
    this.originalLeft = this.element.offsetLeft;
    
    var fontSize = this.element.getStyle('font-size') || '100%';
    ['em','px','%','pt'].each( function(fontSizeType) {
      if (fontSize.indexOf(fontSizeType)>0) {
        this.fontSize     = parseFloat(fontSize);
        this.fontSizeType = fontSizeType;
      }
    }.bind(this));
    
    this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
    
    this.dims = null;
    if (this.options.scaleMode=='box')
      this.dims = [this.element.offsetHeight, this.element.offsetWidth];
    if (/^content/.test(this.options.scaleMode))
      this.dims = [this.element.scrollHeight, this.element.scrollWidth];
    if (!this.dims)
      this.dims = [this.options.scaleMode.originalHeight,
                   this.options.scaleMode.originalWidth];
  },
  update: function(position) {
    var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
    if (this.options.scaleContent && this.fontSize)
      this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
    this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
  },
  finish: function(position) {
    if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
  },
  setDimensions: function(height, width) {
    var d = { };
    if (this.options.scaleX) d.width = width.round() + 'px';
    if (this.options.scaleY) d.height = height.round() + 'px';
    if (this.options.scaleFromCenter) {
      var topd  = (height - this.dims[0])/2;
      var leftd = (width  - this.dims[1])/2;
      if (this.elementPositioning == 'absolute') {
        if (this.options.scaleY) d.top = this.originalTop-topd + 'px';
        if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
      } else {
        if (this.options.scaleY) d.top = -topd + 'px';
        if (this.options.scaleX) d.left = -leftd + 'px';
      }
    }
    this.element.setStyle(d);
  }
});

Effect.Highlight = Class.create(Effect.Base, {
  initialize: function(element) {
    this.element = $(element);
    if (!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { });
    this.start(options);
  },
  setup: function() {
    // Prevent executing on elements not in the layout flow
    if (this.element.getStyle('display')=='none') { this.cancel(); return; }
    // Disable background image during the effect
    this.oldStyle = { };
    if (!this.options.keepBackgroundImage) {
      this.oldStyle.backgroundImage = this.element.getStyle('background-image');
      this.element.setStyle({backgroundImage: 'none'});
    }
    if (!this.options.endcolor)
      this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
    if (!this.options.restorecolor)
      this.options.restorecolor = this.element.getStyle('background-color');
    // init color calculations
    this._base  = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
    this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
  },
  update: function(position) {
    this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
      return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) });
  },
  finish: function() {
    this.element.setStyle(Object.extend(this.oldStyle, {
      backgroundColor: this.options.restorecolor
    }));
  }
});

Effect.ScrollTo = function(element) {
  var options = arguments[1] || { },
    scrollOffsets = document.viewport.getScrollOffsets(),
    elementOffsets = $(element).cumulativeOffset(),
    max = (window.height || document.body.scrollHeight) - document.viewport.getHeight();  

  if (options.offset) elementOffsets[1] += options.offset;

  return new Effect.Tween(null,
    scrollOffsets.top,
    elementOffsets[1] > max ? max : elementOffsets[1],
    options,
    function(p){ scrollTo(scrollOffsets.left, p.round()) }
  );
};

/* ------------- combination effects ------------- */

Effect.Fade = function(element) {
  element = $(element);
  var oldOpacity = element.getInlineOpacity();
  var options = Object.extend({
    from: element.getOpacity() || 1.0,
    to:   0.0,
    afterFinishInternal: function(effect) { 
      if (effect.options.to!=0) return;
      effect.element.hide().setStyle({opacity: oldOpacity}); 
    }
  }, arguments[1] || { });
  return new Effect.Opacity(element,options);
};

Effect.Appear = function(element) {
  element = $(element);
  var options = Object.extend({
  from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
  to:   1.0,
  // force Safari to render floated elements properly
  afterFinishInternal: function(effect) {
    effect.element.forceRerendering();
  },
  beforeSetup: function(effect) {
    effect.element.setOpacity(effect.options.from).show(); 
  }}, arguments[1] || { });
  return new Effect.Opacity(element,options);
};

Effect.Puff = function(element) {
  element = $(element);
  var oldStyle = { 
    opacity: element.getInlineOpacity(), 
    position: element.getStyle('position'),
    top:  element.style.top,
    left: element.style.left,
    width: element.style.width,
    height: element.style.height
  };
  return new Effect.Parallel(
   [ new Effect.Scale(element, 200, 
      { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), 
     new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], 
     Object.extend({ duration: 1.0, 
      beforeSetupInternal: function(effect) {
        Position.absolutize(effect.effects[0].element)
      },
      afterFinishInternal: function(effect) {
         effect.effects[0].element.hide().setStyle(oldStyle); }
     }, arguments[1] || { })
   );
};

Effect.BlindUp = function(element) {
  element = $(element);
  element.makeClipping();
  return new Effect.Scale(element, 0,
    Object.extend({ scaleContent: false, 
      scaleX: false, 
      restoreAfterFinish: true,
      afterFinishInternal: function(effect) {
        effect.element.hide().undoClipping();
      } 
    }, arguments[1] || { })
  );
};

Effect.BlindDown = function(element) {
  element = $(element);
  var elementDimensions = element.getDimensions();
  return new Effect.Scale(element, 100, Object.extend({ 
    scaleContent: false, 
    scaleX: false,
    scaleFrom: 0,
    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
    restoreAfterFinish: true,
    afterSetup: function(effect) {
      effect.element.makeClipping().setStyle({height: '0px'}).show(); 
    },  
    afterFinishInternal: function(effect) {
      effect.element.undoClipping();
    }
  }, arguments[1] || { }));
};

Effect.SwitchOff = function(element) {
  element = $(element);
  var oldOpacity = element.getInlineOpacity();
  return new Effect.Appear(element, Object.extend({
    duration: 0.4,
    from: 0,
    transition: Effect.Transitions.flicker,
    afterFinishInternal: function(effect) {
      new Effect.Scale(effect.element, 1, { 
        duration: 0.3, scaleFromCenter: true,
        scaleX: false, scaleContent: false, restoreAfterFinish: true,
        beforeSetup: function(effect) { 
          effect.element.makePositioned().makeClipping();
        },
        afterFinishInternal: function(effect) {
          effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});
        }
      })
    }
  }, arguments[1] || { }));
};

Effect.DropOut = function(element) {
  element = $(element);
  var oldStyle = {
    top: element.getStyle('top'),
    left: element.getStyle('left'),
    opacity: element.getInlineOpacity() };
  return new Effect.Parallel(
    [ new Effect.Move(element, {x: 0, y: 100, sync: true }), 
      new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
    Object.extend(
      { duration: 0.5,
        beforeSetup: function(effect) {
          effect.effects[0].element.makePositioned(); 
        },
        afterFinishInternal: function(effect) {
          effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
        } 
      }, arguments[1] || { }));
};

Effect.Shake = function(element) {
  element = $(element);
  var options = Object.extend({
    distance: 20,
    duration: 0.5
  }, arguments[1] || {});
  var distance = parseFloat(options.distance);
  var split = parseFloat(options.duration) / 10.0;
  var oldStyle = {
    top: element.getStyle('top'),
    left: element.getStyle('left') };
    return new Effect.Move(element,
      { x:  distance, y: 0, duration: split, afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x: -distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x:  distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x: -distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x:  distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) {
        effect.element.undoPositioned().setStyle(oldStyle);
  }}) }}) }}) }}) }}) }});
};

Effect.SlideDown = function(element) {
  element = $(element).cleanWhitespace();
  // SlideDown need to have the content of the element wrapped in a container element with fixed height!
  var oldInnerBottom = element.down().getStyle('bottom');
  var elementDimensions = element.getDimensions();
  return new Effect.Scale(element, 100, Object.extend({ 
    scaleContent: false, 
    scaleX: false, 
    scaleFrom: window.opera ? 0 : 1,
    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
    restoreAfterFinish: true,
    afterSetup: function(effect) {
      effect.element.makePositioned();
      effect.element.down().makePositioned();
      if (window.opera) effect.element.setStyle({top: ''});
      effect.element.makeClipping().setStyle({height: '0px'}).show(); 
    },
    afterUpdateInternal: function(effect) {
      effect.element.down().setStyle({bottom:
        (effect.dims[0] - effect.element.clientHeight) + 'px' }); 
    },
    afterFinishInternal: function(effect) {
      effect.element.undoClipping().undoPositioned();
      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }
    }, arguments[1] || { })
  );
};

Effect.SlideUp = function(element) {
  element = $(element).cleanWhitespace();
  var oldInnerBottom = element.down().getStyle('bottom');
  var elementDimensions = element.getDimensions();
  return new Effect.Scale(element, window.opera ? 0 : 1,
   Object.extend({ scaleContent: false, 
    scaleX: false, 
    scaleMode: 'box',
    scaleFrom: 100,
    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
    restoreAfterFinish: true,
    afterSetup: function(effect) {
      effect.element.makePositioned();
      effect.element.down().makePositioned();
      if (window.opera) effect.element.setStyle({top: ''});
      effect.element.makeClipping().show();
    },  
    afterUpdateInternal: function(effect) {
      effect.element.down().setStyle({bottom:
        (effect.dims[0] - effect.element.clientHeight) + 'px' });
    },
    afterFinishInternal: function(effect) {
      effect.element.hide().undoClipping().undoPositioned();
      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom});
    }
   }, arguments[1] || { })
  );
};

// Bug in opera makes the TD containing this element expand for a instance after finish 
Effect.Squish = function(element) {
  return new Effect.Scale(element, window.opera ? 1 : 0, { 
    restoreAfterFinish: true,
    beforeSetup: function(effect) {
      effect.element.makeClipping(); 
    },  
    afterFinishInternal: function(effect) {
      effect.element.hide().undoClipping(); 
    }
  });
};

Effect.Grow = function(element) {
  element = $(element);
  var options = Object.extend({
    direction: 'center',
    moveTransition: Effect.Transitions.sinoidal,
    scaleTransition: Effect.Transitions.sinoidal,
    opacityTransition: Effect.Transitions.full
  }, arguments[1] || { });
  var oldStyle = {
    top: element.style.top,
    left: element.style.left,
    height: element.style.height,
    width: element.style.width,
    opacity: element.getInlineOpacity() };

  var dims = element.getDimensions();    
  var initialMoveX, initialMoveY;
  var moveX, moveY;
  
  switch (options.direction) {
    case 'top-left':
      initialMoveX = initialMoveY = moveX = moveY = 0; 
      break;
    case 'top-right':
      initialMoveX = dims.width;
      initialMoveY = moveY = 0;
      moveX = -dims.width;
      break;
    case 'bottom-left':
      initialMoveX = moveX = 0;
      initialMoveY = dims.height;
      moveY = -dims.height;
      break;
    case 'bottom-right':
      initialMoveX = dims.width;
      initialMoveY = dims.height;
      moveX = -dims.width;
      moveY = -dims.height;
      break;
    case 'center':
      initialMoveX = dims.width / 2;
      initialMoveY = dims.height / 2;
      moveX = -dims.width / 2;
      moveY = -dims.height / 2;
      break;
  }
  
  return new Effect.Move(element, {
    x: initialMoveX,
    y: initialMoveY,
    duration: 0.01, 
    beforeSetup: function(effect) {
      effect.element.hide().makeClipping().makePositioned();
    },
    afterFinishInternal: function(effect) {
      new Effect.Parallel(
        [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
          new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
          new Effect.Scale(effect.element, 100, {
            scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, 
            sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
        ], Object.extend({
             beforeSetup: function(effect) {
               effect.effects[0].element.setStyle({height: '0px'}).show(); 
             },
             afterFinishInternal: function(effect) {
               effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); 
             }
           }, options)
      )
    }
  });
};

Effect.Shrink = function(element) {
  element = $(element);
  var options = Object.extend({
    direction: 'center',
    moveTransition: Effect.Transitions.sinoidal,
    scaleTransition: Effect.Transitions.sinoidal,
    opacityTransition: Effect.Transitions.none
  }, arguments[1] || { });
  var oldStyle = {
    top: element.style.top,
    left: element.style.left,
    height: element.style.height,
    width: element.style.width,
    opacity: element.getInlineOpacity() };

  var dims = element.getDimensions();
  var moveX, moveY;
  
  switch (options.direction) {
    case 'top-left':
      moveX = moveY = 0;
      break;
    case 'top-right':
      moveX = dims.width;
      moveY = 0;
      break;
    case 'bottom-left':
      moveX = 0;
      moveY = dims.height;
      break;
    case 'bottom-right':
      moveX = dims.width;
      moveY = dims.height;
      break;
    case 'center':  
      moveX = dims.width / 2;
      moveY = dims.height / 2;
      break;
  }
  
  return new Effect.Parallel(
    [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
      new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
      new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
    ], Object.extend({            
         beforeStartInternal: function(effect) {
           effect.effects[0].element.makePositioned().makeClipping(); 
         },
         afterFinishInternal: function(effect) {
           effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
       }, options)
  );
};

Effect.Pulsate = function(element) {
  element = $(element);
  var options    = arguments[1] || { };
  var oldOpacity = element.getInlineOpacity();
  var transition = options.transition || Effect.Transitions.sinoidal;
  var reverser   = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) };
  reverser.bind(transition);
  return new Effect.Opacity(element, 
    Object.extend(Object.extend({  duration: 2.0, from: 0,
      afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
    }, options), {transition: reverser}));
};

Effect.Fold = function(element) {
  element = $(element);
  var oldStyle = {
    top: element.style.top,
    left: element.style.left,
    width: element.style.width,
    height: element.style.height };
  element.makeClipping();
  return new Effect.Scale(element, 5, Object.extend({   
    scaleContent: false,
    scaleX: false,
    afterFinishInternal: function(effect) {
    new Effect.Scale(element, 1, { 
      scaleContent: false, 
      scaleY: false,
      afterFinishInternal: function(effect) {
        effect.element.hide().undoClipping().setStyle(oldStyle);
      } });
  }}, arguments[1] || { }));
};

Effect.Morph = Class.create(Effect.Base, {
  initialize: function(element) {
    this.element = $(element);
    if (!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({
      style: { }
    }, arguments[1] || { });
    
    if (!Object.isString(options.style)) this.style = $H(options.style);
    else {
      if (options.style.include(':'))
        this.style = options.style.parseStyle();
      else {
        this.element.addClassName(options.style);
        this.style = $H(this.element.getStyles());
        this.element.removeClassName(options.style);
        var css = this.element.getStyles();
        this.style = this.style.reject(function(style) {
          return style.value == css[style.key];
        });
        options.afterFinishInternal = function(effect) {
          effect.element.addClassName(effect.options.style);
          effect.transforms.each(function(transform) {
            effect.element.style[transform.style] = '';
          });
        }
      }
    }
    this.start(options);
  },
  
  setup: function(){
    function parseColor(color){
      if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
      color = color.parseColor();
      return $R(0,2).map(function(i){
        return parseInt( color.slice(i*2+1,i*2+3), 16 ) 
      });
    }
    this.transforms = this.style.map(function(pair){
      var property = pair[0], value = pair[1], unit = null;

      if (value.parseColor('#zzzzzz') != '#zzzzzz') {
        value = value.parseColor();
        unit  = 'color';
      } else if (property == 'opacity') {
        value = parseFloat(value);
        if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
          this.element.setStyle({zoom: 1});
      } else if (Element.CSS_LENGTH.test(value)) {
          var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
          value = parseFloat(components[1]);
          unit = (components.length == 3) ? components[2] : null;
      }

      var originalValue = this.element.getStyle(property);
      return { 
        style: property.camelize(), 
        originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0), 
        targetValue: unit=='color' ? parseColor(value) : value,
        unit: unit
      };
    }.bind(this)).reject(function(transform){
      return (
        (transform.originalValue == transform.targetValue) ||
        (
          transform.unit != 'color' &&
          (isNaN(transform.originalValue) || isNaN(transform.targetValue))
        )
      )
    });
  },
  update: function(position) {
    var style = { }, transform, i = this.transforms.length;
    while(i--)
      style[(transform = this.transforms[i]).style] = 
        transform.unit=='color' ? '#'+
          (Math.round(transform.originalValue[0]+
            (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() +
          (Math.round(transform.originalValue[1]+
            (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() +
          (Math.round(transform.originalValue[2]+
            (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() :
        (transform.originalValue +
          (transform.targetValue - transform.originalValue) * position).toFixed(3) + 
            (transform.unit === null ? '' : transform.unit);
    this.element.setStyle(style, true);
  }
});

Effect.Transform = Class.create({
  initialize: function(tracks){
    this.tracks  = [];
    this.options = arguments[1] || { };
    this.addTracks(tracks);
  },
  addTracks: function(tracks){
    tracks.each(function(track){
      track = $H(track);
      var data = track.values().first();
      this.tracks.push($H({
        ids:     track.keys().first(),
        effect:  Effect.Morph,
        options: { style: data }
      }));
    }.bind(this));
    return this;
  },
  play: function(){
    return new Effect.Parallel(
      this.tracks.map(function(track){
        var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options');
        var elements = [$(ids) || $$(ids)].flatten();
        return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) });
      }).flatten(),
      this.options
    );
  }
});

Element.CSS_PROPERTIES = $w(
  'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' + 
  'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
  'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
  'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
  'fontSize fontWeight height left letterSpacing lineHeight ' +
  'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+
  'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
  'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
  'right textIndent top width wordSpacing zIndex');
  
Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;

String.__parseStyleElement = document.createElement('div');
String.prototype.parseStyle = function(){
  var style, styleRules = $H();
  if (Prototype.Browser.WebKit)
    style = new Element('div',{style:this}).style;
  else {
    String.__parseStyleElement.innerHTML = '<div style="' + this + '"></div>';
    style = String.__parseStyleElement.childNodes[0].style;
  }
  
  Element.CSS_PROPERTIES.each(function(property){
    if (style[property]) styleRules.set(property, style[property]); 
  });
  
  if (Prototype.Browser.IE && this.include('opacity'))
    styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]);

  return styleRules;
};

if (document.defaultView && document.defaultView.getComputedStyle) {
  Element.getStyles = function(element) {
    var css = document.defaultView.getComputedStyle($(element), null);
    return Element.CSS_PROPERTIES.inject({ }, function(styles, property) {
      styles[property] = css[property];
      return styles;
    });
  };
} else {
  Element.getStyles = function(element) {
    element = $(element);
    var css = element.currentStyle, styles;
    styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) {
      results[property] = css[property];
      return results;
    });
    if (!styles.opacity) styles.opacity = element.getOpacity();
    return styles;
  };
};

Effect.Methods = {
  morph: function(element, style) {
    element = $(element);
    new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { }));
    return element;
  },
  visualEffect: function(element, effect, options) {
    element = $(element)
    var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1);
    new Effect[klass](element, options);
    return element;
  },
  highlight: function(element, options) {
    element = $(element);
    new Effect.Highlight(element, options);
    return element;
  }
};

$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+
  'pulsate shake puff squish switchOff dropOut').each(
  function(effect) { 
    Effect.Methods[effect] = function(element, options){
      element = $(element);
      Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options);
      return element;
    }
  }
);

$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each( 
  function(f) { Effect.Methods[f] = Element[f]; }
);

Element.addMethods(Effect.Methods);


/* scroller */
function pc3Scroller( scrollerId, scrollerData, scrollerBars, scrollerArrows ){

 	Object.extend(Event, {
		wheel:function (event){
			if (!event) event = window.event;
			var delta = 0;
			var direction = 'vertical';
			if ( event.wheelDelta ) {
				delta = -(event.wheelDelta/10);
				if ( delta < 1 && delta >= 0 ){
					delta = 1;
				} else if ( delta < 0 && delta >= -1 ){
					delta = -1;
				}
				if ( event.wheelDeltaY == 0 ) direction = 'horizontal';
			} else if (event.detail) {
				delta = event.detail;
				if ( event.VERTICAL_AXIS != event.axis )  direction = 'horizontal';
			}
			
			return {
				direction:direction,
				delta:delta.round()
			}
		}
	});
	
	var self = this;
	this.id = scrollerId;
	this.frame = $( this.id );
	this.currentState = 'mouseOut';
	
	this.bars = $H({});
	this.arrows = $H({});

	this.handleElements = new Array();
	
	this.init = function(){
		$H(scrollerData).each(function(attribute){ self[attribute.key] = attribute.value; });
		this.content = new pc3ScrollerContent(this, this.frame.childElements(), scrollerData);		
		scrollerBars.each(function(data){
			var element = $(self.id+"bar"+data.direction);
			if ( !element ) return;
			self.bars.set(data.direction, new pc3ScrollerBar(self, element, data));
		});

		scrollerArrows.each(function(data){
			var element = $(self.id+"arrow"+data.direction);
			if ( !element ) return;
			self.arrows.set(data.direction, new pc3ScrollerArrow(self, element, data, scrollerData.maxspeed));
		});

		if ( this.activateMouseWheel ){
			var eventType = 'mousewheel';
			if ( Prototype.Browser.Gecko ) eventType = 'DOMMouseScroll';
			Event.observe(this.frame, eventType, this.scrollWheel.bindAsEventListener(this));
		}
		
		this.update();
	}

	
	this.scrollTo = function(position, elementId, alignment){ this.content.scrollTo(position, elementId, alignment); }

	this.scrollWheel = function(event){
		var wheel = Event.wheel(event);
		this.scrollByPixel((wheel.direction=='horizontal'?wheel.delta:null),(wheel.direction!='horizontal'?wheel.delta:null));
		Event.stop(event);
	}
	
	this.hideScrollBars = function(){
		this.bars.each(function(bar){bar.value.hide();});
	}
	
	
	this.initDisplayHandler = function(element){ this.handleElements.push(element); }


	this.showingArrow = function(direction){ if ( this.bars.get(direction) ) this.bars.get(direction).showingArrow(); }

	this.hidingArrow = function(direction){ if ( this.bars.get(direction) ) this.bars.get(direction).hidingArrow(); }
	

	this.scroll = function(x, y, source){
		switch( source ){
			case 'bar':
				this.content.scroll(x, y, (Prototype.Browser.IE?'pixel':'')); //performance for IE
				break;
			case 'content':
				this.bars.each(function(bar){ bar.value.scroll(x, y, source); });
				break;
		}				
	}

	this.scrollByUnit = function(direction){ this.content.scrollByUnit(direction); }

	this.scrollByPixel = function(x, y){ this.content.scrollByPixel(x, y); }

	this.scrollable = function( direction ){
		if ( direction == 'horizontal' && this.content.width && (this.content.width > this.width) ) return true;
		if ( direction == 'vertical' && this.content.height && (this.content.height > this.height) ) return true;
		return false;
	}

	this.handleMouseMove = function(event){
		this.bars.each(function(bar){bar.value.handleMouseMove(event);});
		if ( !this.handleElements.size() ) return;
		if ( this.currentState == 'mouseOut' && pc3Widget.mouseIsInside(event, this.frame) ){
			this.currentState = 'mouseIn';
			this.handleElements.each(function(element){ element.show(); });
		} else if ( this.currentState == 'mouseIn' && !pc3Widget.mouseIsInside(event, this.frame) ){
			var scrolling = false;
			this.bars.each(function(bar){ if ( bar.value.scrolling ) scrolling = true;});
			if ( scrolling ) return;
			this.currentState = 'mouseOut';
			this.handleElements.each(function(element){ element.hide(); });
		}
	}

	this.handleMouseUp = function(event){
		this.bars.each(function(bar){bar.value.handleMouseUp(event);});
		this.arrows.each(function(arrow){arrow.value.handleMouseUp(event);});
	}
	
	this.update = function(){
		this.width = this.frame.getWidth();
		this.height = this.frame.getHeight();
		this.content.update();
		this.bars.each(function(bar){bar.value.update();});
		this.arrows.each(function(arrow){arrow.value.update();});
	}
	
	this.init();
}


/**
*  Content
**/
function pc3ScrollerContent( scroller, childs, data ){
	var self = this;
	this.scroller = scroller;
	this.type = 'content';
	this.dragging = false;
	this.startDragPosition = {x:0,y:0};
	this.scrollElements = new Array();
	
	this.scrollBy = (data.scrollBy == undefined?'pixel':data.scrollBy);
	this.animateScroll = data.animateScroll;
	this.animateDuration = (data.animateDuration?parseFloat(data.animateDuration*1000):300);
	this.tween = '';
	this.cumulativePixels = {x:0,y:0};
	this.lastPosition = {x:0,y:0};
	
	this.init = function(){
		this.content = new Element('div').setStyle({position:'absolute'});
		this.content.addClassName('clearfix');
		childs.each(function(item){  self.content.appendChild(item); });
		this.scroller.frame.appendChild(this.content);

		// Get all Elements with Classname
		if( data.scrollByElementClassName && this.scrollBy == 'elements' ){
			this.content.select('.'+data.scrollByElementClassName).each(function(item){  self.scrollElements.push(item); });
		}
		
		if ( this.paning ) this.initPanning();
	}


	this.scrollByElements = function(){ 
		if ( this.scrollBy == 'elements' && this.scrollElements.size() > 0 ) return true;
		return false;
	}

	this.scrollByPixel = function(x, y){
		var before = {
			left:this.position.left,
			top:this.position.top
		};
		
		var newX = null;
		var newY = null;
	
		if ( x != null ){
			this.cumulativePixels.x = this.cumulativePixels.x + x;
			newX = -this.position.left + this.cumulativePixels.x;
		}
		
		if ( y != null ){
			this.cumulativePixels.y = this.cumulativePixels.y + y;
			newY = -this.position.top + this.cumulativePixels.y;
		}
		
		this.scroll(newX, newY, (Prototype.Browser.IE?'pixel':'')); //performance for IE
		this.scroller.scroll(-this.position.left, -this.position.top, 'content');
		

		if ( x != null && this.position.left != before.left || (x < 0 && this.position.left == 0) || (x > 0 && -this.position.left == this.maxScroll.x) ) this.cumulativePixels.x = 0;
		if ( y != null && this.position.top != before.top || (y < 0 && this.position.top == 0) || (y > 0 && -this.position.top == this.maxScroll.y) ) this.cumulativePixels.y = 0;
	}
	
	
	this.scroll = function(x, y, scrollBy){
		if ( !scrollBy ) scrollBy = this.scrollBy;
		if ( x != null ){
			if ( !this.scrollable.x ) x = 0;
			if ( x < 0 ) x = 0;

			if ( x > 0 && scrollBy != 'pixel' ){
				var newX = 0;
				if ( scrollBy == 'page' ){
					newX = ((x / this.scroller.width).round()) * this.scroller.width;
				} else if ( this.scrollByElements() ){
					for (var i=0,length=this.scrollElements.size(); i<length; ++i){
						newX = this.scrollElements[i].positionedOffset().left;
						if ( parseInt(newX + (this.scrollElements[i].getWidth()/2)) >= x ) break;
					}
				}
				if ( newX == 0 && x > newX && x > parseInt((this.width - this.scroller.width)/2) ) newX = this.maxScroll.x;
				x = newX;
			}
			if ( this.scrollable.x && x > this.maxScroll.x ) x = this.maxScroll.x;
			this.position.left = -x;
		}

		if ( y != null ){
			if ( !this.scrollable.y ) y = 0;
			if ( y < 0 ) y = 0;
			
			if ( y > 0 && scrollBy != 'pixel' ){
				var newY = 0;
				if ( scrollBy == 'page' ){
					newY = ((y / this.scroller.height).round()) * this.scroller.height;
				} else if ( this.scrollByElements() ){
					for (var i=0,length=this.scrollElements.size(); i<length; ++i){
						newY = this.scrollElements[i].positionedOffset().top;
						if ( parseInt(newY + (this.scrollElements[i].getHeight()/2)) >= y ) break;
					}
				}
				if ( newY == 0 && y > newY && y > parseInt((this.height - this.scroller.height)/2) ) newY = this.maxScroll.y;
				y = newY;
			}
			if ( this.scrollable.y && y > this.maxScroll.y ) y = this.maxScroll.y;
			this.position.top = -y;
		}
		
		this.content.setStyle({left:this.position.left+'px', top:this.position.top+'px'});
	}
	
	this.scrollTo = function( position, elementId, alignment ){
		//console.log( this.position );
		var elementPosition = {
			left:0,
			left:0
		}
		var offsetX = 0;
		var offsetY = 0;
		
		if ( elementId ){
			var element = this.content.select('#'+elementId).first();
			if ( !element ) return;
			elementPosition = element.positionedOffset();
			var width = element.getWidth();
			var height = element.getHeight();
			alignment = (alignment==undefined?'topleft':alignment);
			
			switch( alignment ){
				case "topcenter":
					offsetX = ((this.scroller.width - width)/2).round();
					break;
				case "topright":
					offsetX = this.scroller.width - width;
					Break
				case "centerleft":
					offsetY = ((this.scroller.height - height)/2).round();
					break;
				case "centercenter":
					offsetX = ((this.scroller.width - width)/2).round();
					offsetY = ((this.scroller.height - height)/2).round();
					break;
				case "centerright":
					offsetX = this.scroller.width - width;
					offsetY = ((this.scroller.height - height)/2).round();
					break;
				case "bottomleft":
					offsetY = this.scroller.height - height;
					break;
				case "bottomcenter":	
					offsetX = ((this.scroller.width - width)/2).round();
					offsetY = this.scroller.height - height;
					break;
				case "bottomright":
					offsetX = this.scroller.width - width;
					offsetY = this.scroller.height - height;
					break;
			}
		}
		
		this.endPosition = {
			x:(elementPosition.left-offsetX+position.x),
			y:(elementPosition.top-offsetY+position.y)
		}
		
		if ( this.animateScroll ){
			if ( this.tween ) delete this.tween;
			this.startPosition = {
				x:-this.position.left,
				y:-this.position.top
			};
			this.tween = new pc3Tween('scroll', 1, 100, this.animateDuration, this.animateScroll, this.onTween.bind(self), this.onTweenComplete.bind(self));
		} else {
			this.scroll(this.endPosition.x, this.endPosition.y, '');
			this.scroller.scroll(-this.position.left, -this.position.top, 'content');
		}
	}
	
	this.scrollByUnit = function(direction){
		if(console) console.log(direction);
	}

	this.onTween = function( effect ){
		var left = this.startPosition.x - (((this.startPosition.x - this.endPosition.x) * effect.value) / 100).round();
		var top = this.startPosition.y - (((this.startPosition.y - this.endPosition.y) * effect.value) / 100).round();
		this.scroll(left, top, 'pixel');
		this.scroller.scroll(-this.position.left, -this.position.top, 'content');
	}
	
	this.onTweenComplete = function( effect ){ this.tween = ''; }
		
	this.update = function(){
		this.width = this.content.getWidth();
		this.height = this.content.getHeight();
		this.position = this.content.positionedOffset();
		this.scrollable = {x:(this.width<=this.scroller.width?false:true),y:(this.height<=this.scroller.height?false:true)};
		this.maxScroll = {x:(this.scrollable.x?this.width-this.scroller.width:0),y:(this.scrollable.y?this.height-this.scroller.height:0)};
	}
	
	this.init();
}



/**
*  SCROLLBARS
**/
function pc3ScrollerBar( scroller, barElement, data ){
	var self = this;
	this.scroller = scroller;
	this.bar = barElement;
	this.direction = data.direction;
	this.display = (data.display?data.display:'always');
	this.displayOnlyWhenNeeded = (data.displayOnlyWhenNeeded?true:false);
	this.scrolling = false;
	this.minHandleSize = 10;
	this.opacity = pc3Widget.getOpacity(this.bar) * 100;
	this.tweens = $H({});
	
	this.init = function(){
		this.handle = new Element('div', { 'class': 'handle'+this.direction }).setStyle({position:'absolute', left:'0px', top:'0px', pointer:'pointer'});
		this.bar.appendChild( this.handle );
		this.bar.setStyle({pointer:'pointer'});
		if( this.bar.up(0) == this.scroller.content.content ) this.scroller.frame.appendChild( this.bar );
		this.handleOpacity = pc3Widget.getOpacity(this.handle) * 100;
		
		Event.observe(this.handle, 'mousedown', this.startScrolling.bindAsEventListener(this));		
		Event.observe(this.bar, 'click', this.scrollPage.bindAsEventListener(this));		

		if ( this.display != "always" ){
			this.bar.setStyle({opacity: 0});
			this.handle.setStyle({opacity: 0});
		}

		switch( this.display ){
			case "onlyWhenOnBar":
				Event.observe( this.bar, 'mouseover', this.show.bindAsEventListener(this));
				Event.observe( this.bar, 'mouseout', this.hide.bindAsEventListener(this));
				break;
			case "onlyWhenInArea":
				this.scroller.initDisplayHandler(this);
				break;
		}

	}

	
	this.startScrolling = function(event){
		event.stop();
		this.scrolling = true;
		this.startMousePosition = {
			x:pc3Widget.mousePosition.x,
			y:pc3Widget.mousePosition.y
		}
		
		this.startHandlePosition = {
			left:this.position.left,
			top:this.position.top
		}
		
	}

	this.handleMouseMove = function(event){
		if ( !this.scrolling ) return;
		event.stop();
		if ( this.direction == 'horizontal' ){
			this.scroll((this.startHandlePosition.left+(pc3Widget.mousePosition.x - this.startMousePosition.x)), null, false);
			this.scroller.scroll((this.position.left*this.factor).round(), null, 'bar');		
		} else {
			this.scroll(null, (this.startHandlePosition.top + (pc3Widget.mousePosition.y - this.startMousePosition.y)), false);
			this.scroller.scroll(null, (this.position.top*this.factor).round(), 'bar');		
		}
	}

	this.handleMouseUp = function(event){
		var element = Event.element(event);
		if ( this.scrolling && this.display != 'always' && (element != this.bar && element != this.handle) ){
			this.scrolling = false;
			this.hide('');
		}
		this.scrolling = false;
	}

	this.scrollPage = function(event){
		if ( this.direction == 'horizontal' ){
			if ( event.pointerX() < this.handle.cumulativeOffset().left ) this.scroll((this.position.left - this.handle.getWidth()), null, false);
			if ( event.pointerX() > (this.handle.cumulativeOffset().left + this.handle.getWidth()) ) this.scroll((this.position.left + this.handle.getWidth()), null, false);
			this.scroller.scroll((this.position.left*this.factor).round(), null, 'bar');		
		} else {
			if ( event.pointerY() < this.handle.cumulativeOffset().top ) this.scroll(null, (this.position.top - this.handle.getHeight()), false);
			if ( event.pointerY() > (this.handle.cumulativeOffset().top + this.handle.getHeight()) ) this.scroll(null, (this.position.top + this.handle.getHeight()), false);
			this.scroller.scroll(null, (this.position.top*this.factor).round(), 'bar');		
		}
	}

	this.scroll = function(x, y, content){
		if ( content ){
			if ( x != null ) x = x/this.factor;
			if ( y != null ) y = y/this.factor;
		}
		if ( this.direction == 'horizontal' ){
			if ( x < 0 ) x = 0;
			if ( x > this.width - this.handle.getWidth() ) x = this.width - this.handle.getWidth();
			this.position.left = x;
			this.handle.setStyle({left:this.position.left+'px'});
		} else {
			if ( y < 0 ) y = 0;
			if ( y > this.height - this.handle.getHeight() ) y = this.height - this.handle.getHeight();
			this.position.top = y;

			this.handle.setStyle({top:this.position.top+'px'});
		}
	}


	this.show = function(event){
		if ( event ) event.stop();
		if ( this.tweens.size() ) this.tweens.each(function(tween){ self.tweens.unset(tween.key) });
		this.tweens.set('bar', new pc3Tween('opacity', (pc3Widget.getOpacity(self.bar) * 100), self.opacity, 200, 'EaseInQuad', this.fadeInOutBar.bind(self), function(){  }));
		this.tweens.set('handle', new pc3Tween('opacity', (pc3Widget.getOpacity(self.handle) * 100), self.handleOpacity, 200, 'EaseInQuad', this.fadeInOutHandle.bind(self), function(){  }));
	}
	
	this.hide = function(event){
		if ( event ){
			event.stop();
			if ( pc3Widget.mouseIsInside(event, this.bar) ) return;
			if ( this.scrolling ) return;
		}
		if ( this.tweens.size() ) this.tweens.each(function(tween){ self.tweens.unset(tween.key) });
		this.tweens.set('bar', new pc3Tween('opacity', (pc3Widget.getOpacity(self.bar) * 100), 0, 200, 'EaseInQuad', this.fadeInOutBar.bind(self), function(){  }));
		this.tweens.set('handle', new pc3Tween('opacity', (pc3Widget.getOpacity(self.handle) * 100), 0, 200, 'EaseInQuad', this.fadeInOutHandle.bind(self), function(){  }));
	}
	
	this.fadeInOutBar = function(tween){
		this.bar.setStyle({ opacity : (tween.value/100) });
	}
	this.fadeInOutHandle = function(tween){ this.handle.setStyle({ opacity : (tween.value/100) }); }

	this.showingArrow = function(){ if ( this.display == 'onlyWhenOnArrows' ) this.show(''); }

	this.hidingArrow = function(){ if ( this.display == 'onlyWhenOnArrows' ) this.hide(''); }

	
	this.update = function(){
		this.width = this.bar.getWidth();
		this.height = this.bar.getHeight();
		this.position = this.handle.positionedOffset();
		var lenght = 0;
		
		if ( this.direction == 'horizontal' && this.scroller.scrollable(this.direction) ) lenght = parseInt((this.bar.getWidth() * this.scroller.width) / this.scroller.content.width);
		if ( this.direction == 'vertical' && this.scroller.scrollable(this.direction) ) lenght = parseInt((this.bar.getHeight() * this.scroller.height) / this.scroller.content.height);
		if ( lenght > 0 ){
			if ( lenght < this.minHandleSize ) lenght = this.minHandleSize;
			if ( this.direction == 'horizontal' ) this.handle.setStyle({width:lenght+'px',height:this.bar.getHeight()+'px'});
			else this.handle.setStyle({width:this.bar.getWidth()+'px', height:lenght+'px'});
			this.handle.show();
			this.bar.show();
			if ( this.direction == 'horizontal' ) this.factor = (this.scroller.content.width - this.scroller.width) / (this.bar.getWidth()-lenght);
			else this.factor = (this.scroller.content.height - this.scroller.height) / (this.bar.getHeight()-lenght);
		} else {
			this.factor = 1;
			this.handle.hide();
			if ( this.displayOnlyWhenNeeded ) this.bar.hide();
		}
	}
	
	this.init();
}




/**
*  ARROWS
**/

function pc3ScrollerArrow( scroller, arrowElement, data, maxSpeed ){
	var self = this;
	this.scroller = scroller;
	this.arrow = arrowElement;
	this.direction = data.direction;
	this.scrollDirection = (this.direction == 'left' || this.direction == 'right' ? 'horizontal' : 'vertical' );
	this.display = (data.display?data.display:'always');
	this.displayOnlyWhenNeeded = (data.displayOnlyWhenNeeded?true:false);
	this.scroll = (data.scroll?data.scroll:'continuously');
	this.trigger = (data.trigger?data.trigger:'mousedown');
	this.maxSpeed = parseInt((maxSpeed?maxSpeed:40));
	this.scrolling = false;
	this.nextScroll = '';
	this.pastTime = 0;
	this.opacity = pc3Widget.getOpacity(this.arrow) * 100;
	this.tween = '';
		
	this.init = function(){
		if ( this.scroll == 'continuously' ){
			Event.observe(this.arrow, this.trigger, this.startScrolling.bindAsEventListener(this));
			if ( this.trigger == 'mousedown' ){
				Event.observe(this.arrow, 'mouseout', this.haltScrolling.bindAsEventListener(this));
				Event.observe(this.arrow, 'mouseover', this.continueScrolling.bindAsEventListener(this));
			}
			Event.observe(this.arrow, (this.trigger=='mousedown'?'mouseup':'mouseout'), this.stopScrolling.bindAsEventListener(this));
		} else {
			Event.observe(this.arrow, 'click', this.scrollByUnit.bindAsEventListener(this));
		}
		
		if( this.display != "always" ) this.arrow.setStyle({opacity: 0});
		
		switch( this.display ){
			case "onlyWhenOnArrow":
				Event.observe( this.arrow, 'mouseover', this.show.bindAsEventListener(this));
				Event.observe( this.arrow, 'mouseout', this.hide.bindAsEventListener(this));
				break;
			case "onlyWhenInArea":
				this.scroller.initDisplayHandler(this);
				break;
		}
	}
	
	this.startScrolling = function(event){
		if ( event ) event.stop();
		this.pastTime = 0;
		this.scrolling = true;
		this.nextScroll = this.scrollByPixel.bind(this).delay(0);
	}
	
	this.haltScrolling = function(event){
		if ( event ) event.stop();
		this.scrolling = false;
	}
	
	this.continueScrolling = function(event){
		if ( event ) event.stop();
		this.scrolling = true;
	}
	
	this.stopScrolling = function(event){
		if ( event ) event.stop();
		if ( this.nextScroll ) window.clearTimeout(this.nextScroll);
		this.scrolling = false;
	}

	this.handleMouseUp = function(event){ this.stopScrolling(''); }

	this.scrollByPixel = function(){
		if ( this.scrolling ){
			var pixels = 5 + this.pastTime.round();
			if ( pixels > this.maxSpeed ) pixels = this.maxSpeed;
			var x = (this.direction=='left'?-pixels:(this.direction=='right'?pixels:null))
			var y = (this.direction=='up'?-pixels:(this.direction=='down'?pixels:null))
			this.scroller.scrollByPixel(x,y);
			this.pastTime = this.pastTime + 0.16;
		}
		this.nextScroll = this.scrollByPixel.bind(this).delay(0.04);
	}
	
	this.scrollByUnit = function(){ this.scroller.scrollByUnit(this.direction); }

	this.show = function(event){
		if ( event ) event.stop();
		if ( this.tween ) this.tween = '';
		this.tween = new pc3Tween('opacity', (pc3Widget.getOpacity(self.arrow) * 100), self.opacity, 200, 'EaseInQuad', this.fadeInOut.bind(self), function(){  });
		if ( event ) this.scroller.showingArrow(this.scrollDirection);
	}
	
	this.hide = function(event){
		if ( event ) event.stop();
		if ( this.tween ) this.tween = '';
		this.tween = new pc3Tween('opacity', (pc3Widget.getOpacity(self.arrow) * 100), 0, 200, 'EaseInQuad', this.fadeInOut.bind(self), function(){  });
		if ( event ) this.scroller.hidingArrow(this.scrollDirection);
	}
	
	this.fadeInOut = function(tween){ this.arrow.setStyle({ opacity : (tween.value/100) }); }
	
	this.update = function(){
		var display = false;
		if ( this.scroller.scrollable(this.scrollDirection) ) display = true;
		if ( !display && this.displayOnlyWhenNeeded ) this.arrow.hide();
		else this.arrow.show();
	}

	this.init();
}

/* form */
function pc3SubmitForm( form,addScrollPosition ){
	if ( addScrollPosition ) pc3AddScrollPositionOnForm(form);
	
	if ( form.getAttribute('onsubmit') ){
		if ( form.onsubmit() ) {
			form.submit();
		}
	} else {
		form.submit();
	}
}

function addScrollPositionOnForm(formId) {
	if ( !formId ) formId = 'layout';
	var form = document.getElementById(formId);
	
	pc3AddScrollPositionOnForm(form); // FIX #1095 : was a recursive call: addScrollPositionOnForm(formId)
}

function pc3AddScrollPositionOnForm(form) {
	if ( !form ) return;
	
	var formAction = form.action;
	if ( formAction == '' ) { return; }
	
	var regSearch  = "(?:[^?]*)(\\?)([^?]*)";
	var oRegExp    = new RegExp(regSearch);
	var regResult  = oRegExp.exec(formAction);
	var windowScrollPositions = getWindowScrollPositions();
	
	formAction +=
		( regResult && regResult[1] != '' ? '&' : '?')
		+ 'pc3Scroll='
		+ windowScrollPositions['left']
		+ 'x'
		+ windowScrollPositions['top']
	;
	
	form.action = formAction;
}

function pc3SubmitLink(formId,fieldName,value,addScrollPosition){
	var form = document.getElementById(formId);
	if ( !form ) return true;
	
	if ( fieldName ) {
		var field = document.getElementById(fieldName);
		if ( !field ) {
			field = document.createElement('input');
			field.type = 'hidden';
			field.name = fieldName;
			field.id = fieldName;
			
			form.appendChild(field);
		}
		
		field.value = value;
	}
	
	return pc3SubmitForm(form,addScrollPosition);
}

function pc3SetPlaceholders(){
	var set = function(tagName,type){
		inputElements = document.getElementsByTagName(tagName);
		
		for ( var i=0; i<inputElements.length; i++ ) {
			inputElement = inputElements[i];
			inputElement.hasPlaceHolder = (!type || inputElement.type == type) && inputElement.title != '';
			
			if ( !inputElement.hasPlaceHolder ) continue;
			
			if ( inputElement.captureEvents ) {
				inputElement.captureEvents(Event.FOCUS);
				inputElement.captureEvents(Event.BLUR);
			}
			
			inputElement.onfocus = function () { if ( this.value == this.title ) this.value = '';}
			inputElement.onblur = function () { if ( this.value == '' ) this.value = this.title;}
			
			inputElement.onblur();
		}
	}
	
	set('input','text');
	set('textarea');
	
	return true;
}



function pc3ClearPlaceholders(form){
	var clear = function(tagName,type){
		inputElements = document.getElementsByTagName(tagName);
		
		for( var i=0; i<inputElements.length; i++ ) {
			if ( inputElements[i].hasPlaceHolder ) inputElements[i].onfocus();
		}
	}
	
	clear('input','text');
	clear('textarea');
	
	return true;
}