// at this stage no fixes or wrappers are loaded from any lib
// so you can see some dirty "cross browser" code here

if (!(/Oops=disabled/.test(document.cookie)) && !window.console)
{
	try { console.log('Oops enabled') } catch (ex) {}
	
	function Oops (message, url, line, type)
	{
		try // to fully describe an error
		{
			var href = 'http://oops.programica.ru/report',
				esc = window.encodeURIComponent || window.escape,
				q = '?url=' + esc(url + ':' + line) + '&message=' + esc(message) + '&session=' + Oops.session + '&type=' + (type || 'error')
			
			Oops.request(q)
		}
		catch (ex)
		{
			try // to scream for help
			{
				Oops.request(href + '?message=cant+report&type=error')
			}
			catch (ex) {}
		}
		
		return true
	}
	
	Oops.session = (new Date()).getTime().toString() + Math.round(Math.random() * 1E+17)
	Oops.request = function (src)
	{
		var I = new Image(1,1)
		I.src = src
		I.onload = function () {}
	}
	
	window.onerror = Oops
}

// defining spaces
if (!self.Programica) self.Programica = {version: '0.2'}


// base objects extensions
// this code is heavy minified and it couldn't be changed frequently
;(function(){

var O = Object, A = Array, Ap = A.prototype, S = String, Fp = Function.prototype, D = Date, N = Number, M = Math

function add (d, s) { if (d) for (var k in s) if (!(k in d)) d[k] = s[k]; return d }

add
(
	O,
	{
		add: add,
		extend: function (d, s) { if (d) for (var k in s) d[k] = s[k]; return d },
		copy: function (s) { var d = {}; for (var k in s) d[k] = s[k]; return d },
		keys: function (s) { var r = []; for (var k in s) r.push(k); return r },
		values: function (s) { var r = []; for (var k in s) r.push(s[k]); return r }
	}
)

add(String, {localeCompare: function (a, b) { return a < b ? -1 : (a > b ? 1 : 0) }})

add(Fp, {extend: function (s) { for (var k in s) this[k] = s[k]; return this }})

var ceil = M.ceil, floor = M.floor, round = M.round, random = M.random
add(M, {longRandom: function () { return (new D()).getTime().toString() + round(random() * 1E+17) }})


add
(
	Ap,
	{
		indexOf: function(v, i)
		{
			var len = this.length,
				i = N(i) || 0
			i = (i < 0) ? (ceil(i) + len) : floor(i)

			for (; i < len; i++)
				if (i in this && this[i] === v)
					return i
			return -1
		},
		forEach: function (f, inv) { for (var i = 0, len = this.length; i < len; i++) f.call(inv, this[i], i, this) },
		map: function(f, inv)
		{
			var len = this.length,
				res = new A(len)
			for (var i = 0; i < len; i++)
				if (i in this)
					res[i] = f.call(inv, this[i], i, this)
			return res
		}
	}
)

add(A, {copy: function (src) { return Ap.slice.call(src) }})

})();
// defining spaces

function $ (id) { return document.getElementById(id) }
function $E  (type, props)
{
	var node = document.createElement(type)
	if (props)
		for (var i in props)
			node.setAttribute(i, props[i])
	return node
}

$.onload = function (fn) { return self.addEventListener('load', fn, false) }
$.include = function (src)
{
	var me = arguments.callee
	var cache = me.cache || (me.cache = {}) 
	if (me.cache[src])
		return me.cache[src]
	var node = document.createElement('script')
	node.type = 'text/javascript'
	node.src = src
	document.getElementsByTagName('head')[0].appendChild(node)
	cache[src] = node
	return node
}

// NodesShortcut
;(function(){

var doc = document, undef, myName = 'NodesShortcut'

function T (text) { return doc.createTextNode(text) }
function N (tag, cn, text)
{
	var node = doc.createElement(tag)
	if (cn !== undef) node.className = cn
	if (text !== undef) node.appendChild(T(text))
	return node
}

function E (tag, cn, props)
{
	var node = doc.createElement(tag)
	if (cn !== undef) node.className = cn
	if (props)
		for (var i in props)
			node.setAttribute(i, props[i])
	return node
}

var code = 'var T=' + myName + '.T,N=' + myName + '.N,E=' + myName + '.E',
	Me = self[myName] = function () { return code }

Me.T = T
Me.N = N
Me.E = E

})();









/*
 * Sizzle CSS Selector Engine - v1.0
 *  Copyright 2009, The Dojo Foundation
 *  Released under the MIT, BSD, and GPL Licenses.
 *  More information: http://sizzlejs.com/
 */
;(function(){var o=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,i=0,d=Object.prototype.toString;var b=function(C,s,z,u){z=z||[];var e=s=s||document;if(s.nodeType!==1&&s.nodeType!==9){return[]}if(!C||typeof C!=="string"){return z}var A=[],B,x,F,E,y,r,q=true,v=n(s);o.lastIndex=0;while((B=o.exec(C))!==null){A.push(B[1]);if(B[2]){r=RegExp.rightContext;break}}if(A.length>1&&j.exec(C)){if(A.length===2&&f.relative[A[0]]){x=g(A[0]+A[1],s)}else{x=f.relative[A[0]]?[s]:b(A.shift(),s);while(A.length){C=A.shift();if(f.relative[C]){C+=A.shift()}x=g(C,x)}}}else{if(!u&&A.length>1&&s.nodeType===9&&!v&&f.match.ID.test(A[0])&&!f.match.ID.test(A[A.length-1])){var G=b.find(A.shift(),s,v);s=G.expr?b.filter(G.expr,G.set)[0]:G.set[0]}if(s){var G=u?{expr:A.pop(),set:a(u)}:b.find(A.pop(),A.length===1&&(A[0]==="~"||A[0]==="+")&&s.parentNode?s.parentNode:s,v);x=G.expr?b.filter(G.expr,G.set):G.set;if(A.length>0){F=a(x)}else{q=false}while(A.length){var t=A.pop(),w=t;if(!f.relative[t]){t=""}else{w=A.pop()}if(w==null){w=s}f.relative[t](F,w,v)}}else{F=A=[]}}if(!F){F=x}if(!F){throw"Syntax error, unrecognized expression: "+(t||C)}if(d.call(F)==="[object Array]"){if(!q){z.push.apply(z,F)}else{if(s&&s.nodeType===1){for(var D=0;F[D]!=null;D++){if(F[D]&&(F[D]===true||F[D].nodeType===1&&h(s,F[D]))){z.push(x[D])}}}else{for(var D=0;F[D]!=null;D++){if(F[D]&&F[D].nodeType===1){z.push(x[D])}}}}}else{a(F,z)}if(r){b(r,e,z,u);b.uniqueSort(z)}return z};b.uniqueSort=function(q){if(c){hasDuplicate=false;q.sort(c);if(hasDuplicate){for(var e=1;e<q.length;e++){if(q[e]===q[e-1]){q.splice(e--,1)}}}}};b.matches=function(e,q){return b(e,null,null,q)};b.find=function(w,e,x){var v,t;if(!w){return[]}for(var s=0,r=f.order.length;s<r;s++){var u=f.order[s],t;if((t=f.match[u].exec(w))){var q=RegExp.leftContext;if(q.substr(q.length-1)!=="\\"){t[1]=(t[1]||"").replace(/\\/g,"");v=f.find[u](t,e,x);if(v!=null){w=w.replace(f.match[u],"");break}}}}if(!v){v=e.getElementsByTagName("*")}return{set:v,expr:w}};b.filter=function(z,y,C,s){var r=z,E=[],w=y,u,e,v=y&&y[0]&&n(y[0]);while(z&&y.length){for(var x in f.filter){if((u=f.match[x].exec(z))!=null){var q=f.filter[x],D,B;e=false;if(w==E){E=[]}if(f.preFilter[x]){u=f.preFilter[x](u,w,C,E,s,v);if(!u){e=D=true}else{if(u===true){continue}}}if(u){for(var t=0;(B=w[t])!=null;t++){if(B){D=q(B,u,t,w);var A=s^!!D;if(C&&D!=null){if(A){e=true}else{w[t]=false}}else{if(A){E.push(B);e=true}}}}}if(D!==undefined){if(!C){w=E}z=z.replace(f.match[x],"");if(!e){return[]}break}}}if(z==r){if(e==null){throw"Syntax error, unrecognized expression: "+z}else{break}}r=z}return w};var f=b.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF_-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF_-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*_-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF_-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(e){return e.getAttribute("href")}},relative:{"+":function(w,e,v){var t=typeof e==="string",x=t&&!/\W/.test(e),u=t&&!x;if(x&&!v){e=e.toUpperCase()}for(var s=0,r=w.length,q;s<r;s++){if((q=w[s])){while((q=q.previousSibling)&&q.nodeType!==1){}w[s]=u||q&&q.nodeName===e?q||false:q===e}}if(u){b.filter(e,w,true)}},">":function(v,q,w){var t=typeof q==="string";if(t&&!/\W/.test(q)){q=w?q:q.toUpperCase();for(var r=0,e=v.length;r<e;r++){var u=v[r];if(u){var s=u.parentNode;v[r]=s.nodeName===q?s:false}}}else{for(var r=0,e=v.length;r<e;r++){var u=v[r];if(u){v[r]=t?u.parentNode:u.parentNode===q}}if(t){b.filter(q,v,true)}}},"":function(s,q,u){var r=i++,e=p;if(!q.match(/\W/)){var t=q=u?q:q.toUpperCase();e=m}e("parentNode",q,r,s,t,u)},"~":function(s,q,u){var r=i++,e=p;if(typeof q==="string"&&!q.match(/\W/)){var t=q=u?q:q.toUpperCase();e=m}e("previousSibling",q,r,s,t,u)}},find:{ID:function(q,r,s){if(typeof r.getElementById!=="undefined"&&!s){var e=r.getElementById(q[1]);return e?[e]:[]}},NAME:function(r,u,v){if(typeof u.getElementsByName!=="undefined"){var q=[],t=u.getElementsByName(r[1]);for(var s=0,e=t.length;s<e;s++){if(t[s].getAttribute("name")===r[1]){q.push(t[s])}}return q.length===0?null:q}},TAG:function(e,q){return q.getElementsByTagName(e[1])}},preFilter:{CLASS:function(s,q,r,e,v,w){s=" "+s[1].replace(/\\/g,"")+" ";if(w){return s}for(var t=0,u;(u=q[t])!=null;t++){if(u){if(v^(u.className&&(" "+u.className+" ").indexOf(s)>=0)){if(!r){e.push(u)}}else{if(r){q[t]=false}}}}return false},ID:function(e){return e[1].replace(/\\/g,"")},TAG:function(q,e){for(var r=0;e[r]===false;r++){}return e[r]&&n(e[r])?q[1]:q[1].toUpperCase()},CHILD:function(e){if(e[1]=="nth"){var q=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(e[2]=="even"&&"2n"||e[2]=="odd"&&"2n+1"||!/\D/.test(e[2])&&"0n+"+e[2]||e[2]);e[2]=(q[1]+(q[2]||1))-0;e[3]=q[3]-0}e[0]=i++;return e},ATTR:function(t,q,r,e,u,v){var s=t[1].replace(/\\/g,"");if(!v&&f.attrMap[s]){t[1]=f.attrMap[s]}if(t[2]==="~="){t[4]=" "+t[4]+" "}return t},PSEUDO:function(t,q,r,e,u){if(t[1]==="not"){if(t[3].match(o).length>1||/^\w/.test(t[3])){t[3]=b(t[3],null,null,q)}else{var s=b.filter(t[3],q,r,true^u);if(!r){e.push.apply(e,s)}return false}}else{if(f.match.POS.test(t[0])||f.match.CHILD.test(t[0])){return true}}return t},POS:function(e){e.unshift(true);return e}},filters:{enabled:function(e){return e.disabled===false&&e.type!=="hidden"},disabled:function(e){return e.disabled===true},checked:function(e){return e.checked===true},selected:function(e){e.parentNode.selectedIndex;return e.selected===true},parent:function(e){return !!e.firstChild},empty:function(e){return !e.firstChild},has:function(r,q,e){return !!b(e[3],r).length},header:function(e){return/h\d/i.test(e.nodeName)},text:function(e){return"text"===e.type},radio:function(e){return"radio"===e.type},checkbox:function(e){return"checkbox"===e.type},file:function(e){return"file"===e.type},password:function(e){return"password"===e.type},submit:function(e){return"submit"===e.type},image:function(e){return"image"===e.type},reset:function(e){return"reset"===e.type},button:function(e){return"button"===e.type||e.nodeName.toUpperCase()==="BUTTON"},input:function(e){return/input|select|textarea|button/i.test(e.nodeName)}},setFilters:{first:function(q,e){return e===0},last:function(r,q,e,s){return q===s.length-1},even:function(q,e){return e%2===0},odd:function(q,e){return e%2===1},lt:function(r,q,e){return q<e[3]-0},gt:function(r,q,e){return q>e[3]-0},nth:function(r,q,e){return e[3]-0==q},eq:function(r,q,e){return e[3]-0==q}},filter:{PSEUDO:function(v,r,s,w){var q=r[1],t=f.filters[q];if(t){return t(v,s,r,w)}else{if(q==="contains"){return(v.textContent||v.innerText||"").indexOf(r[3])>=0}else{if(q==="not"){var u=r[3];for(var s=0,e=u.length;s<e;s++){if(u[s]===v){return false}}return true}}}},CHILD:function(e,s){var v=s[1],q=e;switch(v){case"only":case"first":while(q=q.previousSibling){if(q.nodeType===1){return false}}if(v=="first"){return true}q=e;case"last":while(q=q.nextSibling){if(q.nodeType===1){return false}}return true;case"nth":var r=s[2],y=s[3];if(r==1&&y==0){return true}var u=s[0],x=e.parentNode;if(x&&(x.sizcache!==u||!e.nodeIndex)){var t=0;for(q=x.firstChild;q;q=q.nextSibling){if(q.nodeType===1){q.nodeIndex=++t}}x.sizcache=u}var w=e.nodeIndex-y;if(r==0){return w==0}else{return(w%r==0&&w/r>=0)}}},ID:function(q,e){return q.nodeType===1&&q.getAttribute("id")===e},TAG:function(q,e){return(e==="*"&&q.nodeType===1)||q.nodeName===e},CLASS:function(q,e){return(" "+(q.className||q.getAttribute("class"))+" ").indexOf(e)>-1},ATTR:function(u,s){var r=s[1],e=f.attrHandle[r]?f.attrHandle[r](u):u[r]!=null?u[r]:u.getAttribute(r),v=e+"",t=s[2],q=s[4];return e==null?t==="!=":t==="="?v===q:t==="*="?v.indexOf(q)>=0:t==="~="?(" "+v+" ").indexOf(q)>=0:!q?v&&e!==false:t==="!="?v!=q:t==="^="?v.indexOf(q)===0:t==="$="?v.substr(v.length-q.length)===q:t==="|="?v===q||v.substr(0,q.length+1)===q+"-":false},POS:function(t,q,r,u){var e=q[2],s=f.setFilters[e];if(s){return s(t,r,q,u)}}}};var j=f.match.POS;for(var l in f.match){f.match[l]=new RegExp(f.match[l].source+/(?![^\[]*\])(?![^\(]*\))/.source)}var a=function(q,e){q=Array.prototype.slice.call(q);if(e){e.push.apply(e,q);return e}return q};try{Array.prototype.slice.call(document.documentElement.childNodes)}catch(k){a=function(t,s){var q=s||[];if(d.call(t)==="[object Array]"){Array.prototype.push.apply(q,t)}else{if(typeof t.length==="number"){for(var r=0,e=t.length;r<e;r++){q.push(t[r])}}else{for(var r=0;t[r];r++){q.push(t[r])}}}return q}}var c;if(document.documentElement.compareDocumentPosition){c=function(q,e){var r=q.compareDocumentPosition(e)&4?-1:q===e?0:1;if(r===0){hasDuplicate=true}return r}}else{if("sourceIndex" in document.documentElement){c=function(q,e){var r=q.sourceIndex-e.sourceIndex;if(r===0){hasDuplicate=true}return r}}else{if(document.createRange){c=function(s,q){var r=s.ownerDocument.createRange(),e=q.ownerDocument.createRange();r.selectNode(s);r.collapse(true);e.selectNode(q);e.collapse(true);var t=r.compareBoundaryPoints(Range.START_TO_END,e);if(t===0){hasDuplicate=true}return t}}}}(function(){var q=document.createElement("div"),r="script"+(new Date).getTime();q.innerHTML="<a name='"+r+"'/>";var e=document.documentElement;e.insertBefore(q,e.firstChild);if(!!document.getElementById(r)){f.find.ID=function(t,u,v){if(typeof u.getElementById!=="undefined"&&!v){var s=u.getElementById(t[1]);return s?s.id===t[1]||typeof s.getAttributeNode!=="undefined"&&s.getAttributeNode("id").nodeValue===t[1]?[s]:undefined:[]}};f.filter.ID=function(u,s){var t=typeof u.getAttributeNode!=="undefined"&&u.getAttributeNode("id");return u.nodeType===1&&t&&t.nodeValue===s}}e.removeChild(q)})();(function(){var e=document.createElement("div");e.appendChild(document.createComment(""));if(e.getElementsByTagName("*").length>0){f.find.TAG=function(q,u){var t=u.getElementsByTagName(q[1]);if(q[1]==="*"){var s=[];for(var r=0;t[r];r++){if(t[r].nodeType===1){s.push(t[r])}}t=s}return t}}e.innerHTML="<a href='#'></a>";if(e.firstChild&&typeof e.firstChild.getAttribute!=="undefined"&&e.firstChild.getAttribute("href")!=="#"){f.attrHandle.href=function(q){return q.getAttribute("href",2)}}})();if(document.querySelectorAll){(function(){var e=b,r=document.createElement("div");r.innerHTML="<p class='TEST'></p>";if(r.querySelectorAll&&r.querySelectorAll(".TEST").length===0){return}b=function(v,u,s,t){u=u||document;if(!t&&u.nodeType===9&&!n(u)){try{return a(u.querySelectorAll(v),s)}catch(w){}}return e(v,u,s,t)};for(var q in e){b[q]=e[q]}})()}if(document.getElementsByClassName&&document.documentElement.getElementsByClassName){(function(){var e=document.createElement("div");e.innerHTML="<div class='test e'></div><div class='test'></div>";if(e.getElementsByClassName("e").length===0){return}e.lastChild.className="e";if(e.getElementsByClassName("e").length===1){return}f.order.splice(1,0,"CLASS");f.find.CLASS=function(q,r,s){if(typeof r.getElementsByClassName!=="undefined"&&!s){return r.getElementsByClassName(q[1])}}})()}function m(q,v,u,z,w,y){var x=q=="previousSibling"&&!y;for(var s=0,r=z.length;s<r;s++){var e=z[s];if(e){if(x&&e.nodeType===1){e.sizcache=u;e.sizset=s}e=e[q];var t=false;while(e){if(e.sizcache===u){t=z[e.sizset];break}if(e.nodeType===1&&!y){e.sizcache=u;e.sizset=s}if(e.nodeName===v){t=e;break}e=e[q]}z[s]=t}}}function p(q,v,u,z,w,y){var x=q=="previousSibling"&&!y;for(var s=0,r=z.length;s<r;s++){var e=z[s];if(e){if(x&&e.nodeType===1){e.sizcache=u;e.sizset=s}e=e[q];var t=false;while(e){if(e.sizcache===u){t=z[e.sizset];break}if(e.nodeType===1){if(!y){e.sizcache=u;e.sizset=s}if(typeof v!=="string"){if(e===v){t=true;break}}else{if(b.filter(v,[e]).length>0){t=e;break}}}e=e[q]}z[s]=t}}}var h=document.compareDocumentPosition?function(q,e){return q.compareDocumentPosition(e)&16}:function(q,e){return q!==e&&(q.contains?q.contains(e):true)};var n=function(e){return e.nodeType===9&&e.documentElement.nodeName!=="HTML"||!!e.ownerDocument&&e.ownerDocument.documentElement.nodeName!=="HTML"};var g=function(e,w){var s=[],t="",u,r=w.nodeType?[w]:w;while((u=f.match.PSEUDO.exec(e))){t+=u[0];e=e.replace(f.match.PSEUDO,"")}e=f.relative[e]?e+"*":e;for(var v=0,q=r.length;v<q;v++){b(e,r[v],s)}return b.filter(t,s)};window.Sizzle=b})();

