/* 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]);
}

/**
 * set the scrollposition of the window
 * 
 * @author acn
 * @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;
	}
}

/**
 * get the scrollposition of the window
 * 
 * @author acn
 * @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 filter is defined, all URL parameters are returned.</li><li>If a filter is defined (array with parameter keys), only these parameters are returned.</li></ul>
 * Note: parameter names and parameter values are case sensitive.<br>
 * 
 * @author acn
 * @param {Array} filter : If the paramter filter isn't defined, all URL paramters are collected and returned. If you are looking for specific parameters, you can define the filter with an array.
 * @return {Object} urlParams : The parameter name is used as the name of the object member variable, the according value is the parameter value. Note that if no parameter is found, an empty object is returned. 
 */
function getUrlParam(filter) {
	var urlParamNames = null;
	var href = window.location.href;
	
	if (!href) return;
	var urlParamNames = new Array();
	var regExp = /[\?&]([^=]+)=/g;
	
	while ((results = regExp.exec(href)) != null) urlParamNames.push(results[1]);
	
	if (!urlParamNames || urlParamNames.length < 1) return;
	
	var urlParams = new Object();
	var uPs = new Object();
	var name, regExpS, regExp, result;
	
	for (var i = 0; i < urlParamNames.length; i++) {
		name = urlParamNames[i].replace(/[\[]/, '\\\[').replace(/[\]]/, '\\\]'); // escape square brackets for regExpS (PHP-style)
		
		regExpS = '[\\?&]'+ name +'=([^&#]*)';
		regExp = new RegExp(regExpS);
		result = regExp.exec(href);
		
		name = name.replace(/[\\]/g, ''); // re-escape square brackets
		uPs[name] = result[1];
	}
	
	var behaviour = arguments.length; // 0 : no filter; all url params asked, 1 : filter; array with asked param names
	switch (behaviour) {
		case 1 :
			for (var idx in filter) {
				key = filter[idx];
				if (uPs[key]) urlParams[key] = uPs[key];
			}
			break;

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

/**
 * @author cri
 */
function getCookieParam(paramName) {
	var params = document.cookie;
	if (params == '') return;
	
	var cookieParams = new Object;
	var cPs = new Object;
	
	//-- get all url params (for internal use)
	cookies = params.split('; '); // array with 'key=value'
	for (var i = 0; i < cookies.length; ++i) {
		var keyAndValue = cookies[i];
		keyAndValue = keyAndValue.split('='); // array with 0 = 'key' and 1 = 'value'
		
		var key = null;
		var value = null;
		
		key = keyAndValue[0];
		if (keyAndValue[1]) value = keyAndValue[1];
		cPs[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 (cPs[key]) cookieParams[key] = cPs[key];
			}
			break;
		
		case 0 :
		default:
			cookieParams = cPs;
	}
	//--
	
	return cookieParams;
}

/**
 * Set GET parameters on a link object (link.href).<br>
 * Keeps (or overwrites) existing parameters and adds nonexisting ones.<br>
 * 
 * @author acn
 * @param {Object} HTML link object (DOM)
 * @param {Object} params containing member variables are used as key, values as value
 * @return void
 */
function setUrlParam(link, params) {
	if (!link.href) return;
	if (!params || typeof params == 'undefined' || typeof params != 'object' || isEmpty(params)) return;
	
	var urlParams = new Object();
	var doEscape = false;
	
	var before = '';
	var href = new String(link.href); // get url from link object, may contain "garbage code" like "javascript void; window.open(..." and my be 'pc3-like' encoded
	var after = '';
	
	var regExpHref = /(.*window\.open\((?:%22))([^,]*)((?:%22)(?:,|).*)/i; // get url from window.open() command
	if ((results = href.match(regExpHref))) {
		doEscape = true;
		
		before = results[1];
		href = results[2];
		after = results[3];
	}
	
	decHref = unescape(href).replace(/\\/g, ''); // decode 'pc3-like' encoding
	
	var results = '';
	var regExpGets = /[\?&]([^=]+)=([^&#]*)/gi; // get all existing get params
	while ((results = regExpGets.exec(decHref)) != null) urlParams[results[1]] = results[2];
	
	for (var key in params) urlParams[key] = params[key]; // overwrite existing, add new ones
	
	var regExpUrl = /([^?]+)/i; // get url without get params
	var results = decHref.match(regExpUrl);
	var url = results[1];
	
	var gets = '?'; // collect all get params
	for (var key in urlParams) {
		if (doEscape) gets += key +'='+ urlParams[key] +'&';
		else gets += escape(key) +'='+ escape(urlParams[key]) +'&';
	}
	gets = gets.substring(0, gets.length - 1); // trim last &
	
	if (doEscape) { // encode 'pc3-like'
		url = escape(url);
		url = url.replace(/\//g, '%5C%2F');
		
		gets = escape(gets); // nothing escaped until now
	}
	
	url = before + url + gets + after; // build new url
	
	link.href = url;
}


/**
 * Check if object is empty (no member variables).
 * 
 * @author acn
 * @param {Object} object
 * @return boolean
 */
function isEmpty(object) {
	for (var prop in object) if (object.hasOwnProperty(prop)) return false;
	
	return true;
}

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
		// 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;
    }

};

/**
 * catch console.log calls on browsers without console
 * 
 * @author acn
 */ 
if (!window.console) {
	var a = false;
	var uP = getUrlParam(new Array('pc3JSDebug'));
	if (uP && uP.pc3JSDebug == '1') a = true;
	
	window.console = {
		log: function(m){ if(a) alert('console.log\n'+ m); },
		debug: function(m){ if(a) alert('console.debug\n'+ m); },
		info: function(m){ if(a) alert('console.info\n'+ m); },
		warn: function(m){ if(a) alert('console.warn\n'+ m); },
		error: function(m){ if(a) alert('console.error\n'+ m); }
	};
}

/**
 * @author acn
 */
if (!pc3) var pc3 = function(){};

pc3.trackLink = function(tracking) {
	if (!pageTracker) return;
	if (!pageTracker._trackEvent) return;
	
	/*
	 * google
	 * 
	 * @see http://code.google.com/intl/de-DE/apis/analytics/docs/tracking/gaTrackingOverview.html
	 * @see http://code.google.com/intl/de-DE/apis/analytics/docs/tracking/eventTrackerGuide.html
	 */
	pageTracker._trackEvent(tracking.category, tracking.action, tracking.label, tracking.value);
};

/**
 * @author acn
 */
function replaceRssWithXml() {
	if (!/Safari/.test(navigator.userAgent) && !/MSIE (\d+\.\d+);/.test(navigator.userAgent)) return; // ms ie and safari only
	
	var anchors = document.links;
	var regexp = /\.rss(?!\.)/i; // '.rss' allowed, '.rss.' disallowed
	
	for (var i = 0; i < anchors.length; i++) { // loop over each entry
		var href = anchors[i].href;
		if (regexp.test(href)) { // modify only if attribute 'href' do match regexp
			href = href.replace(regexp, '.xml');
			anchors[i].href = href;
		}
	}
}

/* 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.setCloseFunction = 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);
}

/* 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;
}