


/*jslint evil: true, forin: true*/
var shrubbery = {};
if(!window.console){
	window.console = {log: function(){}};
}
(function(){
	var S = shrubbery;
	
	S.lambda = {
		empty: function(){},
		identity: function(x){ return x;},
		not: function(x){ return !x;},
		constant: function(c){ return function(){ return c;};}
	};
	
	S.overwrite = function(dest, o){
		if(!dest){
			dest = {};
		}
		for(var k in o){
			dest[k] = o[k];
		}
		return dest;
	};
	S.mixin = function(dest, o){
		if(!dest){
			dest = {};
		}
		if(!o){
			return dest;
		}
		for(var k in o){
			if(!(k in dest)){
				dest[k] = o[k];
			}
		}
		return dest;
	};
	
	function typeofPredicate(type){
		return function(x){
			return typeof(x) === type;
		};
	}
	
	S.isFunction = typeofPredicate('function');	
	S.isString = typeofPredicate('string');	
	S.isUndefined = typeofPredicate('undefined');	
	S.isNumber = typeofPredicate('number');	
	S.isBoolean = typeofPredicate('boolean');
	
	S.isObject = function(x){
		return x && typeof(x) == 'object';
	};
	
	S.isDefined = function(x){
		return typeof(x) != 'undefined';
	};
	
	S.isArray = function(x){
		return x && x instanceof Array || typeof x == "array";
	};	
	
	S.isElement = function(x){
		return x && x.nodeType == 1;
	};
	
	S.isArrayLike = function(x){
		return S.isObject(x) && S.isNumber(x.length);
	};
	
	S.isPlainObject = function(x){
		return x && x.constructor === Object;
	};
	
	S.isInteger = function(x){
		return parseInt(x) === x;
	};
	
	S.isFloat = function(x){
		return parseFloat(x) === x;
	};
		
	S.hooks = function(f){
		var h = new S.Observable('Before', 'After');
		var g = function(){
			h.fireEvent('Before', f, this, arguments);
			var ret = f.apply(this, arguments);
			h.fireEvent('After', f, this, arguments, ret);
			return ret;
		};
		g.hooks = h;
		return g;
	};
	
	S.bind = function(self, f){
		if(S.isString(f)){
			f = self[f];
		}
		return function(){
			return f.apply(self, arguments);
		};
	};
	
	S.defer = function(f, msec){
		setTimeout(f, msec || 1);
	};
	
	S.args = function(args, offset, defaults){
		if(!defaults){
			return Array.prototype.splice.call(args, offset);
		}
		var len = Math.max(defaults.length, args.length);
		var a = [];
		for(var i=offset;i<len;i++){
			a[i] = S.isDefined(args[i]) ? args[i] : defaults[i];
		}
		return a;
	};
	
	S.partial = function(f){
		// FIXME: is copy() really needed?
		var args = S.args(arguments, 1);
		return function(){
			return f(args.concat(arguments));
		};
	};	
		
	S.each = function(a, f, self){
		if(!a || !a.length){
			return;
		}
		for(var i=0;i<a.length;i++){
			f.call(self, a[i], i, a);
		}
	};	
	
	S.find = function(a, p, d){
		for(var i=0;i<a.length;i++){
			if(p(a[i])){
				return a[i];
			}
		}
		return d;
	};		
		
	S.remove = function(a, o){
		var i = a.indexOf(o);
		if(i >= 0){
			a.splice(i, 1);
		}
		return i >= 0;
	};
	
	S.removeAll = function(a, p, callback){
		var modified = false;
		for(var i=a.length-1;i>=0;i--){
			if((S.isFunction(p) && p(a[i])) || a[i] in p){
				var ai = a[i];
				a.splice(i, 1);
				if(callback){
					callback(ai, i);
				}
				modified = true;				
			}			
		}
		return modified;
	};
	
	S.first = function(a){
		return a[0];
	};
	
	S.last = function(a){
		return a[a.length - 1];
	};
	
	S.min = function(a, f){
		f = f || S.lambda.identity;
		var m, mv = Infinity;
		for(var i=0;i<a.length;i++){			
			var v = f(a[i], i);
			if(v < mv){
				mv = v;
				m = a[i];
			}
		}
		return m;
	};
	S.max = function(a, f){
		f = f || S.lambda.identity;
		var m, mv = -Infinity;
		for(var i=0;i<a.length;i++){
			var v = f(a[i], i);
			if(v > mv){
				mv = v;
				m = a[i];
			}
		}
		return m;
	};
	
	S.any = function(a, f){
		for(var i=0;i<a.length;i++){
			if(f(a[i])){
				return true;
			}
		}
		return false;
	};	
	
	S.all = function(a, f){
		return !S.any(a, function(x){ return !f(x);});
	};
	
	S.contains = function(a, o){
		return a.indexOf(o) != -1;
	};
	
	S.equal = function(a, b){
		if(a === b){
			return true;
		}
		if(S.isObject(a) && S.isObject(b) && (a.__equals__ || b.__equals__)){
			return a.__equals__ && a.__equals__(b) || b.__equals__ && b.__equals__(a);
		}
		if(S.isArray(a) && S.isArray(b)){
			if(a.length != b.length){
				return false;
			}
			for(var i=0;i<a.length;i++){
				if(!S.equal(a[i], b[i])){
					return false;
				}
			}
			return true;
		}
		return a == b;
	};
		
	S.repr = function(x){
		if(S.isUndefined(x)){
			return "undefined";
		}
		if(S.isObject(x)){
			if(S.isFunction(x.__repr__)){
				return x.__repr__();
			}			   
			if(S.isPlainObject(x)){
				var props = [];
				for(var p in x){
					props.push(p + ': ' + S.repr(x[p]));
				}
				return '{' + props.join(', ') + '}';
			} 
			return x.toString();
		}
		if(S.isArray(x)){
			return '[' + x.map(S.__repr__).join(', ') + ']';
		}
		return String(x);
	};
	
	S.keys = function(o){
		var keys = [];
		for(var k in o){
			keys.push(k);
		}
		return keys;
	};	
		
	S.toQueryString = function(o){
		var p = [];
		for(var k in o){
			p.push(k + '=' + encodeURIComponent(o[k]));
		}
		return p.join('&');
	};
	
	S.protoExtend = function(ctor, name, f){
		var proto = ctor.prototype;
		if(S.isObject(name)){
			for(var n in name){
				if(!proto[n]){
					proto[n] = name[n];
				}
			}
		}
		else if(!proto[name]){
			proto[name] = f;
		}
	};
	S.protoBind = function(ctor, name, f){
		if(S.isObject(name)){
			for(var n in name){
				S.protoBind(ctor, n, name[n]);
			}
		}
		else{
			S.protoExtend(ctor, name, function(self){
				f.apply(self, Array.prototype.slice.call(arguments, 1));
			});
		}
	};
	
			
	S.string = {
		startsWith: function(str, prefix){
			return str.indexOf(prefix) === 0;
		},
		
		empty: S.lambda.not,
		
		blank: function(str){
			return /^\s*$/.test(str); 
		},
		trim: function(str){
			return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
		},
		reEscape: function(str){
			return str.replace('(','\\(').replace(')','\\)');
		},
		contains: function(str, substr){
			return str.indexOf(substr) >= 0;
		},
		capfirst: function(str){
			return str.charAt(0).toUpperCase() + str.substring(1);
		},
		times: function(str, n){
			var r = '';
			for(var i=0;i<n;i++){
				r += str;
			}
			return r;
		},
		pad: function(str, len, pad){
			pad = pad || '0';
			str = String(str);
			while(str.length < len){
				str = pad + str;
			}
			return str;
		},

		format: function(str, args){
			var result = str;
			if(S.isArray(args)){
				var index = 0;
				result = str.replace(/(%+)s/g, function(match){
					var p = RegExp.$1;
					if(!(p.length % 2)){
						return p + 's';
					}
					return p.substring(1) + args[index++];
				});
			}
			else if(S.isObject(args)){
				result = str.replace(/(%+)\((\w+)\)s/g, function(match, p, name){
					if(!(p.length % 2)){
						return p + '(' + name + ')s';
					}
					return p.substring(1) + args[name];
				});
			}
			return result.replace(/%%/g,'%');
		},
		
		firstDiffIndex: function(a, b){
			var len = Math.min(a.length, b.length);
			for(var i=0;i<len;i++){
				if(a.charAt(i) != b.charAt(i)){
					return i;
				}
			}
			return -1;
		}
	};
	
	S.date = {
		now: new Date(),
		daysInMonth: [31,28,31,30,31,30,31,31,30,31,30,31],
		isThisYear: function(d){
			return d && this.now.getFullYear() == d.getFullYear();
		},
		isThisMonth: function(d){
			return d && this.now.getMonth() == d.getMonth() && this.isThisYear(d);
		},
		isToday: function(d){
			return d && d.getDate() == this.now.getDate() && this.isThisMonth(d);
		},
		// FIXME: fails on 01.jan
		isYesterday: function(d){
			return d && d.getDate() + 1 == this.now.getDate() && this.isThisMonth(d);
		},
		// FIXME: fails on 31.dec
		isTomorrow: function(d){
			return d && d.getDate() - 1 == this.now.getDate() && this.isThisMonth(d);
		},
		isLeapYear: function(d){
			var y = d.getFullYear();
			return !(y & 3 || !(y % 100 || !(y % 400 || !y)));
		},
		getDaysInMonth: function(d){
			var m = d.getMonth();
			return (this.daysInMonth[m] + ((m == 1 && this.isLeapYear(d)) ? 1 : 0));
		},
		getFirstDayOfMonth: function(d){
			return (d.getDay() + 36 - d.getDate()) % 7;
		}
	};
	
	S.meta = {
		property: function(c, name, writeable){
			var capName = S.string.capfirst(name);
			var proto = c.prototype;
			if(writeable !== false){
				proto['set' + capName] = S.setter(name);
			}
			proto['get' + capName] = S.getter(name);
		},
		observableProperty: function(c, name, writeable){
			var capName = S.string.capfirst(name);
			if(writeable !== false){
				c.prototype['set' + capName] = function(value){
					var old = this['get' + capName]();
					if(old != value){
						this[name] = value;
						this.fireEvent('PropertyChange', name, value, old);
					}					
				};
			}
			c.prototype['get' + capName] = S.getter(name);			
		},
		delegate: function(c, d, methods){
			methods.forEach(function(m){
				c.prototype[m] = function(){
					this[d][m].apply(this, arguments);
				};
			});
		}
	};
	
	S.Class = {
		objectPrototype: {
			call: function(klass, method, args){
				return klass.prototype[method].apply(this, args || []);
			},
			init: function(klass, args){
				klass.prototype.__init__.apply(this, args || []);
			},
			__getattr__: function(name, defaultValue){
				return S.getattr(this, name, defaultValue);
			},
			instanceOf: function(klass){
				return this.prototype.constructor.isSubclassOf(klass);
			}
		},
		methods: {
			isSubclassOf: function(c){
				if(c === this){
					return true;
				}
				return this.__parents__.some(function(p){
					return p.isSubclassOf(c);
				});
			},
			isSuperclassOf: function(c){
				return c.isSubclassOf(this);
			},
			instance: function(a, b, c, d, e, f){
				return new this(a, b, c, d, e, f);
			}
		},
		create: function(){
			var parents = [];
			var a0 = arguments[0];
			var a1 = arguments[1];
			if(S.isFunction(a0)){
				parents = [a0];
			}
			else if(S.isArray(a0)){
				parents = a0;
			}
			
			var properties = (parents.length > 0) ? a1 : a0;
			var staticProperties = (parents.length > 0) ? arguments[2] : a1;
			
			var c = function(){
				this.__init__.apply(this, arguments);
			};			

			if(!parents.length){
				S.overwrite(c, S.Class.methods);
				S.overwrite(c.prototype, S.Class.objectPrototype);
			}

			for(var i=0;i<parents.length;i++){
				var p = parents[i];
				S.overwrite(c.prototype, p.prototype);
				for(var staticProperty in p){
					if(staticProperty != 'prototype'){
						c[staticProperty] = S.copy(p[staticProperty]);
					}
				}				
			}

			S.overwrite(c.prototype, properties);
			
			if(staticProperties){
				S.overwrite(c, staticProperties);
			}
			
			if(!c.prototype.__init__){
				c.prototype.__init__ = S.lambda.empty;
			}
			c.prototype.constructor = c;
			c.__parents__ = parents;
			if(c.prototype.__meta__){
				c.prototype.__meta__(c);
			}
			return c;
		}
	};
	
	S.getattr = function(o, p, d){
		if(p == 'this'){
			return o;
		}
		var getter = 'get' + S.string.capfirst(p);
		if(S.isFunction(o[getter])){
			return o[getter].apply(o);
		}
		if(S.isDefined(o[p])){
			return o[p];
		}
		return d;
	};
	
	S.setattr = function(o, p, v){
		var setter = 'set' + S.string.capfirst(p);
		if(S.isFunction(o[setter])){
			o[setter].apply(o, [v]);
		}
		else{			
			o[p] = v;
		}
	};
	
	S.hasattr = function(o, p){
		return S.isDefined(o[p]);
	};
	
	S.getter = function(p){
		return function(){
			return this[p];
		};
	};
	S.setter = function(p){
		return function(x){
			this[p] = x;
		};
	};
	S.alias = function(name){
		return function(){
			this[name].apply(this, arguments);
		};
	};
	
	S.lookup = function(path, object){
		object = object || window;
		path = path.split('.');
		for(var i=0;i<path.length;i++){
			object = object[path[i]];
			if(!S.isDefined(object)){
				return; // undefined
			}
		}
		return object;
	};
	
	S.define = function(path, value, object){
		object = object || window;
		path = path.split('.');
		for(var i=0;i<path.length-1;i++){
			var p = path[i];
			if(!S.isDefined(object[p])){
				object[p] = {};
			}
			object = object[p];
		}
		object[path[path.length-1]] = value;		
	};
	
	// FIXME: functions?
	S.copy = function(x){
		if(S.isArrayLike(x)){
			var c = [];
			for(var i=0;i<x.length;i++){
				c.push(x[i]);
			}
			return c;
		}
		if(S.isObject(x)){
			if(S.isFunction(x.__copy__)){
				return x.__copy__();
			}
			var c = {};
			for(p in x){
				c[p] = x[p];
			}
			return c;
		}
		// should be immutable (number, string, boolean, null)
		return x;
	};
	
	// experimental
	S.deepcopy = function(x, memo){
		if(x instanceof Array){
			return x.map(S.deepcopy);
		}
		if(S.isObject(x)){			
			// don't copy elements
			if(S.isElement(x)){
				return x;
			}
			memo = memo || {};
			var hash = S.hash(x);
			if(S.contains(memo, hash)){
				return memo[hash];
			}
			// custom copy() method
			if(S.isFunction(x.__deepcopy__)){
				return x.__deepcopy__(memo);
			}
			var c = {};
			for(var k in x){
				c[k] = S.deepcopy(x[k], memo);
			}
			return c;
		}
		return x;
	};
	
	S.log = function(msg, args){
		if(!S.isString(msg)){
			return console.log.apply(console, arguments.map(S.repr));
		}
		var argc = arguments.length;
		if(argc == 1){
			return console.log(msg);
		}
		if(argc > 2){
			args = Array.prototype.slice.apply(arguments, [1]);
		}
		return console.log(S.string.format(msg, args));
	};
		
	S.cumulativeOffset = function(element) {
		var x = 0, y = 0;
		do {
			x += element.offsetTop  || 0;
			y += element.offsetLeft || 0;
			element = element.offsetParent;
		} while (element);
		return {x: x, y: y};
	};	
	
	S.cookies = {
		parse: function(cookieString){
			cookieString = cookieString || window.document.cookie || "";
			var pairs = cookieString.split('; ');
			var cookies = {};
			pairs.forEach(function(p){
				var c = p.split('=');
				cookies[c[0]] = c[1];
			});		
			return cookies;
		}
	};
	
})();
// from http://erik.eae.net/playground/arrayextras/arrayextras.js