window.$$ = Sizzle

// Element.prototype.getElementsByClassName=function(c){return $$('.'+c, this)}

if (!document.getElementsByClassName)
{
	// from prototype 1.5.1.1
	Element.prototype.getElementsByClassName = document.getElementsByClassName = function (className, tagName)
	{
		// geting elems with native function
		var children = this.getElementsByTagName(tagName || '*')
		// predeclaring vars
		var elements = [], child, l = 0
		// precompile regexp
		var rex = new RegExp("(?:\\s+|^)" + className + "(?:\\s+|$)")
		// length is constant, so caching its value
		var len = children.length
		
		// memory for array of nodes will be allocated only once
		elements.length = len
		for (var i = 0; i < len; i++)
		{
			// even caching the reference for children[i] gives us some nanoseconds ;)
			child = children[i]
			// just rely on RegExp engine
			if (rex.test(child.className))
			{
				// simple assignment
				elements[l] = child
				// simple increment
				l++
			}
		}
		// truncating garbage length
		elements.length = l
		
		return elements
	}
}


// Class
;(function(){

var myName = 'Class'
var Me = self[myName] = function (name, sup)
{
	var klass = function ()
	{
		this.constructor = klass//arguments.callee
		this.initialize.apply(this, arguments)
		// try { this.initialize.apply(this, arguments) }
		// catch (ex) { throw new Error(name + ': ' + ex.message) }
	}
	
	klass.className = name || '[anonimous ' + myName + ']'
	klass.prototype = new (sup || Me.Object)()
	klass.constructor = Me
	
	return klass
}

Me.className = myName
Me.Object = function () {}
Me.Object.prototype =
{
	initialize: function () {},
	extend: function (s) { if (s) for (var p in s) this[p] = s[p]; return this }
}

// Me.supercall(this, 'initialize', [arg1, arg2, arg3...])
Function.prototype.supercall = function (o, n, a) { this.prototype.constructor.prototype[n].apply(o, a) }

})();



// Module
;(function(){

var myName = 'Module'
var Me = self[myName] = function (name, proto)
{
	var module = function () { throw new Error(myName + ': can`t create direct instances of myself') }
	module.className = name || '[anonimous ' + myName + ']'
	module.constructor = Me
	module.mix = function (cls) { Object.extend(cls.prototype, this.prototype); return this }
	
	if (proto)
		module.prototype = proto
	return module
}

Me.className = myName

Function.prototype.mixIn = function (module)
{
	if (module.constructor != Me)
		throw new Error('Function: can only mixIn modules')
	return module.mix(this)
}

})();

;(function(){

var myName = 'EventDriven',
	Me = self[myName] = Module(myName),
	MyEvent = Me.Event = Class(myName + '.Event')

MyEvent.prototype = 
{
	eventPhase: 2,
	bubbles: false,
	cancelable: true,
	
	initialize: function (target)
	{
		this.timeStamp = +new Date()
		this.currentTarget = this.target = target
	},
	
	stopPropagation: function () {},
	preventDefault: function () { this._preventedDefault = true },
	initEvent: function () {}
}

// var cache = Me.cache = {}

function uselessDispatchEvent () { return true }

function usefullDispatchEvent (e, data)
{
	var event = new MyEvent(this)
	
	if (typeof e == 'string')
	{
		event.type = e
		event.data = data || {}
	}
	else
		Object.extend(event, e)
	
	var handlers, harr, i, ret = true
	if (handlers = this.__EventDrivenHandlers)
		if (harr = handlers[event.type])
			for (i = 0, len = harr.length; i < len; i++)
				harr[i].call(this, event)
	
	return !event._preventedDefault
}

// event = document.createEvent("Event")
// event.initEvent(type, bubbles, cancelable)
// node.dispatchEvent(event)

Me.prototype =
{
	addEventListener: function (type, listener, capture)
	{
		var handlers, harr
		if (handlers = this.__EventDrivenHandlers)
		{
			if (harr = handlers[type])
				harr.indexOf(listener) < 0 ? harr.push(listener) : 1
			else
				handlers[type] = [listener]
		}
		else
			(this.__EventDrivenHandlers = {})[type] = [listener]
		
		this.dispatchEvent = usefullDispatchEvent
	},

	removeEventListener: function (type, listener, capture)
	{
		var handlers, harr, i, k, count
		if (handlers = this.__EventDrivenHandlers)
			if (harr = handlers[type])
			{
				if ((i = harr.indexOf(listener)) >= 0)
				{
					for (len = harr.length; i < len; i++)
						harr[i] = harr[i + 1]
					harr.length--
					
					count = 0
					for (k in handlers)
						count += handlers[k].length
					
					if (!count)
						this.dispatchEvent = uselessDispatchEvent
				}
			}
	},
	
	dispatchEvent: uselessDispatchEvent
}

})();


// based on json2 (http://www.JSON.org/json2.js)
// WARNING: this is not JSON library, it is not secure, it is for internal use only
// if you do not understand the difference please use original json2.js library

