// текущий видимый элемент
g_current_visible = null;
// ширина клиентской области ( null, т.к. документ не загрузился)
g_all_width = null;
// высота клиентской области
g_all_height = null;
// полная высота документа
g_all_scroll = null;
// рут документа
g_root = document.URL.match( /(.*\/)[^\/]*$/)[1];
// эксплорер ли?
g_is_ie = navigator.appName == "Microsoft Internet Explorer" ? true : false;
// таймер текущей страницы
g_time_holder = null;

// массив скриптов документа
if ( !document.scripts) {
    document.scripts = document.getElementsByTagName('script');
}

// возвращает текст узлаprop.end
function get_text( node) {
	return node.text || node.textContent || node.innerText || "";
}

// проверяет и возвращает json-объект
function get_JSON( text) {
    if ( text) {
        return !(text.replace(/"(\\.|[^"\\])*"/g, '').match(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/)) &&
eval('(' + text + ')');
    } else {
        return false;    
    }    
}

// аналог php::stipslashes
function stripslashes( str ) {
	return str.replace('/\0/g', '0').replace('/\(.)/g', '$1');
}

// генерируем условия совпадения элемента для get_elements()
function get_expression( element, value, strictFlag) {
    if ( !value || !value.tagName) {
        return {
            exp: null,
            num: null
        }
    }
	// определяем, что надо искать
    regExp = /^\.([^\[]+)(?:\[(\d+)\])?/;
    regExp2 = /^(\w+)\.([^\[]+)(?:\[(\d+)\])?/;
    regExp3 = /^([^\[]+)(?:\[(\d+)\])?$/;
    // если необходимо найти точное совпадение
	if ( c_match = element.match( regExp) ) {
        if (strictFlag) {
            //:KLUDGE:
            c_regExp = new RegExp( "[^a-z0-9_]" + c_match[1] + "[^a-z0-9_]|^" + c_match[1] + "[^a-z0-9_]|[^a-z0-9_]" + c_match[1] + "$|^" + c_match[1] + "$", "i");
        }
		expression = !strictFlag ? ( value.className.indexOf( c_match[1]) != -1) : ( value.className.match( c_regExp));
		// если запрашивается элемент под определённым номером
        number = c_match[2] ? c_match[2] : null;
	// ищем определённые элементы с классом
	} else if ( c_match = element.match( regExp2) ) {
        if (strictFlag) {
            //:KLUDGE:
            c_regExp = new RegExp( "[^a-z0-9_]" + c_match[2] + "[^a-z0-9_]|^" + c_match[2] + "[^a-z0-9_]|[^a-z0-9_]" + c_match[2] + "$|^" + c_match[2] + "$", "i");
        }
		expression = !strictFlag ? (value.tagName.toLowerCase() == c_match[1] && value.className.indexOf(c_match[2]) != -1) : (value.tagName.toLowerCase() == c_match[1] && value.className.match( c_regExp));
		// если запрашивается элемент под определённым номером
        number = c_match[3] ? c_match[3] : null;
	// ищем определённые элементы без класса
	} else if ( c_match = element.match( regExp3) ) {
		expression = (value.tagName.toLowerCase() == c_match[1]);
		// если запрашивается элемент под определённым номером
        number = c_match[2] ? c_match[2] : null;
	}
    return {
        exp: expression,
        num: number
    }
}

// получаем массив, удовлетворяющий условиям
function get_elements( element, root, flags) {
    // нужно текущее значение result - объявляем здесь.
    function get_concidence( needle, haystack, flags) {
        counter = -1;
		// ищем в текущем элементе
        work_array = flags.scanChildren ? haystack.getElementsByTagName('*') : haystack.children;
        element_before = null;
        element_after = false;
		for( var j in current_children = work_array ) {
            if ( in_obj( current_children[j], result) != -1) {
                continue;
            }
            // пропускаем не числовые значения j
            value = current_children[j];
            // если в прошлый раз условие выполнилось и есть флаг !after
            if ( element_after) {
                result[result.length] = value;
                element_after = false;
            }
            // если выбранное условие выполняется, пишем в результат
            current_expression = get_expression( needle, value, flags.strict);
           if ( current_expression.exp) {
                counter ++;
                // если ищется элемент под определённым номером
                if ( current_expression.num) {
                    if ( counter == current_expression.num ) {
                        if ( flags.before) {
                            result[result.length] = element_before;
                        } else if ( flags.after) {
                            element_after = true;
                        } else {
                            result[result.length] = value;
                        }
                        break;
                    } else {
                        continue;
                    }
                } else {
                    result[result.length] = value;
                }
            }
            element_before = value;
		}
        if ( element_after) {
            result[result.length] = null;
        }
    }

	// определяем, является ли предыдущий запрошенный элемент массивом
	result = [];
	if ( root[0]) {
		for ( var i in root ) {
            get_concidence( element, root[i], flags);                
		}
	} else {
        get_concidence( element, root, flags);    
	}
    return result;
}

// аналог in_array
function in_obj( needle, haystack) {
    for ( var i in haystack) {
        if ( haystack[i] == needle) {
            return i;
        }
    }
    return -1;
}

// обрабатываем принудительные флаги
function accept_flag( root, flag) {
    if ( root == "") {
        root = null;
    }
    switch ( flag ) {
        // если результат должен быть массивом в любом случае
        case "array":
          
            if ( root && typeof root != "object") {
                root = [root];
            }

            break;
        default:
            // если массив с одним элементом, то вытаскиваем его из массива
            if ( root && root.length == 1) {
                root = root[0];
            }
    }
    return root;
}

// получаем элемент по css пути
function $( path, root) {
	root = root || document.body;
	if ( !path ) return root;
	// если в адресе есть id, то обрезаем адрес до него
    regexp = /^([^#]+)(#.+)$/; 
    path = trim( path).replace( regexp, "$2");
	path_elements = path.split( " ");
    flag = null;
    last_root = null;
	for ( var i in path_elements) {
        last_root = (root != "")? root : last_root;
        flags = {
            scanChildren: false,
            before: false,
            after: false,
            strict: false
        }
        if ( path_elements[i].indexOf( "!all") != -1) {
            flags.scanChildren = true;
            path_elements[i] = path_elements[i].replace( "!all", "");
        }
        if ( path_elements[i].indexOf( "!before") != -1) {
            flags.before = true;
            path_elements[i] = path_elements[i].replace( "!before", "");
        }
        if ( path_elements[i].indexOf( "!after") != -1) {
            flags.after = true;
            path_elements[i] = path_elements[i].replace( "!after", "");
        }
        if ( path_elements[i].indexOf( "!strict") != -1 ) {
            flags.strict = true;
            path_elements[i] = path_elements[i].replace( "!strict", "");
        }
        
		switch ( path_elements[i].substr( 0, 1) ) {  
			case ".":
				root = get_elements( path_elements[i], root, flags);
				break;
			case "#":
				root = document.getElementById( path_elements[i].substr( 1));
				break;
			case ":":
				alert( ":");
				break;
			case "!":
                flag = path_elements[i].substr( 1);
				break;
			default:
				root = get_elements( path_elements[i], root, flags);
		}
        if ( !root || root == "") {
            return undefined;
        }
	}
    return accept_flag( root, flag);
}

// удаляем дочерние элементы
function delete_children( node) {
	while ( node.firstChild) {
		node.removeChild( node.firstChild);
	}
}

// выдаём "координаты" ячейки в таблице
function get_table_coord( tdElement) {
    trElementArray = getElementsArray( tdElement.parentNode, "td");
    tableElementArray = getElementsArray( tdElement.parentNode.parentNode, "tr");
    row = 0;
	col = 0;

    for ( var i in tableElementArray) {
        // в этой ли строке находится искомый элемент?
        if ( tableElementArray[i] == tdElement.parentNode) {
            break;
        } else {
            row ++;
        }
    }
    
	for ( var i in trElementArray) {
	   // в этом ли столбце находится искомый элемент
        if ( trElementArray[i] == tdElement) {
            break;
        } else {
            col ++;
        }
	}

	return { "x": col, "y": row}
}

// переключатель видимости элемента ( если мягкий, то через visibility, иначе через display)
function show_element( element, soft) {
    soft = soft || false;
    if ( element.style.display == "none" && !soft || element.style.visibility == "hidden" && soft) {
        if ( !soft) {
            element.style.display = "block";
        }
        element.style.visibility = "visible";
    } else {
        if ( !soft) {
            element.style.display = "none";
        }
        element.style.visibility = "hidden";
    }
}

// закрыть элемент в курсоре
function hide_element_in_cursor() {
	if ( g_current_visible) {
		g_current_visible.style.display = "none";
	}
}

// скрыть элемент в курсоре через некоторое время
function close_element_in_cursor() {
	var timeOut = window.setTimeout( "hide_element_in_cursor()", 350);
}

// поместить абсолютный элемент в позицию курсора
function show_element_in_cursor( div, event) {
	hide_element_in_cursor();
	
 	if ( div && div.style.display == "none") {
		g_current_visible = div;
		div.style.display = "block";
	}
	var wwidth = (window.innerWidth)?window.innerWidth: ((document.all)?document.body.offsetWidth:null);
	if (!event) event=window.event;
	x = (event.clientX) ?  event.clientX : ((event.pageX) ? event.pageX : event.screenX);
	y = (event.clientY) ? (event.clientY + 15) : (event.pageY + 15);
	if ( document.documentElement.scrollTop == 0) {
		y += document.body.scrollTop;
	} else {
		y += document.documentElement.scrollTop;
	}
	
	if ( x + div.offsetWidth + 20 >= wwidth) {
		x -= div.offsetWidth;
	}

	div.style.top = y+"px";
	div.style.left = x+"px";
}

// отцентровать элемент по вертикали и горизонтали
function centre_element( element) {
    if ( !g_all_width) {
        g_all_width = document.body.scrollWidth || document.documentElement.scrollWidth;
    }
    if ( !g_all_height) {
        g_all_height = ( document.documentElement.offsetHeight < document.body.offsetHeight) ? document.documentElement.offsetHeight: document.body.offsetHeight;
    }
    if ( !g_all_scroll) {
       g_all_scroll = document.body.scrollHeight || document.documentElement.scrollHeight;
    }
    element.style.left = Math.ceil( g_all_width / 2 - element.offsetWidth / 2);
    element.style.top = Math.ceil( g_all_height / 2 - element.offsetHeight / 2) + ( !document.documentElement.scrollTop ? document.body.scrollTop : document.documentElement.scrollTop);
}

// обрезаем начальные и конечные пробелы
function trim( string) {
	var regExp = /^(\s)*(.*)(\s)*$/;
	return string.match( regExp)[2];
}

// получаем значение кук  и помещаем их в глобальный массив $_COOKIE
$_COOKIE = new Object();
paar_array = document.cookie.split( ";");

for ( var i in paar_array ) {
	current_values = paar_array[i].split( "=");
	$_COOKIE[trim(current_values[0])] = current_values[1];
}

function get_table( obj, level, stop_level) {
	stop_level = stop_level || 1;
    txt = "<table border=1 style='width:400px; color:inherit; border-collapse:collapse'>";
    for ( var i in obj ) {
        if ( typeof( obj[i]) == "object"  && level < stop_level) {
            txt += "<tr><td width=10%>"+ i +"<span style='color:gray'>("+ typeof( i) +")</span></td><td width=90% style='overflow:auto'>"+ get_table( obj[i], ( level + 1)) +"</td></tr>";
        } else {
            txt += "<tr><td width=10%>"+ i +"<span style='color:gray'>("+ typeof( i) +")</span></td><td width=90% style='overflow:auto'>"+ obj[i] +"</td></tr>";
        }
    }
    txt += "</table>";
    return txt;
}

function get_alert( obj, level, stop_level) {
	stop_level = stop_level || 1;
    txt = "";
    paddingLeft = "";
    for ( var i = 0; i < level; i++) {
        paddingLeft += "\t\t";
    }
    for ( var i in obj ) {
        if ( typeof( obj[i]) == "object" && level < stop_level) {
            txt += paddingLeft + i +" ("+ typeof( i) +"):\n"+ get_alert( obj[i], ( level + 1) ) +"\n";
        } else {
            txt += paddingLeft + i +"("+ typeof( i) +"):\t"+ obj[i] +"\n";
        }
    }
    return txt;
}

// вытаскиваем свойства объекта
function alert_obj( obj) {
    txt = get_table( obj);
    if ( txt.length < 6000) {
        txt = get_alert( obj, 0);
        alert( txt);
    } else {
        win = window.open();
        win.document.write( txt);
    }
}

// вместо body.onload=...
function bind_ready( handler_unload){
	var called = false
	function ready() {
		if (called) return
		called = true
		handler_unload();
	}
	if ( document.addEventListener ) {
		document.addEventListener( "DOMContentLoaded", function(){
			ready()
		}, false )
	} else if ( document.attachEvent ) {
		if ( document.documentElement.doScroll && window == window.top ) {
			function tryScroll(){
				if (called) return
				if (!document.body) return
				try {
					document.documentElement.doScroll("left")
					ready()
				} catch(e) {
					setTimeout(tryScroll, 0)
				}
			}
			tryScroll()
		}
		document.attachEvent( "onreadystatechange", function(){
			if ( document.readyState === "complete" ) {
				ready()
			}
		})
	}
    if (window.addEventListener) {
        window.addEventListener('load', ready, false)
    } else if (window.attachEvent) {
        window.attachEvent('onload', ready)
    }
    /*  else  // (4.1)
        window.onload=ready
	*/
}

// получаем порядковый номер элемента в родителе
function get_element_number( elem) {
    elements = $( elem.tagName.toLowerCase(), elem.parentNode);
    counter = 0;
    for ( var i in elements) {
        if ( elements[i] == elem) {
            return counter;
        }
        counter ++;
    }
    return false;
}

// position:fixed для ie 
function fixed(e, pos) {
    if (/MSIE (5\.5|6).+Win/.test(navigator.userAgent))
    {
        e.style.position = 'absolute';
        return (document.documentElement.scrollTop + pos) + 'px';
    }
    else e.style.top = pos + 'px';
}

// обработка событий
Event = (function() {

  var guid = 0

  function fixEvent(event, obj) {
	event = event || window.event

    if ( event.isFixed ) {
      return event
    }
    event.isFixed = true

    event.preventDefault = event.preventDefault || function(){this.returnValue = false}
    event.stopPropagation = event.stopPropagaton || function(){this.cancelBubble = true}
    if (!event.target) {
        event.target = event.srcElement;
    }
    if ( !event.currentTarget ) {
        event.currentTarget = obj;
    }
    if (!event.relatedTarget && event.fromElement) {
        event.relatedTarget = event.fromElement == event.target ? event.toElement : event.fromElement;
    }
    if ( event.pageX == null && event.clientX != null ) {
        var html = document.documentElement, body = document.body;
        event.pageX = event.clientX + (html && html.scrollLeft || body && body.scrollLeft || 0) - (html.clientLeft || 0);
        event.pageY = event.clientY + (html && html.scrollTop || body && body.scrollTop || 0) - (html.clientTop || 0);
    }

    if ( !event.which && event.button ) {
        event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
    }

    if ( !event.which && !event.button && event.keyCode) {
	event.which = event.keyCode;
    }

	return event
  }
  function commonHandle(event) {
    event = fixEvent(event, this)

    var handlers = this.events[event.type]

	for ( var g in handlers ) {
      var handler = handlers[g]

      var ret = handler.call(this, event)
      if ( ret === false ) {
          event.preventDefault()
          event.stopPropagation()
      }
    }
  }

  return {
    add: function(elem, type, handler) {
      if (elem.setInterval && ( elem != window && !elem.frameElement ) ) {
        elem = window;
      }

      if (!handler.guid) {
        handler.guid = ++guid
      }

      if (!elem.events) {
        elem.events = {}
		elem.handle = function(event) {
		  if (typeof Event !== "undefined") {
			return commonHandle.call(elem, event)
		  }
        }
      }

		if ( !elem.events) return
      if (!elem.events[type]) {
        elem.events[type] = {}

        if (elem.addEventListener)
		  elem.addEventListener(type, elem.handle, false)
		else if (elem.attachEvent)
          elem.attachEvent("on" + type, elem.handle)
      }

      elem.events[type][handler.guid] = handler
    },

    remove: function(elem, type, handler) {
	  if ( !elem || !elem.events) return
      var handlers = elem.events && elem.events[type]

      if (!handlers) return

      delete handlers[handler.guid]

      for(var any in handlers) return
	  if (elem.removeEventListener)
		elem.removeEventListener(type, elem.handle, false)
	  else if (elem.detachEvent)
		elem.detachEvent("on" + type, elem.handle)

	  delete elem.events[type]


	  for (var any in elem.events) return
	  try {
	    delete elem.handle
	    delete elem.events
	  } catch(e) { // IE
	    elem.removeAttribute("handle")
	    elem.removeAttribute("events")
	  }
    }
  }
}())

// перетаскивание элемента
Drag = {
    current: {
        obj         : null,
        inside_x    : null,
        inside_y    : null,
        cursor      : null
    },
    start: function( event) {
        event.preventDefault();
        event.stopPropagation();
        if ( event.target.tagName.toLowerCase() != "input" && event.target.tagName.toLowerCase() != "textarea" && event.target.tagName.toLowerCase() != "textarea" && event.target.scrollHeight <= event.target.offsetHeight) {
            Drag.current.inside_x = event.clientX - event.currentTarget.offsetLeft;
            Drag.current.inside_y = event.clientY - event.currentTarget.offsetTop;
			Drag.current.last_x = null;
			Drag.current.last_y = null;
            Drag.current.obj = event.currentTarget;
            Drag.current.cursor = !event.currentTarget.style.cursor || event.currentTarget.style.cursor == "move" ? "default" : event.currentTarget.style.cursor;
            event.currentTarget.style.cursor = "move";
        }
    },
    process: function( event) {
        if ( Drag.current.obj == event.currentTarget && event.target.tagName.toLowerCase() != "input" && event.target.tagName.toLowerCase() != "textarea" && event.target.scrollHeight <= event.target.offsetHeight) {
            if ( event.currentTarget.offsetWidth < document.body.offsetWidth ) {
                xExp = (event.clientX + event.currentTarget.offsetWidth - Drag.current.inside_x <= g_all_width && event.clientX - Drag.current.inside_x >= 0 );
                yExp = (event.clientY + event.currentTarget.offsetHeight - Drag.current.inside_y <= g_all_scroll && event.clientY - Drag.current.inside_y >= 0);
            } else {
				/*
                ((event.clientX > Drag.current.last_x) && -event.currentTarget.offsetLeft + document.body.offsetWidth < event.currentTarget.offsetWidth) ||
                
                 (event.currentTarget.offsetLeft + (event.clientX - Drag.current.inside_x) > 0 && !(event.clientX < Drag.current.last_x)) ||
                 
				*/
				
				
                xExp = (
	                (-event.currentTarget.offsetLeft + document.body.offsetWidth < event.currentTarget.offsetWidth && event.currentTarget.offsetLeft <= 0) ||
	                (!(-event.currentTarget.offsetLeft + document.body.offsetWidth < event.currentTarget.offsetWidth) && event.clientX > Drag.current.last_x) ||
	                (!(event.currentTarget.offsetLeft <= 0)  && !(event.clientX > Drag.current.last_x))
                );
				Drag.current.last_x = event.clientX;
                yExp = (document.body.offsetHeight - (event.clientY - Drag.current.inside_y) < event.currentTarget.offsetHeight && event.currentTarget.offsetTop + (event.clientY - Drag.current.inside_y) <= 0);
            }
            if ( xExp && !Drag.current.clearBorder || true && Drag.current.clearBorder) {
				if ( event.clientX - Drag.current.inside_x < (tmp = -event.currentTarget.offsetWidth + document.body.offsetWidth)) {
	                event.currentTarget.style.left = tmp + "px";
				} else if ( event.clientX - Drag.current.inside_x > 0) {
					event.currentTarget.style.left = "0px";
				} else {
					 event.currentTarget.style.left = (event.clientX - Drag.current.inside_x) + "px";
				}
	        }
            if ( yExp && !Drag.current.clearBorder || true && Drag.current.clearBorder) {
                event.currentTarget.style.top = (event.clientY - Drag.current.inside_y) + "px";
            }
        }
    },
    stop: function( event) {
        Drag.current.obj = null;
        event.currentTarget.style.cursor = Drag.current.cursor;
    },
/*    out: function( event) {
        if ( event.relatedTarget == Drag.current.obj) {
            Drag.current.obj = null;
            event.currentTarget.style.cursor = Drag.current.cursor;
        }
    },*/
    add: function( element, clearBorder) {
        Drag.current.clearBorder = clearBorder || false;
        if ( !g_all_width) {
            g_all_width = document.body.scrollWidth || document.documentElement.scrollWidth;
        }
        if ( !g_all_scroll) {
           g_all_scroll = document.body.scrollHeight || document.documentElement.scrollHeight;
        }
        Event.add( element, "mousedown", Drag.start); 
        Event.add( element, "mouseup", Drag.stop); 
        Event.add( element, "mouseout", Drag.stop); 
        Event.add( element, "mousemove", Drag.process); 
    },
    remove: function( element) {
        Event.remove( element, "mousedown", Drag.start); 
        Event.remove( element, "mouseup", Drag.stop); 
        Event.remove( element, "mouseout", Drag.stop); 
        Event.remove( element, "mousemove", Drag.process); 
    }
}

// спёртая откуда-то функция определения абсолютного положения элемента
function getOffsetSum(elem) {
	var top=0;
	var left = 0;
	while ( elem) {
		top = top + parseInt(elem.offsetTop);
		left = left + parseInt(elem.offsetLeft);
		elem = elem.offsetParent;
	}
	    return {top: top, left: left}
}

function getOffsetRect(elem) {
	var box = elem.getBoundingClientRect();
	
	var body = document.body;
	var docElem = document.documentElement;
	
	var scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop;
	var scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft;
	
	var clientTop = docElem.clientTop || body.clientTop || 0;
	var clientLeft = docElem.clientLeft || body.clientLeft || 0;
	
	var top  = box.top +  scrollTop - clientTop;
	var left = box.left + scrollLeft - clientLeft;
	
	return { top: Math.round(top), left: Math.round(left) }
}

function getOffset(elem) {
	if ( elem.getBoundingClientRect) {
		return getOffsetRect(elem);
	} else {
		return getOffsetSum(elem);
	}
}

// бессмертная функция наследования
function extend(Child, Parent) {
	var F = function() { }
	F.prototype = Parent.prototype
	Child.prototype = new F()
	Child.prototype.constructor = Child
	Child.superclass = Parent.prototype
}

// предзагрузчик картинок
g_img_preload = {};
function preload_img( img) {
	if ( img.length) {
		for ( var i in img) {
			g_img_preload[img[i]] = new Image();
			g_img_preload[img[i]].src = img[i];
		}
	} else {
		g_img_preload[img] = new Image();
		g_img_preload[img].src = img[i];
	}
}


Animation = function () {
	// глобальный идентификатор
	var guid = 0;
	// текущий таймаут
	var timeoutHolder = null;
	// массив временных меток
	var timeline = [];
	// массив идентификаторов, раскиданных по временным меткам
	var objTimePlacement = {};
	// текущая задержка
	var delay = null;
	// объекты
	var objects = {};
	
	// функция сортировки по временной метке
	var commonSort = function( a, b) {
		return parseInt( a) - parseInt( b);
	}

	// добавление нового объекта
	function init( prop) {
		// создаём новый объект, над которым надо издеваться
		objects[prop.id] = {
			"id":					prop.id,
			"element": 		prop.element,
			"property": 		prop.property,
			"framesArr": 	prop.framesArr,
			"period": 			prop.period,
			"funcArr":		prop.funcArr,
			"counter": 		0
		}
		// останавливаем текущий таймаут, если он существует
		if ( timeoutHolder ) {
			window.clearTimeout( timeoutHolder);
		}
		timestamp = Math.floor( new Date().getTime() / 10) * 10;

		// если на текущей метки нет на временной шкале, то добавляем
		if ( !objTimePlacement[timestamp]) {
			timeline.push( timestamp);
			timeline.sort( commonSort);
			objTimePlacement[timestamp] = [];
		}

		// дописываем необходимое действие на указанное время
		objTimePlacement[timestamp].push( prop.id);
// 		$( "#console").innerHTML += "<h3 style='color:inherit; margin-bottom:0'>" + timestamp + ":</h3>" + get_table( objTimePlacement);
		
		// возвращаем ссылку на созданный объект
		return objects[prop.id];
	}
	
	function play() {
		// достаём ближайшую временную метку с шкалы и смотрим, что нужно в этот момент времени сделать
		var timestamp = timeline.shift();
		// если есть запись, связанная с данной временной меткой
		if ( tmp = objTimePlacement[timestamp] ) {
			// перебираем задачи на данную временную метку и выполняем их
			for (  var i in tmp ) {
				currentObj = objects[tmp[i]];
				if ( !currentObj ) continue;
				// обрабатываем специфичные css-свойства
				switch ( currentObj.property) {
					case "opacity":
						if ( g_is_ie ) {
							currentObj.element.style.filter = "alpha( opacity=" + parseInt( currentObj.framesArr[currentObj.counter].value * 100) + ")";
							for ( var j in currentObj.element.childNodes) {
								if ( currentObj.element.childNodes[j].nodeType == 1) {
									currentObj.element.childNodes[j].style.filter = "alpha( opacity=" + parseInt( currentObj.framesArr[currentObj.counter].value * 100) + ")";
								}
							}
						} else {
							currentObj.element.style.opacity = currentObj.framesArr[currentObj.counter].value;
						}
						break;
					case "backgroundPositionX":
						tmp = currentObj.element.style.backgroundPosition.split( " ")[1];
						currentObj.element.style.backgroundPosition = currentObj.framesArr[currentObj.counter].value + " " + tmp;
						break;
					case "backgroundPositionY":
						tmp = currentObj.element.style.backgroundPosition.split( " ")[0];
						currentObj.element.style.backgroundPosition = tmp + " " + currentObj.framesArr[currentObj.counter].value;
						break;
					default:
						currentObj.element.style[currentObj.property] = currentObj.framesArr[currentObj.counter].value;
				}

				if ( currentObj.framesArr[currentObj.counter].func ) currentObj.framesArr[currentObj.counter].func.call( this);

				// если путь данного объекта не закончен, то записываем его на временную шкалу
				if ( currentObj.counter < currentObj.framesArr.length - 1) {
					// если нет записанных на данное время объектов, то пишем на временную шкалу и создаём массив для хранения объектов
					nextTimestamp = timestamp + currentObj.period;
					if ( !objTimePlacement[nextTimestamp]) {
						timeline.push( nextTimestamp);
						objTimePlacement[nextTimestamp] = [];
					}
					// привязываем объект к времени на шкале
					objTimePlacement[nextTimestamp].push( tmp[i]);
					// инкремент счётчика текущего объекта
					currentObj.counter ++;
				} else {
					// если путь элемента закончен, то удаляем объект его движения
					delete objects[tmp[i]];
// 					$( "#console").innerHTML = get_table( objects);
				}
			}
		}
//  		$( "#console").innerHTML = get_table( objects);
		// если есть записи на временной шкале, то перезапускаем функцию через нужный таймаут
		if ( timeline.length > 0 ) {
			// сортируем по возрастанию времени
			timeline.sort( commonSort);
			// расчитываем задержку
			delay = timeline[0] - timestamp;
			// перезапускаем функцию через timeout
			timeoutHolder = window.setTimeout( play, delay);
		} else {
			// останавливаем анимацию
			stop();
		}
		
	}
	
	function stop() {
		// удаляем таймаут
		window.clearTimeout( timeoutHolder);
		// обнуляем глобальный идентификатор
		var guid = 0;
		// обнуляем массив временных меток
		var timeline = [];
		// обнуляем массив идентификаторов, раскиданных по временным меткам
		var objTimePlacement = {};
		// обнуляем массив объектов
		var objects = {};
	}

	return {
		add: function() {
			for ( var e = 0; e < arguments.length; e ++ ) {
				prop = arguments[e];
				element =		prop.element || prop.elem || prop.object || prop.obj;
				if ( !element ) {
					throw "element is undefined!";
				}
				currentID =		++ guid;

				duration = 		prop.time || 3000;
				points = 			prop.points || 20;
				property = 		prop.property || prop.prop;
				motionType = 	prop.type || "uniform";
				funcArr = [];
				if ( prop.func ) {
					if ( !(prop.func instanceof Array) ) prop.func = [prop.func]; 
					for ( var i in prop.func ) {
						funcArr.push( {
							"property": 	prop.func[i].prop,
							"value":		(prop.func[i].value.replace) ? parseFloat( prop.func[i].value.replace( /%|px/, "")) : prop.func[i].value,
							"func":			prop.func[i].func,
							"charge":		1
						});
					}
				}
				begin = 			prop.begin || "curr0ent";
				end = 				prop.delta ? null : prop.end;
				distance = 		!prop.distance ? null : prop.delta;
				temp = 			begin || end || delta;
				temp = 			(temp + "").match( /[0-9]+([^0-9]+)$/);
				dimension = 	temp ? temp[1] : "";
				
				if ( !element.animation) {
					element.animation = {};
					element.animation.length = 0;
				}
				
				// считаем массив 
				startValue 		= parseFloat( begin);
				endValue 			= parseFloat( end);
				conditionFlag	= startValue > endValue ? 1 : -1;						// используется для определения, достигнута ли конечная точка
				framesArr		= new Array();
				
				// создаём массив "кадров" согласно выбранному закону изменения
				switch ( motionType ) {
					case "uniform":
						delta = (endValue - startValue) / points;
						for ( var i = 0; i <= points; i ++) {
							cPoint = startValue + delta * i;
							cFunc = null;
							for ( var k in funcArr ) {
								condition = (
									funcArr[k].charge
									&&
										(
											funcArr[k].property != "ready"
											&&
											(cPoint - funcArr[k].value) * conditionFlag <= 0
												||
											funcArr[k].property == "ready"
											&&
											(Math.round(( i / points) * 100 ) - funcArr[k].value) * conditionFlag <= 0
										)
								);
								if ( condition ) {
									funcArr[k].charge = 0;
									cFunc = funcArr[k].func;
								}
							}
							if ( (cPoint -  endValue) * conditionFlag <= 0 ) {
								framesArr.push( {"value": endValue + dimension, "func": cFunc});
								break;
							} else {
								framesArr.push( {"value": cPoint + dimension, "func": cFunc});
							}
						}
						break;
					case "acceleration":
						break;
					case "deceleration":
						acc = 2 * (endValue - startValue) / Math.pow( points, 2);
						cDeltaobjects = acc * points; 
						cPoint = startValue;
						for ( var i = 0; i <= points; i++) {
							if ( (cPoint -  endValue) * conditionFlag <= 0 ) {
								framesArr.push( endValue + dimension);
								break;
							} else {
								framesArr.push( cPoint + dimension);
							}
							cPoint += Math.round( cDelta);
							cDelta -= acc; 
						}
						break;
					case "parabola":
						break;
					case "reverse_parabola":
						break;
				}
				element.animation[currentID] = init( {"id": currentID, "element": element, "property": property, "framesArr": framesArr, "period": duration / points});
				element.animation[property] = []; 
				element.animation[property].push( element.animation[currentID]);
				element.animation.length ++;
			}
			play();
		},
		// удаляем объект
		remove: function() {
			for ( var e = 0; e < arguments.length; e ++ ) {
				var prop = arguments[e];
				var currentID = 	prop.id || null;
				var element = 	prop.element || prop.elem || prop.object || prop.obj;
				var property = 	prop.property || prop.prop;

				// если задан id
				if ( currentID && objects[currentID]) {
					if ( objects[currentID].element.animation.length == 1 ) {
						delete objects[currentID].element.animation;
					}
					delete objects[currentID];
				} else if ( element && property && element.animation && element.animation[property] ) {
					for ( var i in element.animation[property]) {
						delete objects[element.animation[property][i].id];
					}
					element.animation.length -= element.animation[property].length;
					delete element.animation[property];
					if ( element.animation.length == 0) {
						delete element.animation;
					}
				} else if ( element && !property && element.animation ) {
					for ( var i in element.animation) {
						delete objects[element.animation[i].id];
					}
					delete element.animation;
				}
			}
		}
	}
}();