// Mozilla 1.8 has support for indexOf, lastIndexOf, forEach, filter, map, some, every
// http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Array
(function(){
var S = shrubbery;
S.mixin(Array.prototype, {

	indexOf: function(obj, fromIndex){
		if(!fromIndex){
			fromIndex = 0;
		}
		else if(fromIndex < 0){
			fromIndex = Math.max(0, this.length + fromIndex);
		}
		for(var i = fromIndex; i < this.length; i++){
			if(this[i] === obj){
				return i;
			}
		}
		return -1;
	},
	
	lastIndexOf: function(obj, fromIndex){
		if(fromIndex == null){
			fromIndex = this.length - 1;
		}
		else if(fromIndex < 0){
			fromIndex = Math.max(0, this.length + fromIndex);
		}
		for(var i = fromIndex; i >= 0; i--){
			if(this[i] === obj){
				return i;
			}
		}
		return -1;
	},	

	forEach: function(f, obj){
		var l = this.length;
		for(var i = 0; i < l; i++){
			f.call(obj, this[i], i, this);
		}
	},
	
	filter: function(f, obj){
		var l = this.length;
		var res = [];
		for(var i = 0; i < l; i++){
			if(f.call(obj, this[i], i, this)){
				res.push(this[i]);
			}
		}
		return res;
	},

	map: function(f, obj){
		var l = this.length;
		var res = [];
		for(var i = 0; i < l; i++){
			res.push(f.call(obj, this[i], i, this));
		}
		return res;
	},
	
	some: function(f, obj){
		var l = this.length;
		for(var i = 0; i < l; i++){
			if(f.call(obj, this[i], i, this)){
				return true;
			}
		}
		return false;
	},

	every: function(f, obj){
		var l = this.length;
		for(var i = 0; i < l; i++){
			if(!f.call(obj, this[i], i, this)){
				return false;
			}
		}
		return true;
	},
	
	reduce: function(f, n){
		var offset = 0;
		if(!S.isDefined(n)){
			if(!this.length){
				return;
			}
			n = this[0];
			offset++;
		}
		for(var i=offset;i<this.length;i++){
			n = f(n, this[i], i, this);
		}
		return n;
	},
	
	reduceRight: function(f, n){
		var offset = this.length - 1;
		if(!S.isDefined(n)){
			if(!this.length){
				return;
			}
			n = this[offset];
			offset--;
		}
		for(var i=offset;i>=0;i--){
			n = f(n, this[i], i, this);
		}
		return n;	
	}
});

})();