;(function ()
{
	// Format integers to have at least two digits.
	function f (n) { return n < 10 ? '0' + n : n }
	
	var escapeable = new RegExp('[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]', 'g'),
	bareword = new RegExp('^[\$_a-zA-Z][\$_a-zA-Z0-9]*$'),
	Object_hasOwnProperty = Object.hasOwnProperty,
	Date_constructor = Date,
	String_constructor = String,
	meta =
	{
		'\b': '\\b',
		'\t': '\\t',
		'\n': '\\n',
		'\f': '\\f',
		'\r': '\\r',
		'"' : '\\"',
		'\\': '\\\\'
	}
	
	function quoteReplacer (a)
	{
		return meta[a] || '\\u' + ('0000' + (+(a.charCodeAt(0))).toString(16)).slice(-4)
	}
	
	function quote (string)
	{
		escapeable.lastIndex = 0
		return '"' + string.replace(escapeable, quoteReplacer) + '"'
	}
	
	
	function str (value)
	{
		var i, k, v, length
		
		switch (typeof value)
		{
			case 'string':
				return quote(value)
			
			case 'number':
			case 'boolean':
			case 'null':
				return String_constructor(value)
			
			// Objects
			case 'object':
				// Null
				if (!value)
					return 'null'
				
				var partial = [], partial_length = 0
				
				// Array
				if (typeof value.length === 'number' && !(value.propertyIsEnumerable('length')))
				{
					length = value.length
					if (length === 0)
						return '[]'
					
					for (i = 0; i < length; i += 1)
						partial[i] = str(value[i])
					
					return '[' + partial.join(',') + ']'
				}
				
				// Date
				if (value.constructor === Date_constructor)
					return value.getUTCFullYear() + '-' +
							f(value.getUTCMonth() + 1) + '-' +
							f(value.getUTCDate()) + 'T' +
							f(value.getUTCHours()) + ':' +
							f(value.getUTCMinutes()) + ':' +
							f(value.getUTCSeconds()) + 'Z';
				
				// Plain object
				for (k in value)
					if (Object_hasOwnProperty.call(value, k))
						partial[partial_length++] = (bareword.test(k) ? k : quote(k)) + ':' + str(value[k])
				
				return partial_length === 0 ? '{}' : '{' + partial.join(',') + '}'
		}
	}
	
	function parse (code)
	{
		parse.lastError = null
		try { return eval('(' + code + ')') }
		catch(ex) { log(ex); parse.lastError = ex; return null }
	}
	
	Object.stringify = str
	Object.parse = parse
	
}) ();

;(function(){

var myName = 'Cookie', day = 864e5, doc = document,
	// encode = encodeURIComponent, decode = decodeURIComponent
	encode = escape, decode = unescape

if (!self[myName]) self[myName] =
{
	days: 30,
	path: '/',
	set: function (name, value, days, path)
	{
		var d = new Date()
		days = days || this.days
		path = path || this.path
		
		d.setTime(d.getTime() + day * days)
		doc.cookie = encode(name) + '=' + encode(this.stringify(value)) + '; expires=' + d.toGMTString() + '; path=' + path
		return value
	},
	
	get: function (name)
	{
		var value, cookie = new RegExp('(^|;)\\s*' + encode(name) + '=([^;\\s]*)').exec(doc.cookie)
		return cookie ? this.parse(decode(cookie[2])) : null
	},
	
	erase: function (name)
	{
		var cookie = this.get(name) || true
		this.set(name, '', -1)
		return cookie
	},
	
	keys: function ()
	{
		log(doc.cookie)
		
		var cookie, keys = [], rex = new RegExp('(?:^|;)\\s*([^=]+)=[^;\\s]*', 'g')
		while (cookie = rex.exec(doc.cookie))
			keys.push(cookie[1])
		
		return keys
	},
	
	clear: function ()
	{
		var keys = this.keys()
		for (var i = 0; i < keys.length; i++)
			this.erase(keys[i])
	},
	
	stringify: function (value) { return value },
	parse: function (value) { return value }
}

})();
;(function(){

Cookie.stringify = Object.stringify
Cookie.parse = Object.parse

// // tests
// var val = 123
// alert(Cookie.set('aaa', val) === val && Cookie.get('aaa') === val)
// var val = "123"
// alert(Cookie.set('aaa', val) === val && Cookie.get('aaa') === val)

})();
;(function(){

var myName = 'UrlEncode',
	Me = self[myName] =
{
	paramDelimiter: '&',
	
	parse: function (string, forceArray)
	{
		var res = {}
	
		var parts = String(string).split(/[;&]/)
		for (var i=0; i < parts.length; i++)
		{
			var pair = parts[i].split('=')
			var name = decodeURIComponent(pair[0])
			var val = decodeURIComponent(pair[1] || '')
		
			if (forceArray)
			{
				if (res[name])
					res[name].push(val)
				else
					res[name] = [val]
			}
			else
			{
				if (res[name])
				{
					if (typeof res[name] == 'array')
						res[name].push(val)
					else
						res[name] = [res[name], val]
				}
				else
					res[name] = val
			}
		}
	
		return res
	},
	
	stringify: function (data)
	{
		var enc = encodeURIComponent,
			pd = Me.paramDelimiter
		
		if (!data)
			return ''
		
		if (typeof data.toUrlEncode == 'function')
			return data.toUrlEncode()
		
		switch (data.constructor)
		{
			case Array:
				return data.join(pd)
			
			case Object:
				var arr = []
				for (var i in data)
					if (i !== undefined && i != '')
					{
						var val = data[i]
						var enci = enc(i)
						if (val !== undefined && val !== null)
							switch (val.constructor)
							{
								case Array:
									for (var j = 0, jl = val.length; j < jl; j++)
										arr.push(enci + "=" + enc(val[j]))
									break
								default:
									arr.push(enci + "=" + enc(val))
									break
							}
					}
				return arr.join(pd)
			
			default:
				return enc(data)
		}
	}
}

})();
;(function(){

var Me = Programica.Request = {},
	XHR = XMLHttpRequest,
	statusEventNames = ['success', 'information', 'success', 'redirect', 'error', 'error'],
	urlEncode = UrlEncode.stringify

// EventDriven.mix(XHR)

function onreadystatechange ()
{
	if (this.readyState == 4)
	{
		var status = Math.floor(this.status / 100),
			type = statusEventNames[status]
		
		this.dispatchEvent({type: 'load', request: this})
		
		if (type)
			this.dispatchEvent({type: type, request: this})
		else
			throw new Error("wrong response status: " + this.status)
	}
}

Object.extend(XHR, {UNSENT: 0, OPENED: 1, HEADERS_RECEIVED: 2, LOADING: 3, DONE: 4})

Me.onreadystatechange = onreadystatechange

Me.post = function (url, params, callback, sync)
{
	var r = new XHR(),
		data = urlEncode(params)
	
	r.open('POST', url, !sync)
	r.setRequestHeader('Content-type', 'application/x-www-form-urlencoded') // ; charset=utf-8
	// r.setRequestHeader('Content-length', data.length)
	Object.extend(r, EventDriven.prototype) // dirty fix for FF 3.5
	r.onreadystatechange = function () { return onreadystatechange.apply(r, arguments) }
	if (callback)
		r.addEventListener('load', callback, false)
	r.send(data)
	if (sync)
		onreadystatechange.apply(r)
	
	return r
}

Me.get = function (url, params, callback, sync)
{
	var r = new XHR()
	
	if (params)
		url += '?' + urlEncode(params)
	
	r.open('GET', url, !sync)
	r.onreadystatechange = onreadystatechange
	if (callback)
		r.addEventListener('load', callback, false)
	r.send(null)
	if (sync)
		onreadystatechange.apply(r)
	
	return r
}

var $ = self.$
if ($)
{
	$.post = Me.post
	$.get = Me.get
}


})();
;(function(){

var R = RegExp, rexCache = {}

Object.add
(
	Element.prototype,
	{
		setClassName: function (cn)
		{
			this.className = cn
			return cn
		},
		
		addClassName: function (cn)
		{
			// this.removeClassName(cn)
			this.className += ' ' + cn
			return cn
		},
		
		removeClassName: function (cn)
		{
			var className = this.className
			if (className)
				this.className = className.replace(rexCache[cn] || (rexCache[cn] = new R('(?:^| +)(?:' + cn + '(?:$| +))+', 'g')), ' ')
								.replace(/^\s+|\s+$/g, '')
			return cn
		},
		
		hasClassName: function (cn)
		{
			var className = this.className
			return (className == cn || (rexCache[cn] || (rexCache[cn] = new R('(?:^| +)(?:' + cn + '(?:$| +))+'))).test(className))
		},
		
		empty: function ()
		{
			var node
			while (node = this.firstChild)
				this.removeChild(node)
		},
		
		hide: function () { this.addClassName('hidden') },
		show: function () { this.removeClassName('hidden') },
		
		remove: function ()
		{
			var parent = this.parentNode
			return parent ? parent.removeChild(this) : this
		},
		
		isParent: function (parent, root)
		{
			var node = this
			do
			{
				if (node === parent)
					return true
				if (node === root)
					break
			}
			while ((node = node.parentNode))
			
			return false
		}
	}
)

})();
;(function () {

var PA = Programica.Animation = function (prms)
{
	prms = prms || {}

	// defaults
	this.trans	= []
	this.duration			= prms.duration || 1,
	this.unit				= prms.unit != null ? prms.unit : PA.defaults.unit
	this.motion				= prms.motion || PA.defaults.type
	this.running			= false
	this.complete			= false
	this.node				= prms.node || false
	this.animationTypes		= PA.Types
	
	this.onstart = this.oncomplete = this.onstep = function () {}
	
	switch (typeof this.motion)
	{
		case 'string':
			this.motion = this.animationTypes[this.motion] || null
			break
		case 'function':
			break
		default:
			this.motion = null
	}
	
	if (prms.trans)
		for (var i = 0; i < prms.trans.length; i++)
			if (prms.trans[i])
				this.trans.push(prms.trans[i])
}

Element.prototype.animate = function (motion, props, duration, unit)
{
	var trans = []
	for (var i in props)
	{
		var pi = props[i]
		if (pi.length == 2)
			trans.push({property: i, begin: pi[0], end: pi[1]})
		else
			trans.push({property: i, begin: null, end: pi[0] || pi})
	}
	return new PA({node: this, motion: motion, duration: duration, trans: trans, unit: unit}).start()
}


PA.fps = 60
PA.defaults = {unit: 'px', type: 'linearTween'}

PA.prototype =
{
	start: function ()
	{
		// if animation is already started
		if (this.running)
			return this
		
		// only one animation per node for now
		if (this.node.animation)
			this.node.animation.stop()
		this.node.animation = this
		
		this.running = true
		this.complete = false
		
		var t = this
		if (this.motion == this.animationTypes.directJump)
		{
			this.frame = this.totalFrames - 1
			setTimeout(function () { t.renderForceLast(); t.stop(); t.complete = true; t.oncomplete() }, 0)
		}
		else
		{
			this.frame = 0
			this.totalFrames = this.duration * PA.fps
			
			for (var i = 0; i < this.trans.length; i++)
			{
				var tr = this.trans[i]
				if (tr.begin == null)
					tr.begin = this.getStyleProperty(tr.property)
				tr.step = ( tr.end - tr.begin ) / this.totalFrames
			}
			
			this.timer = PA.addTimer( function () { t.step() } )
		}
		
		this.onstart()
		return this
	},
	
	stop: function ()
	{
		if (this.running)
		{
			this.node.animation = null
			this.running = false
			PA.removeTimer(this.timer)
		}
		
		return this
	},
	
	step: function ()
	{
		this.render()
		this.onstep()
		
		if (this.frame >= this.totalFrames - 1)
		{
			this.stop()
			this.complete = true
			this.oncomplete()
		}
		
		this.frame++
	},
	
	renderForceLast: function ()
	{
		for (var i = 0; i < this.trans.length; i++)
		{
			var t = this.trans[i]
			this.setStyleProperty(t.property, t.end)
		}
	},
	
	render: function ()
	{
		for (var i = 0; i < this.trans.length; i++)
		{
			var t = this.trans[i]
			if ( t.property != null && t.begin != null && t.end != null  )
				this.setStyleProperty(t.property, this.motion(this.frame + 1, t.begin, t.end - t.begin, this.totalFrames))
			else
				throw new Error("Corupted transformation: " + t)
		}
	},

	getStyleProperty: function (p)
	{
		if (p == "top" && !this.node.style[p])
			return this.node.offsetTop
		
		if (p == "left" && !this.node.style[p])
			return this.node.offsetLeft
		
		
		if (p == "opacity" && isNaN(parseFloat(this.node.style[p])))
			return 1
		
		
		if (/scroll/.test(p))
			return this.node[p]
		
		return parseFloat(this.node.style[p]) || 0
	},
	
	setStyleProperty: function (p, value)
	{
		try
		{
			if (/color/.test(p))
				return this.node.style[p] = 'rgb(' + parseInt(value) + ',' + parseInt(value) + ',' + parseInt(value) + ')'
			
			// for SVG elements
			if (p == 'r')
				return this.node.r.baseVal.value = value
			
			
			if (/scroll/.test(p))
				return this.node[p] = Math.round(value)
			
			if (p == "opacity")
				return this.node.style[p] = value
			
			if ((p == 'width' || p == 'height') && value < 0)
				value = 0
			
			if (this.unit == 'em')
				return this.node.style[p] = Math.round(value * 100) / 100 + this.unit
			
			return this.node.style[p] = Math.round(value) + this.unit
		}
		catch (ex)
		{
			log(ex + p + value)
			return value
		}
	}
}

PA.addTimer = function (func)
{
	if (!this.timer)
	{
		var t = this
		this.timer = setInterval(function (d) { t.time(d) }, 1000 / this.fps)
	}
	
	return this.timers.push(func)
}

PA.removeTimer = function (num)
{
	var ts = this.timers
	ts[num-1] = null
	
	while (ts[ts.length-1] === null)
		ts.length--
	
	if (!ts.length)
		clearInterval(this.timer), this.timer = null
}

PA.timers = []

PA.time = function (d)
{
	for (var i = 0; i < this.timers.length; i++)
		this.timers[i] && this.timers[i](d)
}


;(function(){var M=Math;var e=M.cos;var f=M.sin;var g=M.sqrt;var h=M.PI;var i=M.pow;var j=M.abs;var k=M.asin;var l=Programica.Animation.Types={directJump:function(t,b,c,d){},linearTween: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){return((t/=d/2)<1)?c/2*t*t+b:-c/2*((--t)*(t-2)-1)+b},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){return((t/=d/2)<1)?c/2*t*t*t+b:c/2*((t-=2)*t*t+2)+b},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){return((t/=d/2)<1)?c/2*t*t*t*t+b:-c/2*((t-=2)*t*t*t-2)+b},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){return((t/=d/2)<1)?c/2*t*t*t*t*t+b:c/2*((t-=2)*t*t*t*t+2)+b},easeInSine:function(t,b,c,d){return-c*e(t/d*(h/2))+c+b},easeOutSine:function(t,b,c,d){return c*f(t/d*(h/2))+b},easeInOutSine:function(t,b,c,d){return-c/2*(e(h*t/d)-1)+b},easeInExpo:function(t,b,c,d){return(t==0)?b:c*i(2,10*(t/d-1))+b},easeOutExpo:function(t,b,c,d){return(t==d)?b+c:c*(-i(2,-10*t/d)+1)+b},easeInCirc:function(t,b,c,d){return-c*(g(1-(t/=d)*t)-1)+b},easeOutCirc:function(t,b,c,d){return c*g(1-(t=t/d-1)*t)+b},easeInOutCirc:function(t,b,c,d){return((t/=d/2)<1)?-c/2*(g(1-t*t)-1)+b:c/2*(g(1-(t-=2)*t)+1)+b},easeInBounce:function(t,b,c,d){return c-l.easeOutBounce(d-t,0,c,d)+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*i(2,10*(t-1))+b;return c/2*(-i(2,-10*--t)+2)+b},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;if(a<j(c)){var s=p/4;a=c}else var s=p/(2*h)*k(c/a);return-(a*i(2,10*(t-=1))*f((t*d-s)*(2*h)/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;if(a<j(c)){var s=p/4;a=c}else var s=p/(2*h)*k(c/a);return a*i(2,-10*t)*f((t*d-s)*(2*h)/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);if(a<j(c)){a=c;var s=p/4}else var s=p/(2*h)*k(c/a);if(t<1)return-.5*(a*i(2,10*(t-=1))*f((t*d-s)*(2*h)/p))+b;else return a*i(2,-10*(t-=1))*f((t*d-s)*(2*h)/p)*.5+c+b},easeInBack:function(t,b,c,d,s){if(s==null)s=1.70158;return c*(t/=d)*t*((s+1)*t-s)+b},easeOutBack:function(t,b,c,d,s){if(s==null)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==null)s=1.70158;if((t/=d/2)<1)return c/2*(t*t*(((s*=(1.525))+1)*t-s))+b;else return c/2*((t-=2)*t*(((s*=(1.525))+1)*t+s)+2)+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 l.easeInBounce(t*2,0,c,d)*.5+b;else return l.easeOutBounce(t*2-d,0,c,d)*.5+c*.5+b}}})();

})();