(function(){

var S = shrubbery;

S.error = {
	Interceptor: S.Class.create(S.Observable, {
		__init__: function(){
			this.init(S.Observable);			
			this.addEvents('Exception');
			var globalInterceptor = S.error.globalInterceptor;
			
			if(globalInterceptor){
				this.addListener('Exception', globalInterceptor);
			}
		},				
		
		intercept: function(f, scope, args, fin){
			try{
				return f.apply(scope, args);
			}
			catch(e){
				this.fireEvent('Exception', e, f);
			}
			finally{
				if(fin){
					fin();
				}
			}
		},
		
		intercepted: function(f, scope, fin){
			return S.bind(this, function(){
				return this.intercept(f, scope, arguments, fin);
			});
		},
		
		onException: function(source, e, f){
			this.fireEvent('Exception', e, f);
		}
	}),
	
	Exception: S.Class.create({
		__init__: function(msg){
			var args = null;
			if(arguments.length > 1){
				args = Array.prototype.slice.apply(arguments, 1);
				if(args.length == 1 && S.isObject(args[0])){
					args = args[0];
				}
				msg = S.string.format(msg, args)
			}
			this.msg = msg;
			this.args = args;
		},
		toString: function(){
			return 'shrubbery.Exception: ' + this.msg;
		}
	})
};

S.error.globalInterceptor = new S.error.Interceptor();

})();
(function(){
	var S = shrubbery;
	
    var safariKeys = {
        63234 : 37, // left
        63235 : 39, // right
        63232 : 38, // up
        63233 : 40, // down
        63276 : 33, // page up
        63277 : 34, // page down
        63272 : 46, // delete
        63273 : 36, // home
        63275 : 35  // end
    };	
	
    S.event = {
    	keys: {
		  BACKSPACE: 8,
		  TAB:       9,
		  RETURN:   13,
		  ESCAPE:   27,
		  LEFT:     37,
		  UP:       38,
		  RIGHT:    39,
		  DOWN:     40,
		  DELETE:   46,
		  SPACE:    32,
		  HOME:     36,
		  END:      35,
		  PAGEUP:   33,
		  PAGEDOWN: 34,
		  INSERT:   45    	
    	},
    	
    	errors: new S.error.Interceptor(),
    	
    	addSaveListener: function(el, type, scope, f){
			if(scope && S.isString(f)){
				f = scope[f];
			}
			return this.addListener(el, type, null, S.event.errors.intercepted(f, scope));
    	},
    	
		addListener: function(el, type, scope, f){
			if(scope && S.isString(f)){
				f = scope[f];
			}			
			var callback = function(e){
				f.apply(scope, [e || window.event, el, type]);
			};
			if(el.addEventListener){
				el.addEventListener(type, callback, false);
			}
			else{
				el.attachEvent("on" + type, callback);
			}
			return {
				type: type,
				callback: callback,
				element: el
			};
		},
		
		addMouseWheelListener: function(scope, f){
			
		},
		
		removeListener: function(handle){
			var el = handle.element;
			if(el.removeEventListener){
				el.removeEventListener(handle.type, handle.callback, false);
			}
			else{
				el.detachEvent("on" + handle.type, handle.callback);
			}
		},
		
		stopPropagation: function(e){
			if(S.isIE){
				e.cancelBubble = true;
			}
			else{
				e.stopPropagation();
			}
		},
						
		preventDefault: function(e){
			if(S.isIE){
				e.returnValue = false;
			}
			else{
				e.preventDefault();
			}
		},
		
		stop: function(e){
			this.preventDefault(e);
			this.stopPropagation(e);
		},		
		    
		pointer: function(e){
			return new S.Position(this.pointerX(e), this.pointerY(e));
		},	
		pointerX: function(event){
			return event.pageX || (event.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft));
		},
		pointerY: function(event){
			return event.pageY || (event.clientY + (document.documentElement.scrollTop || document.body.scrollTop));
		},
		
		relatedTarget: function(e){
			switch(e.type) {
				case 'mouseover': 
					return e.fromElement;
					break;
				case 'mouseout':  
					return e.toElement;
					break;
			}
			return null;
		},
		// from ext
        isNavKeyPress : function(e){
			var k = this.getKeyCode(e);
			return (k >= 33 && k <= 40) || k == this.keys.RETURN || k == this.keys.TAB || k == this.keys.ESC;
        },
        getKeyCode: function(e){
			var k = e.keyCode;
			return S.isSafari ? (safariKeys[k] || k) : k;
        },
        getCharCode: function(e){
        	return e.charCode || e.keyCode;
        },
        getWheelDelta : function(e){
            var delta = 0;
            if(e.wheelDelta){ /* IE/Opera. */
                delta = e.wheelDelta/120;
                if(S.isOpera){
                	delta = - delta;
                }
            }
            else if(e.detail){ /* Mozilla case. */
                delta = -e.detail/3;
            }
            return delta;
        }
		
    };    		

})();
(function(){
	var S = shrubbery;
	
	(function(){
		// from dojo
		var n = navigator;
		var ua = n.userAgent;
		var av = n.appVersion;
		var tv = parseFloat(av);
		console.log(ua, av);

		S.isOpera = (ua.indexOf("Opera") >= 0) ? tv : 0;
		S.isSafari = av.indexOf("Safari") >= 0;
		S.isKhtml = (av.indexOf("Konqueror") >= 0) || (av.indexOf("Safari") >= 0) ? tv : 0;
		if(S.isSafari){
			S.isSafari = parseFloat(av.split("Version/")[1]) || 2;
		}
		var geckoPos = ua.indexOf("Gecko");
		S.isMozilla = S.isMoz = ((geckoPos >= 0)&&(!S.isKhtml)) ? tv : 0;
		S.isFF = 0;
		S.isIE = 0;
		try{
			if(S.isMoz){
				S.isFF = parseFloat(ua.split("Firefox/")[1].split(" ")[0]);
			}
			if((document.all) && (!S.isOpera)){
				S.isIE = parseFloat(av.split("MSIE ")[1].split(";")[0]);
			}
		}catch(e){}

		var cm = document.compatMode;
		S.isQuirks = (cm == "BackCompat") || (cm == "QuirksMode") || (S.isIE < 6);
	})();		

	S.getViewportBox = function(el){
		return new S.Box(el.scrollTop, el.scrollLeft, el.offsetWidth, el.offsetHeight);
	};	
	
	// fixme	
	S.getBox = function(el){
		return S.getDimensions(el);
	};	
	// fixme	
	S.getContentWidth = function(el){
		return S.getDimensions(el).width;
	};
	// fixme	
	S.getContentHeight = function(el){
		return S.getDimensions(el).height;
	};	
	// fixme
	S.getContentDimensions = function(el){
		return S.getDimensions(el);
	};
	// fixme
	S.getContentBox = function(el){
		return S.getDimensions(el);
	};
	
	S.getHeight = function(el){
		return S.getDimensions(el).height;
	};
	
	S.getWidth = function(el){
		return S.getDimensions(el).width;
	};
		
	// from prototype
	S.getDimensions = function(element) {
		var display = S.html.getStyle(element, 'display');
		if (display != 'none' && display !== null){
			return {width: element.offsetWidth, height: element.offsetHeight};
		}
	
		// All *Width and *Height properties give 0 on elements with display none,
		// so enable the element temporarily
		var els = element.style;
		var originalVisibility = els.visibility;
		var originalPosition = els.position;
		var originalDisplay = els.display;
		els.visibility = 'hidden';
		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};
	};
	
	
	/* end fixme */
	
	S.$ = function(elOrId){
		if(S.isString(elOrId)){
			return document.getElementById(elOrId);
		}
		return elOrId;
	};						    
					
    S.getViewportDimensions = function(){
        var win = window;
        var b = document.body;
        var w,h;

        if (win.innerWidth) {
			w = win.innerWidth;
			h = win.innerHeight;
		}
        else if (b.parentElement.clientWidth) {
			w = b.parentElement.clientWidth;
			h = b.parentElement.clientHeight;
		}
        else if (b && b.clientWidth) {
			w = b.clientWidth;
			h = b.clientHeight;
        }
        return {width: w, height: h};
    };
				
	S.setPosition = function(el, x, y, unit){
		if(S.isObject(x)){
			unit = y;		
			y = x.y;
			x = x.x;
		}
		if(!S.isDefined(unit)){
			unit = 'px';
		}
		S.html.setStyle(el, 'left', x + unit);
		S.html.setStyle(el, 'top', y + unit);
	};
	
	S.getPosition = function(el){
		return new S.Position(parseInt(el.offsetLeft, 10), parseInt(el.offsetTop, 10));
	};
	
	(function() {
		/*
		from prototype
		*/
		/* Support for the DOMContentLoaded event is based on work by Dan Webb,
		Matthias Miller, Dean Edwards and John Resig. */
		
		var onLoadCallbacks = [];
		var timer;		
		
		S.callOnLoad = function(f){
			if(document.loaded){
				f();
			}
			else{
				onLoadCallbacks.push(f);
			}
		};					
		
		function fireContentLoadedEvent(){
			if(document.loaded){
				return;
			}
			if(timer){
				window.clearInterval(timer);
			}
			S.each(onLoadCallbacks, function(c){
				c();
			});
			document.loaded = true;
		}
		
		if(document.addEventListener){
			if(S.isSafari){
				timer = window.setInterval(function() {
					if(/loaded|complete/.test(document.readyState)){
						fireContentLoadedEvent();
					}
				}, 0);
				S.event.addListener(window, "load", null, fireContentLoadedEvent);
			}
			else{
				document.addEventListener("DOMContentLoaded", fireContentLoadedEvent, false);
			}		
		}
		else{
			document.write("<script id=__onDOMContentLoaded defer src=//:><\/script>");
			S.$("__onDOMContentLoaded").onreadystatechange = function() {
				if (this.readyState == "complete") {
					this.onreadystatechange = null;
					fireContentLoadedEvent();
				}
			};
		}
	})();		

})();
(function(){
	var S = shrubbery;
	S.html = {
		idPrefix: 'shrubbery_',
		id: (function(){
			var nextId = 1;
			return function(el){
				if(!el.id){
					el.id = this.idPrefix + (nextId++);
				}
				return el.id;
			};			
		})(),

		create: function(cls, content, attributes){
			var el = this.createElement('div', content, attributes);
			if(cls){
				if(S.isArray(cls)){
					cls = cls.join(' ');
				}			
				el.className = cls;
			}
			return el;
		},
		
		escape: function(str){
			return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
		},

		createElement: function(tagName, content, attributes){
			var el = document.createElement(tagName);
			if(content){
				if(S.isString(content)){
					el.innerHTML = content;
				}
				else if(S.isArray(content)){
					for(var i=0;i<content.length;i++){
						var c = this.toElement(content[i]);
						if(!c || !S.isDefined(c.nodeType)){
							throw sdkshdk;
						}
						el.appendChild(c);
					}
				}
				else if(S.isDefined(content.nodeType)){
					el.appendChild(content);
				}
				else{
					el.appendChild(this.toElement(content));
				}
			}
			if(attributes){
				if(S.isString(attributes)){
					el.className = attributes;
				}
				else{
					for(var attr in attributes){
						el[attr] = attributes[attr];
					}
				}
			}
			return el;
		},	
		
		append: function(parent, cls, content){
			return parent.appendChild(this.create.call(this, cls, content));
		},
		
		update: function(el, content){
			S.html.clear(el);
			if(content){
				if(S.isString(content)){
					el.innerHTML = content;
				}
				else{
					el.appendChild(this.toElement(content));
				}			
			}
		},		
		
		hasClass: function(el, cls){
			return ((" "+el.className+" ").indexOf(" "+cls+" ") >= 0);
		},
	
		addClass: function(el){
			for(var i=1;i<arguments.length;i++){
				var cls = arguments[i];
				if((" "+el.className+" ").indexOf(" "+cls+" ") < 0){
					el.className = el.className + (el.className ? ' ' : '') + cls;
				}
			}
		},

		removeClass: function(el, cls){
			var t = S.string.trim((" " + el.className + " ").replace(" " + cls + " ", " "));
			if(el.className != t){
				el.className = t; 
			}
		},
		
		toggleClass: function(el, cls, condition){
			if(!S.isDefined(condition)){
				condition = !S.html.hasClass(el, cls);
			}
			this[condition ? "addClass" : "removeClass"](el, cls);
		},
		
		setClass: function(el, cls, set){
			if(set === false){
				this.removeClass(el, cls);
			}
			else{
				this.addClass(el, cls);
			}
		},
		
		hide: function(el){
			el.style.display = 'none';
		},
	
		show: function(el, display){
			el.style.display = display || '';
		},
		
		getStyle: function(element, style){
			if(style == 'float'){
				style = 'cssFloat';
			}
			var value = element.style[style];
			if(!value || value == 'auto'){
				var cs = document.defaultView.getComputedStyle(element, null);
				value = cs ? cs[style] : null;
			}
			if(style == 'opacity'){
				return parseFloat(value) || 1;
			}
			//console.log(style, value);
			return value == 'auto' ? null : value;
		},		
		
		setStyle: function(el, style, value){
			if(S.isObject(style)){
				for(var s in style){
					this.setStyle(el, s, style[s]);
				}
			}
			else{			
				if(style == 'opacity'){
					if(value > 0.99999){
						value = 1;
					}
					else if(value && value < 0.00001){
						value = 0;
					}
					el.style.opacity = value === 1 ? '' : value;
				}
				else{
					if(style == 'float' || style == 'cssFloat'){
						style = S.isDefined(el.style.styleFloat) ? 'styleFloat' : 'cssFloat';
					}
					el.style[style] = value;
				}
			}
		},
		
		setWidth: function(el, w, unit){
			this.setStyle(el, 'width', w + (unit || 'px'));
		},
		
		setHeight: function(el, h, unit){
			this.setStyle(el, 'height', h + (unit || 'px'));
		},
		
		setOpacity: function(element, value) {
		},
		
		remove: function(el){
			el.parentNode.removeChild(el);
		},
		
		replace: function(el, node){
			el.parentNode.replaceChild(node, el);
		},
		
		clear: function(el){
			el.innerHTML = '';
		},
		
		getViewportOffset: function(){
			return {
				x: window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
				y: window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
			};
		},
		
		contains: function(parent, child){
			var el = child;
			while(el){
				if(el === parent){
					return true;
				}
				el = el.parentNode;
			}
			return false;
		},
		
		render: function(str){
			var h = document.createElement('div');
			h.innerHTML = str;
			var el = h.firstChild;
			//this.remove(el);
			return el;
		},
		
		toElement: function(el, cls){
			if(!el){
				return undefined;
			}
			if(S.isString(el)){
				el = this.render(el);
			}
			else if(S.isFunction(el.toElement)){
				el = el.toElement();
			}
			else if(S.isFunction(el)){
				el = this.toElement(el());
			}
			if(S.isElement(el)){
				if(cls){
					this.addClass(el, cls);
				}			
			}			
			return el;
		},
		
		getElementsByClassName: function(cls, root, tagName){
			root = root || document;
			var els = root.getElementsByTagName(tagName || '*');
			var matches = [];
			for(var i=0;i<els.length;i++){
				if(this.hasClass(els[i], cls)){
					matches.push(els[i]);
				}
			}
			return matches;
		}
		
	};	
	
	if(S.isIE){
		S.html.getStyle = function(element, style){
			if(style == 'float' || style == 'cssFloat'){
				style = 'styleFloat';
			}
			var value = element.style[style];
			
			if(!value || value == 'auto'){
				var cs = element.currentStyle;
				value = cs ? cs[style] : null;
			}
			
			if(style == 'opacity'){
				var match = this.getStyle('filter').match(/alpha\(opacity=(.*)\)/);
				if(match[1]){
					return parseFloat(match[1]) / 100;
				}
				return 1;			
			}			
			
			if(value == 'auto'){
				if(style == 'height'){
					value = element.offsetHeight;
				}
				else if(style == 'width'){
					value = element.offsetWidth;
				}				
			}
			
			return value;
		};
	}
	
})();