// based on json2 (http://www.JSON.org/json2.js)
// WARNING: this is not JSON library, it is not secure, it is for internal use only
// if you do not understand the difference please use original json2.js library

;(function ()
{
	// Format integers to have at least two digits.
	function f (n) { return n < 10 ? '0' + n : n }
	
	var escapeable = new RegExp('[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]', 'g'),
	bareword = new RegExp('^[\$_a-zA-Z][\$_a-zA-Z0-9]*$'),
	Object_hasOwnProperty = Object.hasOwnProperty,
	Date_constructor = Date,
	String_constructor = String,
	meta =
	{
		'\b': '\\b',
		'\t': '\\t',
		'\n': '\\n',
		'\f': '\\f',
		'\r': '\\r',
		'"' : '\\"',
		'\\': '\\\\'
	}
	
	function quoteReplacer (a)
	{
		return meta[a] || '\\u' + ('0000' + (+(a.charCodeAt(0))).toString(16)).slice(-4)
	}
	
	function quote (string)
	{
		escapeable.lastIndex = 0
		return '"' + string.replace(escapeable, quoteReplacer) + '"'
	}
	
	
	function str (value)
	{
		var i, k, v, length
		
		switch (typeof value)
		{
			case 'string':
				return quote(value)
			
			case 'number':
			case 'boolean':
			case 'null':
				return String_constructor(value)
			
			// Objects
			case 'object':
				// Null
				if (!value)
					return 'null'
				
				var partial = [], partial_length = 0
				
				// Array
				if (typeof value.length === 'number' && !(value.propertyIsEnumerable('length')))
				{
					length = value.length
					if (length === 0)
						return '[]'
					
					for (i = 0; i < length; i += 1)
						partial[i] = str(value[i])
					
					return '[' + partial.join(',') + ']'
				}
				
				// Date
				if (value.constructor === Date_constructor)
					return value.getUTCFullYear() + '-' +
							f(value.getUTCMonth() + 1) + '-' +
							f(value.getUTCDate()) + 'T' +
							f(value.getUTCHours()) + ':' +
							f(value.getUTCMinutes()) + ':' +
							f(value.getUTCSeconds()) + 'Z';
				
				// Plain object
				for (k in value)
					if (Object_hasOwnProperty.call(value, k))
						partial[partial_length++] = (bareword.test(k) ? k : quote(k)) + ':' + str(value[k])
				
				return partial_length === 0 ? '{}' : '{' + partial.join(',') + '}'
		}
	}
	
	function parse (code)
	{
		parse.lastError = null
		try { return eval('(' + code + ')') }
		catch(ex) { log(ex); parse.lastError = ex; return null }
	}
	
	Object.stringify = str
	Object.parse = parse
	
}) ();



// 1, 2, 5: банкир, банкира, банкиров
String.prototype.plural = Number.prototype.plural = function (a, b, c)
{
	if (this % 1)
		return b
	
	var v = Math.abs(this) % 100
	if (11 <= v && v <= 19)
		return c
	
	v = v % 10
	if (2 <= v && v <= 4)
		return b
	if (v == 1)
		return a
	
	return c
}

Date.rusMonths = ['Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь']
Date.rusMonths2 = ['января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря']
Date.prototype.toRusDate = function () { return this.getDate() + ' ' + Date.rusMonths2[this.getMonth()] + ' ' + this.getFullYear() }



;(function(){

// fast and easy but looks terrible
function parseOutJS (src)
{
	// this trick is for IE only
	src = src.split('')
	var i = 0, len = src.length, c,
		res = [], buff = '', open
	
	// string
	while (i < len)
	{
		c = src[i++]
		// backslash
		if (c === '\\') { buff += src[i++]; continue }
		
		// maybe code
		if (c === '$')
		{
			c = src[i++]
			// block
			if (c === '{')
			{
				res.push(buff)
				buff = ''
				open = 1
				while (i < len)
				{
					c = src[i++]
					// string ""
					if (c === '"')
					{
						buff += c
						while (i < len)
						{
							c = src[i++]
							// backslash
							if (c === '\\') { buff += src[i++]; continue }
							// end of string ""
							if (c === '"') { break }
							buff += c
						}
					}
					// string ''
					if (c === "'")
					{
						buff += c
						while (i < len)
						{
							c = src[i++]
							// backslash
							if (c === '\\') { buff += src[i++]; continue }
							// end of string ""
							if (c === "'") { break }
							buff += c
						}
					}
					// block
					if (c === '{') { open++ }
					// end of block
					else if (c === '}' && !--open) { res.push(buff); buff = ''; break}
					 
					// if (c === '\\') { i++; continue }
					buff += c
				}
			}
			// just one dollar ;)
			else
				buff += '$' + c
		}
		else
			buff += c
	}
	
	res.push(buff)
	
	return res
}

// bake() is used to increase compression level by placing nomungeble variables in one closure
function bake ($_$h, $_$s, $_$o)
{
	"$_$h:nomunge, $_$s:nomunge, $_$o:nomunge"
	return eval('(0, function($_$h){with($_$h){return ' + $_$o.join('+') + '}})')
}

var cache = {}
function interpolateJS (h)
{
	var i, b, s, o, f = cache[this]
	if (!f)
	{
		b = parseOutJS(this), s = [], o = []
		
		if (b[0])
			s[0] = b[0], o[0] = '($_$s[0])'
			
		
		for (i = 1; i < b.length; i+=2)
		{
			s.push(b[i+1])
		
			o.push('(' + b[i] + ')')
			o.push('$_$s[' + (i + 1)/2 + ']')
		}
		
		if (!b[b.length-1])
			o.length--
		f = cache[this] = bake(h, s, o)
	}
	try { return f.apply(this, arguments) }
	catch (ex) { reportError(ex); return null }
}

interpolateJS.cache = cache
String.prototype.interpolateJS = interpolateJS
String.parseOutJS = parseOutJS

// alert('1${a}2${b}3${c}4'.interpolateJS({a:'A',b:'B',c:'C'}))
// '${a{"a{\\\'a}\\"}a"b}c} wor$ld ${n{{{"\'"}}}n\'n\'}'.interpolateJS()

// // test for parseOutJS
// var blocks = parseOutJS('"hello" \\${x} ${a{"a{\\\'a}\\"}a"b}c} wor$ld ${n{{{"\'"}}}n\'n\'}')
// log(blocks[0] === '"hello" ${x} ')
// log(blocks[1] === 'a{"a{\'a}"}a"b}c')
// log(blocks[2] === ' wor$ld ')
// log(blocks[3] === 'n{{{"\'"}}}n\'n\'')
// log(blocks[4] === '')
// log(blocks[5] === undefined)

})();


// prototypes extensions
;(function(){

String.prototype.pluralEx = Number.prototype.pluralEx = function (arr)
{
	if (!arr)
		arr = []
	return this.plural.call(this, arr[0], arr[1], arr[2])
}

// \{ and \} is for the f*cking Opera 9.2
String.prototype.interpolate = function (hash) { return this.replace(/\$\{(\w+)\}/g, function (a, b) { return hash[b] }) }

// not so fast but very usefull
String.prototype.interpolateEx = function (hash)
{
	function interpolate (m, v, x, f, p)
	{
		var val = hash
		var membs = v.split('.')
		for (var i = 0, il = membs.length; i < il; i++)
			val = val[membs[i]]
		
		if (f && p && val !== undefined)
		{
			f = val[f]
			if (typeof f == 'function')
			{
				x = []
				// log(p)
				p.replace(/\s*((\d+)|("((\\"|.)*?)")|([^,]+))\s*,?/g, function (m, a, n, b, v, c, w) { /*log(typeof n);*/ x.push((typeof n !== 'nuber') ? String(v || w).replace(/\\(.)/g, '$1') : +n) })
				// log(x)
				val = f.apply(val, x)
			}
			else
				throw new Error('interpolateEx: typeof String#' + f + ' is not a function')
		}
		return val
	}
	return this.replace(/\$\{([\w\.]+)(\.(\w+)\(((\\\)|.)*?)\))?\}/g, interpolate)
}



Array.prototype.joinAnd = function (sep, and)
{
	if (this.length > 1)
	{
		var last = this.pop()
		return this.join(sep) + and + last
	}
	else
	 return this.join(sep)
}

var fetchPathCache = {}
Array.prototype.fetchPath = function (path)
{
	var f = fetchPathCache[path]
	if (!f)
	{
		var code = '(0,function(a){var u,r=[];for(var i=0,l=a.length;i<l;i++){try{r[i]=a[i]'+path+'}catch(x){r[i]=u};}return r})'
		f = fetchPathCache[path] = eval(code)
	}
	
	return f(this)
}

Array.prototype.filt = function(f)
{
	var len = this.length,
		res = [], l = 0
	for (var i = 0; i < len; i++)
		if (i in this && f.call(this, this[i], i))
			res[l++] = this[i]
	return res
}


Array.prototype.quniq = function ()
{
	var seen = {}, res = []
	for (var i = 0, l = this.length; i < l; i++)
	{
		if (i in this)
		{
			var val = this[i]
			if (!seen[val])
			{
				res.push(val)
				seen[val] = true
			}
		}
	}
	
	return res
}

Array.prototype.uniq = function ()
{
	var i, l, v, r = []
	for (i = 0, l = this.length; i < l; i++)
	{
		if (i in this)
		{
			v = this[i]
			if (r.indexOf(v) === -1)
				r.push(v)
		}
	}
	
	return r
}

// var a = []
// a[0] = a[3] = 1
// a = a.uniq()
// log(a.length == 1 && a[0] === 1)

Array.prototype.minus = function (arr)
{
	var res = []
	for (var i = 0, l = this.length; i < l; i++)
	{
		var val = this[i]
		if (arr.indexOf(val) < 0)
			res.push(val)
	}
	
	return res
}

String.prototype.tr = function (src, rep)
{
	var res = []
	for (var i = 0; i < this.length; i++)
	{
		var ch = this.charAt(i)
		var pos = src.indexOf(ch)
		res[i] = pos >= 0 ? rep[pos] : ch
	}
	
	return res.join('')
}

String.transtypeStrings =
{
	en: 'qwertyuiop[]asdfghjkl;\'\\zxcvbnm,./',
	ru: 'йцукенгшщзхъфывапролджэёячсмитьбю/'
}

String.prototype.transtype = function (from, to)
{
	var strings = String.transtypeStrings
	return this.tr(strings[from], strings[to])
}

Date.monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
Date.fromYMD = function (y, m, d) { return new Date(this.monthNames[m] + ' ' + d + ' ' + y + ' 00:00:00') }
var myIsNaN = isNaN
Date.prototype.isValid = function () { return !myIsNaN(+this) }

Element.prototype.toggleClassName = function (cn, state)
{
	if (arguments.length < 2)
		state = !this.hasClassName(cn)
	
	this.removeClassName(cn)
	if (state) this.addClassName(cn)
}

Cookie.version = 1

Cookie.check = function ()
{
	var version = this.version
	if (Cookie.get('V') < version)
		Cookie.clear()
	return Cookie.set('V', version)
}

})();

;(function(){

var myName = 'Stateful',
	Me = self[myName] = Module(myName)

Me.prototype =
{
	stateChanged: function (state) { log('please, define a useful stateChanged method in ' + this.constructor.className) },
	mergeState: function (state) { log('please, define a useful mergeState method in ' + this.constructor.className) },
	
	initStateful: function ()
	{
		this.state = {}
	},
	
	bindStateful: function (saver)
	{
		this.stateSaver = saver
		var me = this
		saver.addEventListener('change', function (e) { me.setState(me.loadState()) }, false)
		
		var state = {}
		Object.extend(state, this.defaultState)
		Object.extend(state, this.loadState())
		this.setState(state)
	},
	
	saveState: function ()
	{
		// what has changed since default state
		var diff = this.diffState(this.defaultState, this.state)
		
		// // if nothing then holding still
		// if (Object.keys(diff) == 0)
		// 	return
		
		// saving the difference to the saver
		if (this.stateSaver)
			this.stateSaver.set(UrlEncode.stringify(diff))
	},
	
	loadState: function (value)
	{
		return UrlEncode.parse(this.stateSaver.get())
	},
	
	setState: function (next)
	{
		var diff = this.diffState(this.state, next)
		this.mergeState(diff, next)
	},
	
	diffState: function (a, b)
	{
		var d = {}
		
		for (var k in b)
			if (!(k in a) || a[k] !== b[k])
				d[k] = b[k]
		
		return d
	}
}

})();

;(function(){

var myName = 'LocationHash',
	Me = self[myName] = function () {}

Me.prototype =
{
	interval: 250,
	bind: function (location)
	{
		this.location = location
		this.lastHash = this.location.hash
		this.last = this.get()
		
		var me = this
		function check ()
		{
			// stop checking while are waiting to set timer
			if (this.setting)
				return
			
			if (me.location.hash != me.lastHash)
			{
				me.lastHash = me.location.hash
				me.dispatchEvent({type:'change', value:me.get(), last:me.last})
			}
		}
		
		this.checkTimer = setInterval(check, this.interval)
		document.addEventListener('click', function () { setTimeout(check, 10) }, true)
	},
	
	set: function (val)
	{
		val = String(val)
		if (this.last === val)
			return
		
		this.last = val
		
		this.setting = true
		var me = this
		function set ()
		{
			var hash = '#' + me.last
			// change only if it differs
			if (me.location.hash == hash)
				return
			
			me.lastHash = me.location.hash = hash
			me.setting = false
		}
		clearTimeout(this.setTimer)
		// deferred hash writing (to prevent luggish behavior)
		this.setTimer = setTimeout(set, 10)
	},
	
	get: function ()
	{
		return this.location.hash.replace(/^#/, '')
	}
}

EventDriven.mix(Me)

})();
// MVC
;(function(){

var myName = 'MVC'
var ME = self[myName] = Class(myName)
ME.prototype.extend
({
	initialize: function ()
	{
		var model = this.model = new this.constructor.Model(),
			view = this.view = new this.constructor.View(),
			controller = this.controller = new this.constructor.Controller()
		
		model.view = controller.view = view
		view.controller = controller
		controller.model = model
		model.parent = view.parent = controller.parent = this
		
		this.bind.apply(this, arguments)
		
		return this
	},
	
	bind: function () {}
})

ME.Model = Class(myName + '.Model')
ME.View = Class(myName + '.View')
ME.Controller = Class(myName + '.Controller')

ME.create = function (name)
{
	var widget = Class(name, ME)
	widget.Model = Class(name + '.Model', this.Model)
	widget.View = Class(name + '.View', this.View)
	widget.Controller = Class(name + '.Controller', this.Controller)
	
	return widget
}

})();

;(function(){

var myName = 'LanguagePack'
var Me = self[myName] =
{
	code: 'ru',
	name: 'русский',
	nameEn: 'russian'
}

var weekDaysRP = ['в понедельник', 'во вторник', 'в среду', 'в четверг', 'в пятницу', 'в субботу', 'в воскресенье'],
	weekDaysIP = ['понедельник', 'вторник', 'среда', 'четверг', 'пятница', 'суббота', 'воскресение'],
	and = ' и '

Me.TimeClicker =
{
	weekDays: ['пн', 'вт', 'ср', 'чт', 'пт', 'сб', 'вс'],
	cornerTitle: 'очистить всё',
	cornerText: 'всё',
	selectHour: 'очистить/выбрать час',
	selectWeekday: 'очистить/выбрать день недели',
	emptyCell: 'нерабочее время',
	legend: {selected:'Могу работать', cell:'Не могу работать', empty:'Нерабочее время'},
	humanIn: 'в ${begin}',
	humanFromTo: 'с ${begin} до ${end}'
}

Me.TimeChecker =
{
	weekDays: weekDaysIP
}

Me.TimeChecker.Rules = {}

Me.TimeChecker.Rules.NHoursAWeek =
{
	error: 'Можно работать не менее ${limit} ${limit.plural("часа","часов","часов")} в неделю. Вы выбрали всего ${total} ${total.plural("час","часа","часов")}.',
	done: 'Могу работать ${total} ${total.plural("час","часа","часов")} в неделю.'
}

Me.TimeChecker.Rules.NDaysAWeek =
{
	error: 'Можно работать не менее ${limit} ${limit.plural("дня","дней","дней")} в неделю. Пожалуйста, выберите хотя бы еще ${diff} ${diff.plural("день","дня","дней")}.',
	done: 'Могу работать ${total} ${total.plural("день","дня","дней")} в неделю.'
}

Me.TimeChecker.Rules.NHoursADay =
{
	weekDays: weekDaysRP,
	error: 'Можно работать не менее ${limit} ${limit.plural("часа","часов","часов")} в день. Добавьте, пожалуйста, часы ${wrong.joinAnd(", ", " и ")}.',
	empty: 'Можно работать не менее ${limit} ${limit.plural("часа","часов","часов")} в день. Добавьте, пожалуйста, часы.',
	done: 'Могу работать не менее ${limit} ${limit.plural("часа","часов","часов")} в день.'
}

Me.TimeChecker.Rules.OnlyNSpaces =
{
	weekDays: weekDaysRP,
	error: 'Часы должны идти непрерывно, друг за другом (есть пробел ${wrong.joinAnd(", ", " и ")}).',
	done: 'Часы идут непрерывно, друг за другом.',
	empty: 'Не выбрано ни одного часа.'
}

Me.PopupUploader =
{
	errorText:
	{
		0: 'неопределенная ошибка',
		413: 'файл слишком большой',
		25871: 'файл не указан',
		unknown: 'безымянная ошибка'
	}
}

Me.FormPage =
{
	error: 'Введенные вами данные сохранены.\n\nВо время отправления анкеты произошла ошибка: ${message}. Пожалуйста, повторите отправку анкеты позже.'
}

Me.VacancySearcher =
{
	order: ['*', 'midnight', 'consultant', 'cashier', 'senior-cashier', 'senior-consultant', 'manager'],
	names: {'*': 'Все вакансии', midnight:'Ночная смена', consultant:'Продавец-консультант', 'senior-consultant':'Старший продавец', cashier:'Кассир', 'senior-cashier':'Старший кассир', manager:'Управляющий магазина'},
	namesVP: {'*': 'сотрудников', midnight:'сотрудников ночной смены', consultant:'продавцов-консультантов', 'senior-consultant':'старших продавцов', cashier:'кассиров', 'senior-cashier':'старших кассиров', manager:'управляющих магазинами'},
	status:
	{
		normal: 'Сегодня ${count} ${count.plural("известный бренд ищет","известных бренда ищут","известных брендов ищут")} ${position}.',
		no: 'Сегодня нет вакансий ${position}.',
		noatall: 'Сегодня закрыты все вакансии.',
		notselected: 'Выберите удобный адрес работы на карте и посмотрите открытые вакансии.',
		selected: 'По адресу «${address}» ${count.plural("открыта","открыты","открыто")} ${count} ${count.plural("вакансия","вакансии","вакансий")}.'
	}
}

;(function()
{
	Me.QuestionType = {}
	Me.QuestionType.types =
	{
		Map:
		{
			name: 'Адрес',
			empty: 'не указан',
			one: 'Мне удобно работать по адресу: ${address}',
			many: 'Мне удобно работать по любому из выбранных адресов.',
			error: 'Адреса на карте не выбраны. Пожалуйста, выберите удобные адреса.'
		},
		TimeClicker:
		{
			name: 'График работы',
			empty: 'не указан',
			errorStatus: 'Обратите внимание на условия гибкого графика работы.',
			okStatus: 'Спасибо, график выбран замечательный!'
		},
		FirstMiddleSecondName:
		{
			name: 'Меня зовут',
			second: 'Фамилия',
			first: 'Имя',
			middle: 'Отчество',
			hint: '(заполняйте только русскими буквами)',
			rex: /^\s*[А-Яа-я][А-Яа-я\- ]*$/,
			empty: 'не указано'
		},
		Name:
		{
			name: 'Меня зовут',
			tip: 'Иван Иванов',
			rex: /\S/,
			empty: 'не указано'
		},
		Nationality:
		{
			name: 'Мое гражданство',
			hint: '(только граждане России могут работать по этим адресам)',
			empty: 'не указано'
		},
		Birthday:
		{
			name: 'Дата моего рождения',
			tip: {day: 20, month: 11, year: 1990},
			hint: '(строго с 18 лет)',
			empty: 'не указана'
		},
		Metro:
		{
			name: 'Ближайшая станция метро',
			station: 'Маяковская',
			// rex: /^[А-Яа-яёЁ0-9ё- ]+$/ // to complex
			rex: /[А-Яа-я]+/,
			empty: 'не указана'
		},
		Experience:
		{
			name: 'Опыт',
			empty: 'не указан'
		},
		CheckList:
		{
			name: 'Выберите из списка',
			empty: 'вариант не выбран'
		},
		Text:
		{
			name: 'О себе',
			rex: /\S/,
			empty: 'пусто'
		},
		File:
		{
			name: 'Файл',
			upload: 'Загрузить…',
			errors:
			{
				0: 'Неопределенная ошибка.',
				413: 'Файл слишком большой.',
				500: 'Внутренняя ошибка сервера.',
				25871: 'Файл не указан.',
				unknown: 'Безымянная ошибка.'
			},
			empty: 'нет'
		},
		Photo:
		{
			name: 'Фотография',
			upload: 'Загрузить…',
			errors:
			{
				0: 'Неопределенная ошибка.',
				413: 'Файл слишком большой.',
				500: 'Внутренняя ошибка сервера.',
				25871: 'Файл не указан.',
				unknown: 'Безымянная ошибка.'
			},
			empty: 'нет'
		},
		Email:
		{
			name: 'Электронная почта',
			tip: 'moy@email.ru',
			rex: /\S+@[\w\-\.]+\.\w\w+/,
			empty: 'не указана'
		},
		Phone:
		{
			name: 'Телефон',
			countryCode: '8',
			tip: {code: '1234', number: '4567890'},
			codeRex: /\d{2,}/,
			numberRex: /\d{5,}/,
			codeMaxLength: 5,
			numberMaxLength: 7,
			empty: 'не указан'
		},
		MobilePhone:
		{
			name: 'Мобильный телефон',
			countryCode: '+7',
			tip: {code: '123', number: '4567890'},
			codeRex: /\d{3}/,
			numberRex: /\d{7}/,
			codeMaxLength: 3,
			numberMaxLength: 7,
			empty: 'не указан'
		},
		Submit:
		{
			submit: 'Отправить анкету'
		}
	}
}
)();

Me.Point =
{
	hot: 'Новый адрес!',
	closed: 'Сегодня вакансий нет.'
}

})();





// GoogleApiLoader
;(function(){

var myName = 'GoogleApiLoader'
var Me = self[myName] = Class(myName)

Me.mixIn(EventDriven)

Me.prototype.extend
({
	initialize: function (keys, host, language)
	{
		this.host = /[^.]+\.[^.]+$/i.exec(host || location.host)
		this.state = 0
		this.apis = []
		this.language = language || (self.LanguagePack ? LanguagePack.code : 'en')
		this.keys = keys
		this.prerequisites = {}
	},
	
	load: function (name, version)
	{
		if (name)
			this.apis.push({name: name, version: version})
		
		var me = this, timer
		
		if (this.state === 0)
		{
			if (!this.keys)
				throw new Error(myName + ': keys is undefined')
			
			this.node = $.include('http://www.google.com/jsapi?key=' + this.keys[this.host])
			
			timer = setInterval(function () { if (self.google) clearInterval(timer), me.apiLoaderLoaded(self.google) }, 250)
			
			this.state = 1
		}
		
		if (this.state === 2)
			setTimeout(function () { me.fireAll() }, 10)
			
		return this
	},
	
	apiLoaderLoaded: function (google)
	{
		this.state = 2
		this.google = google
		this.apiLoaded({name: 'loader'}, this.google['loader'])
		
		this.fireAll()
	},
	
	fireAll: function ()
	{
		var i, me = this
		for (i = 0; i < this.apis.length; i++)
			(function (api) { me.google.load(api.name, api.version, {nocss: true, language: me.language, callback: function () { me.apiLoaded(api) }}) })(this.apis[i])
		
		this.apis.length = 0
	},
	
	apiLoaded: function (api)
	{
		this.dispatchEvent({type: api.name, api: this.google[api.name]})
	}
})

})();


self.googleApiLoader = new GoogleApiLoader
({
	'retailstars.ru': 'ABQIAAAARQzYWu9IpurDSJW9DIJqrxRAD2hlzUPVjzbVAmy-N2j8kLTHsBQ7TUU1PSmu_x2_YPrpDO7w51u_wg',
	'programica.ru':  'ABQIAAAARQzYWu9IpurDSJW9DIJqrxQVF992HTeapfH7j2YfASPwC0rg6BRLdvrEuTsIejJP0tsK0O0WOScMCw'
})


;(function(){

var myName = 'Scroller',
	Me = self[myName] = Class(myName),
	doc = document

Me.mixIn(EventDriven)

Me.prototype.extend
({
	initialize: function ()
	{
		this.nodes = {}
		this.conf =
		{
			duration: 1,
			animation: 'easeOutBack',
			auto: 0.5
		}
	},
	
	bind: function (main, conf)
	{
		Object.extend(this.conf, conf)
		this.nodes.main = main
		
		var me = this
		function mouseup (e)
		{
			e.preventDefault()
			clearInterval(me.autoTimer)
			doc.removeEventListener('mouseup', mouseup, false)
		}
		
		function meGoPrev () { me.goPrev(); me.dispatchSelect({origin:'user'}) }
		function meGoNext () { me.goNext(); me.dispatchSelect({origin:'user'}) }
		
		this.prevmousedown = function (e)
		{
			e.preventDefault()
			clearInterval(me.autoTimer)
			meGoPrev()
			me.autoTimer = setInterval(meGoPrev, me.conf.duration * 1000 * me.conf.auto + 150)
			doc.addEventListener('mouseup', mouseup, false)
		}

		this.nextmousedown = function (e)
		{
			e.preventDefault()
			clearInterval(me.autoTimer)
			meGoNext()
			me.autoTimer = setInterval(meGoNext, me.conf.duration * 1000 * me.conf.auto + 150)
			doc.addEventListener('mouseup', mouseup, false)
		}
		
		this.sync()
		this.goInit()
	},
	
	sync: function ()
	{
		var nodes = this.nodes
		nodes.viewport = this.my('viewport')[0]
		if (!nodes.viewport)
			throw new Error('Can`t find viewport for ' + nodes.main)
		if (!nodes.viewport.animate)
			throw new Error('Viewport can`t be animated!')
		
		nodes.points = this.my('point')
		nodes.prev = this.my('prev')[0]
		nodes.next = this.my('next')[0]
		
		// if syncing when pushed
		clearInterval(this.autoTimer)
		
		if (nodes.prev)
			nodes.prev.addEventListener('mousedown', this.prevmousedown, false)
		
		if (nodes.next)
			nodes.next.addEventListener('mousedown', this.nextmousedown, false)
		
		this.updateNavigation()
	},
	
	goPrev: function () { if (this.current > 0) this.goToFrame(this.current - 1) },
	goNext: function () { if (this.current < this.nodes.points.length - 1) this.goToFrame(this.current + 1) },
	my: function (cn) { return this.nodes.main.getElementsByClassName(cn) },
	
	goInit: function (n) { this.goToFrame(n || 0, 'directJump'); return n },
	
	goToFrame: function (n, anim, dur) { return this.nodes.points ? this.goToNode(this.nodes.points[n || 0], anim, dur) : null },
	
	goToNode: function (node, anim, dur)
	{
		if (!node)
			return null
		
		var points = this.nodes.points
		// change number of current node
		for (var i = 0, il = points.length; i < il; i++)
			if (points[i] == node)
				this.setCurrent(i)
		
		return this.animateTo(node.offsetLeft, node.offsetTop, anim, dur)
	},
	
	animateTo: function (left, top, anim, dur)
	{
		return this.nodes.viewport.animate(anim || this.conf.animation, {scrollLeft: left, scrollTop: top}, dur || this.conf.duration)
	},
	
	jumpTo: function (left, top)
	{
		var viewport = this.nodes.viewport
		viewport.scrollLeft = left
		viewport.scrollTop = top
	},
	
	updateNavigation: function ()
	{
		var nodes = this.nodes
		
		if (nodes.prev)
			nodes.prev.toggleClassName('disabled', this.current <= 0)
		
		if (nodes.next)
			nodes.next.toggleClassName('disabled', this.current >= nodes.points.length - 1)
	},
	
	setCurrent: function (num)
	{
		this.current = num
		this.updateNavigation()
		
		this.dispatchSelect()
		
		// if (cp && cp.onselect)
		// 	cp.onselect()
	},
	
	dispatchSelect: function (opts)
	{
		var num = this.current, node = this.nodes.points[num],
			event = {type:'select', node: node, number:num}
		
		if (opts)
			Object.extend(event, opts)
		this.dispatchEvent(event)
	}
})


})();
;(function(){

var myName = 'ScrollerRotator',
	Me = self[myName] = Class(myName)

Me.mixIn(EventDriven)

Me.prototype.extend
({
	initialize: function ()
	{
		this.conf = {time: 5, dir: 1, count: Infinity}
		this.loaded = []
	},
	
	configure: function (conf) { Object.extend(this.conf, conf) },
	
	bind: function (scroller, conf)
	{
		// image.onload = function () { alert(image.src) }
		// big images rotator
		this.scroller = scroller
		var me = this
		scroller.addEventListener('select', function (e) { me.onSelect(e) }, false)
		
		return this
	},
	
	onSelect: function (e)
	{
		if (e.origin == 'user')
			this.stop()
	},
	
	start: function ()
	{
		this.stop()
		var me = this, conf = this.conf
		this.count = conf.count
		this.dir = conf.dir
		this.timer = setInterval(function () { me.rotate() }, this.conf.time * 1000)
	},
	
	stop: function ()
	{
		clearInterval(this.timer)
	},
	
	rotate: function ()
	{
		if (--this.count <= 0)
			this.stop()
		else
		{
			var next = this.getNext()
			if (next >= 0 && this.dispatchEvent({type:'rotate', next:next}))
				this.scroller.goToFrame(next)
		}
	},
	
	getNext: function ()
	{
		var scroller = this.scroller,
			len = scroller.nodes.points.length,
			cur = scroller.current, next
		
		if (len < 2)
			return -1
		
		next = cur + this.dir
		
		if (next <= 0 || next + 1 >= len)
			this.dir *= -1
		
		return Math.round(next)
	}
})


})();
// ScrollRangeFinder
;(function(){

var myName = 'ScrollRangeFinder',
	Me = self[myName] = Class(myName),
	max = Math.max, sqrt = Math.sqrt

Me.mixIn(EventDriven)

Me.prototype.extend
({
	interval: 1000,
	initialize: function ()
	{
		this.nodes = {}
		this.vals = {}
	},
	
	bind: function (main, images)
	{
		var nodes = this.nodes
		
		nodes.main = main
		nodes.images = images
		
		this.precalc()
		
		this.monitor(true)
	},
	
	precalc: function ()
	{
		var images = this.nodes.images, circles = this.circles = [],
			image, i, x, y, w, h, cx, cy, r
		
		for (i = 0; i < images.length; i++)
		{
			image = images[i]
			x = image.offsetLeft
			y = image.offsetTop
			w = image.offsetWidth
			h = image.offsetHeight
			cx = x + w / 2
			cy = y + h / 2
			r = max(w, h) / 2
			
			circles[i] = {cx: cx, cy: cy, r: r}
		}
		
	},
	
	changed: function (old, pos)
	{
		var nodes = this.nodes, main = nodes.main, circles = this.circles,
			w = main.offsetWidth, h = main.offsetHeight,
			i, circle, dx, dy, d,
			cx = pos.x + w / 2, cy = pos.y + h / 2,
			s = max(w, h), ranges = []
			// box = {w: main.scrollWidth, h: main.scrollHeight}
		
		// log('changed')
		
		for (i = 0; i < circles.length; i++)
		{
			circle = circles[i]
			
			dx = circle.cx - cx
			dy = circle.cy - cy
			
			d = sqrt(dx*dx + dy*dy) - circle.r - s / 2
			
			ranges[i] = d / s + 1
		}
		
		this.dispatchEvent({type: 'scroll', ranges: ranges})
	},
	
	check: function ()
	{
		var main = this.nodes.main, oldVals = this.vals,
			newVals = {x: main.scrollLeft, y: main.scrollTop}
		
		if (newVals.x !== oldVals.x || newVals.y !== oldVals.y)
		{
			this.changed(oldVals, newVals)
			this.vals = newVals
		}
	},
	
	monitor: function (start)
	{
		clearInterval(this.timer)
		if (start)
		{
			var me = this
			function check () { me.check() }
			this.timer = setInterval(check, this.interval)
			me.check()
		}
	}
})

})();
;(function(){

var myName = 'TabSwitcher'
var Me = self[myName] = Class(myName)

Me.mixIn(EventDriven)

// eval(NodesShortcut())

// function assert (val)
// {
// 	if (!val)
// 	{
// 		log(arguments.callee.caller)
// 		try { throw new Error() }
// 		catch (ex) { log(ex) }
// 	}
// }

Me.prototype.extend
({
	eventType: 'mousedown',
	
	initialize: function ()
	{
		this.nodes = {tabs:[], sections:[]}
	},
	
	bind: function (nodes)
	{
		var me = this
		this.mouseListener = function (e) { me.onMouse(e, this) }
		
		this.setTabs(nodes.tabs)
		this.setSections(nodes.sections)
		
		return this
	},
	
	unbind: function ()
	{
		this.setTabs([])
	},
	
	onMouse: function (e, node)
	{
		// if (e.target != node)
		// 	return
		
		var tabs = this.nodes.tabs,
			i, num = -1
		
		for (i = 0; i < tabs.length; i++)
			if (node === tabs[i])
				num = i
		
		if (!this.select(num))
			e.preventDefault()
	},
	
	select: function (num)
	{
		var ok = this.dispatchEvent({type:'select', num:num})
		if (ok)
			this.renderSelected(num)
		return ok
	},
	
	setTabs: function (tabs)
	{
		var i, old = this.nodes.tabs, listener = this.mouseListener
		
		for (i = 0; i < old.length; i++)
			if (old[i])
				old[i].removeEventListener(this.eventType, listener, false)
		
		this.nodes.tabs = tabs
		
		for (i = 0; i < tabs.length; i++)
			if (tabs[i])
				tabs[i].addEventListener(this.eventType, listener, false)
	},
	
	setSections: function (sections)
	{
		this.nodes.sections = sections
	},
	
	renderSelected: function (num)
	{
		var i, node, nodes = this.nodes, tabs = nodes.tabs, sections = nodes.sections
		
		for (i = 0; i < tabs.length; i++)
			if ((node = tabs[i]))
				node.toggleClassName('selected', num === i)
		
		for (i = 0; i < sections.length; i++)
			if ((node = sections[i]))
				node.toggleClassName('hidden', num !== i)
	}
	
})

})();
;(function(){

var myName = 'Autocompleter'
var Me = self[myName] = MVC.create(myName)

Me.prototype.extend
({
	bind: function (main)
	{
		this.view.bind({main:main})
		return this
	},
	
	setDataSource: function (ds) { this.model.dataSource = ds },
	setCount: function (count) { this.model.count = count }
})

Me.mixIn(EventDriven)

eval(NodesShortcut())

var VK_TAB = 9, VK_ENTER = 13, VK_ESC = 27, VK_PGUP = 33, VK_PGDN = 34, VK_END = 35, VK_HOME = 36,
	VK_LEFT = 37, VK_UP = 38, VK_RIGHT = 39, VK_DOWN = 40

Me.View.prototype.extend
({
	initialize: function ()
	{
		this.nodes = {}
		this.chache = {}
	},
	
	bind: function (nodes)
	{
		this.nodes = nodes
		var main = nodes.main
		main.setAttribute('autocomplete', 'off')
		
		var list = this.nodes.list = N('ul')
		list.className = 'completition'
		main.parentNode.appendChild(list)
		
		var me = this
		function key (e) { me.onKeyPress(e) }
		// main.addEventListener('keypress', key, false)
		main.addEventListener('keydown', key, false)
		main.addEventListener('blur', function (e) { me.onBlur(e) }, false)
		list.addEventListener('mousedown', function (e) { me.onMouseDown(e) }, false)
		list.addEventListener('mousemove', function (e) { me.onMouseMove(e) }, false)
	},
	
	onKeyPress: function (e)
	{
		var targ = e.target
		var controller = this.controller
		
		switch (e.keyCode)
		{
			case VK_UP:
				controller.goUp(targ.value)
				e.preventDefault()
				e.stopPropagation()
			break
			
			case VK_DOWN:
				controller.goDown(targ.value)
				e.preventDefault()
				e.stopPropagation()
			break
			
			case VK_ESC:
				controller.goEscape(targ.value)
				e.preventDefault()
				e.stopPropagation()
			break
			
			case VK_ENTER:
				if (this.active)
				{
					controller.goEnter(targ.value)
					e.preventDefault()
					e.stopPropagation()
				}
			break
			
			default:
				setTimeout(function () { controller.valueUpdated(targ.value) }, 1)
		}
	},
	
	onBlur: function (e)
	{
		this.controller.goBlur()
	},
	
	onMouseMove: function (e)
	{
		var index = Array.copy(this.nodes.list.childNodes).indexOf(e.target)
		this.controller.itemHovered(index)
	},
	
	onMouseDown: function (e)
	{
		var index = Array.copy(this.nodes.list.childNodes).indexOf(e.target)
		this.controller.itemClicked(index)
	},
	
	renderVariant: function (str)
	{
		this.nodes.main.value = str
	},
	
	renderResults: function (valsSet)
	{
		this.updateNodes(valsSet)
		valsSet.length ? this.show() : this.hide()
	},
	
	show: function ()
	{
		var main = this.nodes.main
		var list = this.nodes.list
		
		list.style.top = (main.offsetTop + main.offsetHeight) + 'px'
		list.style.width = (main.offsetWidth - 2) + 'px'
		
		list.show()
		
		this.active = true
	},
	
	hide: function ()
	{
		this.nodes.list.hide()
		
		this.active = false
	},
	
	updateNodes: function (valsSet)
	{
		var list = this.nodes.list
		list.empty()
		for (var i = 0; i < valsSet.length; i++)
		{
			var val = valsSet[i]
			var item = this.chache[val]
			if (!item)
			{
				var item = N('li')
				item.className = 'item'
				item.appendChild(T(val))
				this.chache[val] = item
			}
			list.appendChild(item)
		}
		
		this.selectVariant()
	},
	
	selectVariant: function (num)
	{
		var childs, list
		if ((list = this.nodes.list) && (childs = Array.copy(list.childNodes)))
		{
			for (var i = 0; i < childs.length; i++)
				childs[i].removeClassName('selected')
			
			var node = childs[num]
			if (node)
				node.addClassName('selected')
		}
	}
})

Me.Controller.prototype.extend
({
	initialize: function ()
	{
		this.vals = []
		this.value = ''
		this.selected = -1
	},
	
	resetSelector: function ()
	{
		this.view.renderResults([])
		this.vals = []
		this.value = ''
		this.selected = -1
	},
	
	beginSearch: function (value)
	{
		this.resetSelector()
		
		var vals = this.model.search(value)
		if (vals.length == 1 && vals[0] == value)
			vals.shift()
		this.view.renderResults(vals)
		this.vals = vals
		this.value = value
	},
	
	valueUpdated: function (value)
	{
		if (this.lastValue != value)
		{
			this.lastValue = value
			this.beginSearch(value)
		}
	},
	
	updateValue: function (value)
	{
		this.lastValue = value
		this.view.renderVariant(value)
		this.parent.dispatchEvent({type:'modelChanged', value:value})
	},
	
	goEscape: function ()
	{
		this.updateValue(this.value)
		this.resetSelector()
	},
	
	goEnter: function (value)
	{
		this.updateValue(this.vals[this.selected] || this.value)
		this.resetSelector()
	},
	
	goBlur: function ()
	{
		this.resetSelector()
	},
	
	goUp: function (value)
	{
		if (!this.vals.length)
		 	return// this.beginSearch(value)
		
		switch (--this.selected)
		{
			case -1:
				value = this.value
			break
			
			case -2:
				this.selected = this.vals.length - 1
			
			default:
				value = this.vals[this.selected]
		}
		
		this.view.selectVariant(this.selected)
		this.updateValue(value)
	},
	
	goDown: function (value)
	{
		if (!this.vals.length)
			return this.beginSearch(value)
			
		
		switch (++this.selected)
		{
			case this.vals.length:
				this.selected = -1
				value = this.value
			break
			
			default:
				value = this.vals[this.selected]
		}
		
		this.view.selectVariant(this.selected)
		this.updateValue(value)
	},
	
	itemHovered: function (num)
	{
		if (this.selected != num)
		{
			this.selected = num
			this.value = this.vals[num]
			this.view.selectVariant(num)
		}
	},
	
	itemClicked: function (num)
	{
		this.selected = num
		this.value = this.vals[num]
		this.view.selectVariant(num)
		this.updateValue(this.value)
		this.resetSelector()
	}
})

Me.Model.prototype.extend
({
	search: function (value)
	{
		return this.dataSource.searchSubstr(value, this.count)
	}
})

})();

;(function(){

var myName = 'FocusTip'
var Me = self[myName] = Class(myName)

Me.prototype.extend
({
	filledClassName: 'filled',
	
	initialize: function (main)
	{
		this.nodes = {}
	},
	
	bind: function (main)
	{
		this.nodes.main = main
		this.nodes.inputs = $$('input[type="text"], textarea', main)
		this.addListeners()
		return this
	},
	
	addListeners: function ()
	{
		var me = this, inputs = this.nodes.inputs, input
		function focus (e) { me.focus(e.target) }
		function blur (e) { me.blur(e.target) }
		for (var i = 0; i < inputs.length; i++)
		{
			input = inputs[i]
			input.addEventListener('focus', focus, false)
			input.addEventListener('blur', blur, false)
			if (input.value)
				input.parentNode.addClassName(this.filledClassName)
		}
	},
	
	focus: function (input)
	{
		input.parentNode.addClassName(this.filledClassName)
	},
	
	blur: function (input)
	{
		if (!input.value)
			input.parentNode.removeClassName(this.filledClassName)
	}
})

})();
;(function(){

var myName = 'MapLiteMarker',
	Me = self[myName] = Module(myName)

Me.prototype =
{
	initialize: function (map)
	{
		var node = this.node || (this.node = this.createNode())
		map.getPane(G_MAP_MARKER_PANE).appendChild(node)
		this.map = map
	},
	
	redraw: function (force)
	{
		if (!force)
			return
		
		var map = this.map, latlng = this.latlng, style = this.node.style,
			sw = map.fromLatLngToDivPixel(latlng)
		
		style.left = sw.x + "px";
		style.top = sw.y + "px";
	},
	
	remove: function () { this.node.remove() },
	copy: function () { return new this.constructor() }
}

})();


;(function(){

var myName = 'DB',
	Me = self[myName] = Class(myName)

Me.extend
({
	getVacancies: function () { return this.vacancies },
	getMetro: function () { return this.metro },
	getPoints: function () { return this.points },
	getCities: function () { return this.cities }
})


})();
;(function(){

var myName = 'City',
	Me = self[myName] =
{
	initialize: function ()
	{
		this.db = []
	},
	
	setData: function (db) { this.db = db },
	
	get: function (num)
	{
		return this.db[num]
	}
}


})();
;(function(){

var myName = 'Vacancy',
	Me = self[myName] = Class(myName)

Me.extend
({
	setData: function (data)
	{
		var db = this.db = []
		for (var i = 0; i < data.length; i++)
		{
			var d = data[i],
				v = db[i] = new Me()
			
			for (var k in d)
				v[k] = d[k]
			
			v.type = v.brand + '--' + v.position
		}
	},
	
	getPositions: function ()
	{
		var db = this.db, names = {}
		
		for (var i = 0, len = db.length; i < len; i++)
		{
			var position = db[i].position
			
			names[position] ? names[position]++ : (names[position] = 1)
		}
		
		return Object.keys(names).sort(function (a, b) { return names[a] - names[b] })
	},
	
	getAll: function ()
	{
		return this.db
	},
	
	getByQuery: function (query)
	{
		var db = this.db, res = [], count = 0
		
		if (!Object.keys(query).length)
			return db
		
		scan: for (var i = 0, len = db.length; i < len; i++)
		{
			var entity = db[i]
			
			for (var k in query)
				if (entity[k] !== query[k])
					continue scan
			
			res[count++] = entity
		}
		
		return res
	},
	
	count: function (query)
	{
		return query ? this.getByQuery(query).length : this.getAll().length
	}
})


Me.prototype.extend
({
	initialize: function ()
	{
	}
})


})();
;(function(){

var myName = 'Point',
	Me = self[myName] = Class(myName), keys = Object.keys,
	id = 0, lang = LanguagePack[myName]

eval(NodesShortcut())

Me.extend
({
	setData: function (points)
	{
		var db = this.db = [], byId = this.cacheById = {}
		
		for (var i = 0; i < points.length; i++)
		{
			var data = points[i]
			
			point = db[i] = byId[data.id] = new Me()
			point.id = data.id
			point.latlng = data.latlng
			point.address = data.address
		}
	},
	
	byId: function (id)
	{
		return this.cacheById[id] || null
	},
	
	fromVacancies: function (vacancies)
	{
		var points = [], byId = this.cacheById,
			closed = {}, normal = {}, hot = {}, seen = {}
		// alert(vacancies.length)
		for (var i = 0; i < vacancies.length; i++)
		{
			var vac = vacancies[i], pid = vac.point, status = vac.status,
				point = byId[pid]
			
			if (!point)
				continue
			
			if (!seen[pid])
			{
				points.push(point)
				seen[pid] = point
			}
			
			
			if (hot[pid])
				continue
			if (status === 'hot')
			{
				hot[pid] = point
				delete normal[pid]
				delete closed[pid]
				continue
			}
			
			if (normal[pid])
				continue
			if (status === 'open')
			{
				normal[pid] = point
				delete closed[pid]
				continue
			}
			
			if (closed[pid])
				continue
			if (status === 'closed')
			{
				closed[pid] = point
				continue
			}
		}
		
		for (var k in hot)
			hot[k].setStatus('hot')
		
		for (var k in normal)
			normal[k].setStatus('')
		
		for (var k in closed)
			closed[k].setStatus('closed')
		
		// log(points)
		// alert(points.length)
		return points
	}
})

Me.prototype.extend
({
	latlng: {},
	initialize: function ()
	{
		this.id = ++id
		this.nodes = {}
	},
	
	createNode: function ()
	{
		return this.node || (this.node = this.renderNode())
	},
	
	renderNode: function ()
	{
		var nodes = this.nodes
		
		var main = nodes.main = N('div', 'point')
		nodes.icon = main.appendChild(N('div', 'icon'))
		var title = nodes.title = main.appendChild(N('div', 'title'))
		var name = nodes.name = title.appendChild(N('span', 'point-name', name))
		nodes.nameText = name.appendChild(T(''))
		nodes.titleText = title.appendChild(T(this.address))
		
		if (this.status)
			this.setStatus(this.status)
		
		return main
	},
	
	setStatus: function (status)
	{
		this.status = status
		
		var nodes = this.nodes
		
		if (!nodes.main)
			return
		
		nodes.main.className = 'point ' + status
		
		name = lang[status]
		if (name)
			nodes.nameText.nodeValue = name
	}
})


})();
;(function(){

var myName = 'Map',
	Me = self[myName] = MVC.create(myName),
	MODE_SINGLE = Me.MODE_SINGLE = 0,
	MODE_MULTY = Me.MODE_SINGLE = 1


Me.prototype.extend
({
	bind: function (main, center, zoom)
	{
		this.view.bind({main:main}, center, zoom)
		this.todos = []
		return this
	},
	
	setMode: function (mode) { this.controller.setMode(mode) },
	setPoints: function (points) { this.model.setPoints(points) },
	selectPoint: function (point) { this.controller.selectPoint(point) },
	apiLoaded: function () { this.dispatchEvent('ready') }
})

Me.mixIn(EventDriven)

eval(NodesShortcut())

Me.View.prototype.extend
({
	initialize: function ()
	{
		this.nodes = {}
		this.markersCache = {}
		this.visibleMarkers = {}
	},
	
	bind: function (nodes, center, zoom)
	{
		this.nodes = nodes
		this.center = center
		this.zoom = zoom
		
		googleApiLoader.load('maps', 2)
		nodes.main.addClassName('loading')
		var me = this
		self.googleApiLoader.addEventListener('maps', function (e) { me.apiLoaded(e) }, false)
	},
	
	apiLoaded: function (e)
	{
		var api = this.api = e.api
		this.createMarkerClass()
		this.createMap()
		
		this.ready = true
		this.controller.apiLoaded()
	},
	
	createMap: function ()
	{
		var api = this.api, main = this.nodes.main,
			map, me = this
		
		main.removeClassName('loading')
		
		map = this.map = new api.Map2(main)
		api.Event.addListener(map, 'load', function () { me.mapLoaded(this) })
		map.enableContinuousZoom()
		map.setCenter(new api.LatLng(this.center.lat, this.center.lng), this.zoom)
	},
	
	mapLoaded: function ()
	{
		// log('mapLoaded')
		var me = this
		this.api.Event.addListener(this.map, 'moveend', function () { me.mapMoveEnd(this) })
		this.addControls()
	},
	
	addControls: function ()
	{
		// var api = this.api, controlPosition = new api.ControlPosition(api.ANCHOR_TOP_LEFT, new api.Size(10, 15))
		// this.map.addControl(new api.SmallMapControl(), controlPosition)
		
		var main = this.nodes.main, map = this.map, control
		
		control = main.appendChild(N('div', 'position-control'))
		
		control.appendChild(N('div', 'to-top'))
		control.appendChild(N('div', 'to-right'))
		control.appendChild(N('div', 'to-bottom'))
		control.appendChild(N('div', 'to-left'))
		
		control.appendChild(N('div', 'to-plus'))
		control.appendChild(N('div', 'to-minus'))
		
		function move (e)
		{
			switch (e.target.className)
			{
				case 'to-top':
				map.panDirection(0, 1)
				break
				
				case 'to-right':
				map.panDirection(-1, 0)
				break
				
				case 'to-bottom':
				map.panDirection(0, -1)
				break
				
				case 'to-left':
				map.panDirection(1, 0)
				break
				
				case 'to-plus':
				map.zoomIn()
				break
				
				case 'to-minus':
				map.zoomOut()
				break
			}
		}
		
		control.addEventListener('mousedown', move, false)
	},
	
	createMarkerClass: function ()
	{
		if (!this.markerClass)
		{
			var klass = this.markerClass = function () {  },
				kp = klass.prototype = new this.api.Overlay()
			// kp.createNode = function () { return this.point.createNode() }
			klass.mixIn(MapLiteMarker)
		}
	},
	
	mapMoveEnd: function (map)
	{
		var bounds = map.getBounds(), sw = bounds.getSouthWest(), ne = bounds.getNorthEast()
		this.controller.moved(map.getCenter(), map.getZoom(), {lat:sw.lat(), lng:sw.lng()}, {lat:ne.lat(), lng:ne.lng()})
	},
	
	pointClicked: function (point)
	{
		this.controller.pointInvoked(point)
	},
	
	renderPoints: function (points, state)
	{
		if (!this.ready)
			return
		
		var map = this.map,
			visible = this.visibleMarkers,
			now = this.visibleMarkers = {}
		
		// map.clearOverlays()
		// removeOverlay
		for (var i = 0; i < points.length; i++)
		{
			var point = points[i], pid = point.id
			
			// get the marker and insert it into the new visibleMarkers hash
			var marker = now[pid] = this.getGMarker(point)
			
			// add marker (and delete its record) only if it isn't already shown
			if (!visible[pid])
				map.addOverlay(marker)
			else
				delete visible[pid]
			
			marker.node.toggleClassName('selected', !!state[point.id])
		}
		
		// remove all the markers that are still visible
		for (var k in visible)
			map.removeOverlay(visible[k])
	},
	
	updatePoint: function (point, state)
	{
		if (!this.ready)
			return
		
		var marker = this.getGMarker(point)
		marker.node.toggleClassName('selected', state)
	},
	
	getGMarker: function (point)
	{
		var cache = this.markersCache, marker
		if ((marker = cache[point.id]))
			return marker
		
		var latlng = point.latlng, api = this.api, me = this, node
		
		marker = cache[point.id] = new this.markerClass()
		marker.latlng = new GLatLng(latlng.lat, latlng.lng)
		node = marker.node = point.createNode()
		node.addEventListener('mousedown', function (e) { e.stopPropagation(); me.pointClicked(point) }, false)
		return marker
	}
})

Me.Controller.prototype.extend
({
	moved: function (center, zoom, sw, ne)
	{
		this.parent.dispatchEvent({type:'moved', center:center, zoom:zoom, sw:sw, ne:ne})
	},
	
	pointInvoked: function (point)
	{
		this.model.pointInvoked(point)
	},
	
	setMode: function (mode) { this.model.setMode(mode) },
	selectPoint: function (point) { this.model.pointInvoked(point, true) },
	apiLoaded: function () { this.model.apiLoaded(); this.parent.apiLoaded() }
})

Me.Model.prototype.extend
({
	initialize: function ()
	{
		this.points = []
		this.state = {}
		this.mode = 1
		this.pointsById = {}
	},
	
	setMode: function (mode) { this.mode = mode },
	
	apiLoaded: function () { this.view.renderPoints(this.points, this.state) },
	
	setPoints: function (points)
	{
		this.points = points
		for (var i = 0; i < points.length; i++)
		{
			var point = points[i]
			this.pointsById[point.id] = point
		}
		this.fireModelChanged()
		this.view.renderPoints(points, this.state)
	},
	
	pointInvoked: function (point, status)
	{
		var view = this.view, state = this.state, id = point.id
		
		if (status === undefined)
			status = !state[id]
		
		if (!this.parent.dispatchEvent({type:'pointInvoked', point: point, status: status}))
			return false
		
		if (this.mode === MODE_MULTY)
		{
			if (status)
				state[id] = true
			else
				delete state[id]
		}
		else if (this.mode === MODE_SINGLE)
		{
			var pointsById = this.pointsById
			for (var k in state)
			{
				var p = this.pointsById[k]
				if (p !== point)
					view.updatePoint(p, false)
			}
			
			state = this.state = {}
			if (status)
				state[id] = status
		}
		
		this.fireModelChanged()
		view.updatePoint(point, status)
	},
	
	fireModelChanged: function ()
	{
		var byId = this.pointsById, state = this.state, selected = []
		
		for (var k in state)
			if (state[k])
			 	selected.push(byId[k])
		
		this.parent.dispatchEvent({type:'modelChanged', state: this.state, selected: selected})
	}
})

})();

;(function(){

var myName = 'LazyScroller',
	Me = self[myName] = Class(myName)

Me.mixIn(EventDriven)

Me.prototype.extend
({
	initialize: function ()
	{
		this.nodes = {}
		this.conf = {range: 2, count: Infinity}
	},
	
	bind: function (scroller)
	{
		// onscroll loader
		var conf = this.conf, rangeFinder = this.rangeFinder = new ScrollRangeFinder(),
			images = scroller.nodes.points
		
		function imageLoaded () { this.lazyLoadind = false; this.lazyLoaded = true }
		for (var i = 0; i < images.length; i++)
			images[i].addEventListener('load', imageLoaded, false)
		
		function scrolled (e)
		{
			var ranges = e.ranges
			for (var i = 0; i < ranges.length; i++)
			{
				var image = images[i], lazy
				if (!image.lazyLoadind && !image.lazyLoaded && ranges[i] <= conf.range)
				{
					lazy = image.getAttribute('lazy')
					if (lazy)
					{
						image.src = lazy
						image.lazyLoadind = true
					}
					else
						image.lazyLoaded = true
				}
			}
		}
		rangeFinder.addEventListener('scroll', scrolled, false)
		rangeFinder.bind(scroller.nodes.viewport, images)
		
		var rotator = this.rotator = new ScrollerRotator()
		rotator.conf.count = this.conf.count
		
		function mayRotate (e)
		{
			if (!this.scroller.nodes.points[e.next].lazyLoaded)
				e.preventDefault()
		}
		rotator.addEventListener('rotate', mayRotate, false)
		rotator.bind(scroller).start()
		
		return this
	}
})


})();

;(function(){

var myName = 'Analytics',
	empty = function () {},
	error = function () { throw new Error(myName + ' currently is not in right state') }

function job (name) { return function () { this.jobs.push([name, arguments]) } }

var Me = self[myName] =
{
	jobs: [],
	
	state: null,
	states:
	{
		init:
		{
			load: function (account)
			{
				this.account = account
				$.include(('https:' == document.location.protocol ? 'https://ssl.' : 'http://www.') + 'google-analytics.com/ga.js')
				this.setState('loading')
				
				var me = this, timer
				function check ()
				{
					// log('check')
					if (self._gat)
					{
						clearInterval(timer)
						me.setState('ready')
						me.loaded()
					}
				}
				timer = setInterval(check, 250)
			},
			exec: error,
			track: error
		},
		
		loading:
		{
			load: empty,
			exec: job('exec'),
			track: job('track')
		},
		
		ready:
		{
			load: empty,
			loaded: function ()
			{
				this.tracker = _gat._getTracker(this.account)
				
				this.doJobs()
			},
			exec: function (func) { func.apply(this) },
			track: function () { this.tracker._trackPageview.apply(this.tracker, arguments) }
		}
	},
	
	doJobs: function ()
	{
		var jobs = this.jobs
		for (var i = 0; i < jobs.length; i++)
		{
			var job = jobs[i]
				meth = this[job[0]]
			if (meth)
				meth.apply(this, job[1])
			else
				throw new Error('method "' + job[0] + '" is undefined')
		}
	},
	
	setState: function (name)
	{
		// log('setState', name)
		var state = this.states[name]
		if (state)
		{
			this.state = name
			Object.extend(this, state)
		}
		else
			throw new Error(myName + ': now such state "' + name + '"')
	}
}

// // a few tests
// var error
// try { Me.track() } catch (ex) { error = true }
// log(error ? 'ok' : 'fail')
// 
// Me.setState('loading')
// error = false
// try { Me.track() } catch (ex) { error = true }
// log(!error ? 'ok' : 'fail')

Me.setState('init')


})();

$.onload(function ()
{
	Cookie.check()
	document.documentElement.removeClassName('loading')
	setTimeout(function () { Analytics.load("UA-1635720-10"); Analytics.track() }, 500)
	
	var locationHash = window.locationHash = new LocationHash()
	locationHash.bind(window.location)
})
