function zoom(el, grow, k) {
 if (typeof(el) == 'string') el = document.getElementById(el);
 if (grow == undefined)
  grow = ('grow' in el ? (el.grow == '+' ? '-' : '+') 
                       : (el.offsetHeight > 0 ? '-' : '+'));
 el.grow = grow;
 el.zoomStep = k || 2;
 el.style.overflow = 'hidden';
 if (grow == '+') {
  if (!('zoomCurValue' in el) || el.zoomCurValue == 0) el.zoomCurValue = 1;
  el.style.display = '';
  if ('zoomMaxHeight' in el) el.zoomNewValue = el.zoomMaxHeight;
  else {
    el.style.height = '';
    el.zoomNewValue = el.offsetHeight;
  }
 } else {
  if (!('zoomCurValue' in el)) el.zoomCurValue = el.offsetHeight;
  if (!('zoomMaxHeight' in el)) el.zoomMaxHeight = el.offsetHeight;
  el.zoomNewValue = 0;
 }
 el.style.height = el.zoomCurValue+'px';
 if (!el.zoomIntervalId) el.zoomIntervalId = setInterval('stepZooming("'+el.id+'")', 50);
}

function stepZooming(id) {
 var el = document.getElementById(id);
 var d = (el.grow == '+' ? el.zoomCurValue * el.zoomStep : el.zoomCurValue / el.zoomStep);
 var finished = false;
 if (el.grow == '+' && d > el.zoomNewValue) {
  d = el.zoomNewValue;
  finished = true;
 } else if (el.grow != '+' && d < 1) {
  d = 0;
  finished = true;
 }
 el.zoomCurValue = d;
 el.style.height = d+'px';
 if (finished) {
  clearInterval(el.zoomIntervalId);
  el.zoomIntervalId = null;
  if (d == 0) el.style.display = 'none';
  else {
    el.style.overflow = '';
    el.style.height = '';
  }
  if (el.afterZoom) el.afterZoom();
 }
}


//-- common -----------------------------------------------

if (!('$' in window)) {
  $ = function(el) {
    return (typeof(el) == 'string' ? document.getElementById(el) : el);
  }
}

if (!Array.prototype.indexOf) {
  Array.prototype.indexOf = function(item, first) {
    for (var i = first || 0; i < this.length; i++) {
      if (this[i] == item) return i;
    }
    return -1;
  }
}

Array.prototype.addItem = function(item) {
  var i = this.indexOf(item);
  if (i < 0) {
    i = this.length;
    this.push(item);
  }
  return i;
}

Array.prototype.removeItem = function(item) {
  var i = this.indexOf(item);
  if (i >= 0) this.removeItemByIndex(i, true);
  return i;
}

Array.prototype.removeItemByIndex = function(i, shrink) {
  if (shrink) {
    if (this.splice) this.splice(i, 1)
    else {
      for (var j = i+1; j < this.length; j++) this[j-1] = this[j];
      this.length = this.length-1;
    }
  } else {
    if (i < this.length-1) this[i] = null
    else {
      do { this.length = (i--) } while (i >= 0 && this[i] == null);
    }
  }
  return true;
}


Function.prototype.funcName = function() {
  return (/function\s*(\S*)\s*\(/.test(this.toString()) ? RegExp.$1 : '');
}

function isTypeOf(prm, type) {
  return (prm && prm.constructor && prm.constructor.funcName() == type);
}

function isNumber(prm) {
  return isTypeOf(prm, 'Number');
}

function isString(prm) {
  return isTypeOf(prm, 'String');
}

function isFunction(prm) {
  return isTypeOf(prm, 'Function');
}

function isArray(prm) {
  return isTypeOf(prm, 'Array');
}

function callEventHandler(method, object) {
  if (method == null)     return false;
  if (!object) object = window;
  if (isFunction(method)) return method.call(object);
  if (isArray(method))    return method[0].apply(object, method.slice(1));
  if (isString(method))   return eval(method);
  warn('callEventHandler('+method+'): unsupported method type: '+method.constructor);
  return false;
}

function warn(s) {
  alert(s);
}

function setupEvent(el, eventType, handler, capture) {
  if (el.attachEvent) el.attachEvent('on'+eventType, handler)
  else if (el.addEventListener) el.addEventListener(eventType, handler, capture)
}

function removeEvent(el, eventType, handler, capture) {
  if (el.detachEvent) el.detachEvent('on'+eventType, handler)
  else if (el.removeEventListener) el.removeEventListener(eventType, handler, capture)
}

var
  mouseMoveListeners = [],
  mouseAbsPosX = 0,
  mouseAbsPosY = 0;

function addMouseMoveListener(handler, object) {
  var i, o;
  for (i = 0; i < mouseMoveListeners.length; i++) {
    o = mouseMoveListeners[i];
    if (o.method == handler && o.instance == object) return false;
  }
  if (mouseMoveListeners.length == 0)
    setupEvent(document, 'mousemove', mouseMoveEventHandler);
  mouseMoveListeners.push({method: handler, instance: object});
  return true;
}

function removeMouseMoveListener(handler, object) {
  var i, o;
  for (i = 0; i < mouseMoveListeners.length; i++) {
    o = mouseMoveListeners[i];
    if (o.method == handler && o.instance == object) {
      mouseMoveListeners.splice(i, 1);
      if (mouseMoveListeners.length == 0)
        removeEvent(document, 'mousemove', mouseMoveEventHandler);
      return;
    }
  }
}

function mouseMoveEventHandler(event) {
  mouseAbsPosX = event.clientX + document.body.scrollLeft;
  mouseAbsPosY = event.clientY + document.body.scrollTop;
  var i, o;
  for (i = 0; i < mouseMoveListeners.length; i++) {
    o = mouseMoveListeners[i];
    if (o.method) o.method.call(o.instance || window, event);
  }
}

var DEF_FADE_TIME    = 200;

var DEF_FADE_STEPS   = 4;

var DEF_DISPLAY_MODE = 'block';

function fadeIn(el, time, steps, displayMode) {
  fade(el, 'in', time, steps, displayMode);
}

function fadeOut(el, time, steps, displayMode) {
  fade(el, 'out', time, steps, displayMode);
}

// fade() v. 1.0
//  [how]: 'in' / 'out' (default)
//  [time]: мсек.
//  [steps]: кол-во шагов
//  [displayMode]: режим работы с блоками
//
var
  fadingElements = [];

function fade(el, how, time, steps, displayMode) {
  el = $(el);
  if (time == undefined) time = DEF_FADE_TIME;
  if (steps == undefined) steps = DEF_FADE_STEPS; else if (steps <= 0) steps = 1;
  if (el.fadeIntervalId) {
    clearTimeout(el.fadeIntervalId);
    el.fadeIntervalId = null;
  }
  el.fadeStep = ((how == 'in' ? 1 : -1)/steps);
  if (!('fadeValue' in el)) el.fadeValue = (how == 'in' ? 0 : 1);
  el.fadeValue += el.fadeStep;
  el.fadeDisplayMode = displayMode; 
  if (hasAlpha(el)) setAlpha(el, el.fadeValue);
  if (!el.alphaMode || steps == 1) {
    setVisible(el, how == 'in', displayMode);
    if (el.afterFade) callEventHandler(el.afterFade, el);
    if (el.afterFadeEnd) {
      var i = fadingElements.addItem(el);
      setTimeout('callAfterFadeEnd('+i+')', 0);
    }
    return;
  }
  if (how == 'in') setVisible(el, true, displayMode);
  var i = fadingElements.addItem(el);
  el.fadeIntervalId = setInterval('stepFading('+i+')', time/(steps-1));
}

function stepFading(i) {
  var el = fadingElements[i];
  el.fadeValue += el.fadeStep;
  if (el.fadeValue > 1) el.fadeValue = 1; else if (el.fadeValue < 0) el.fadeValue = 0;
  setAlpha(el, el.fadeValue);
  if (el.fadeValue <= 0 || el.fadeValue >= 1) {
    clearInterval(el.fadeIntervalId);
    el.fadeIntervalId = null;
    if (el.fadeValue <= 0) setVisible(el, false, el.fadeDisplayMode);
    if (el.afterFade) callEventHandler(el.afterFade, el);
    if (el.afterFadeEnd) setTimeout('callAfterFadeEnd('+i+')', 0);
    else fadingElements.removeItemByIndex(i);
  }
}

function callAfterFadeEnd(i) {
  var el = fadingElements[i];
  fadingElements.removeItemByIndex(i);
  callEventHandler(el.afterFadeEnd, el);
}


// hasAlpha() v. 1.0
//  возвращает: 'filter' / 'opacity' / false
//  запоминает состояние в свойстве alphaMode
//
function hasAlpha(el) {
  if (!('alphaMode' in el))
    el.alphaMode = ('filter' in el.style ? 'filter' :
                     ('opacity' in el.style ? 'opacity' : false));
  return el.alphaMode;
}

function getAlpha(el) {
  return ('alphaValue' in el ? el.alphaValue : 1);
}

function setAlpha(el, value) {
  if (el.alphaValue != value) {
    if (value < 0.01) value = 0;
    else if (value > 0.99) value = 1;
    el.alphaValue = value;
    if (!('alphaMode' in el)) hasAlpha(el);
    if (el.alphaMode == 'filter') 
      el.style.filter = 'alpha(opacity='+Math.round(el.alphaValue*100)+')';
    else if (el.alphaMode == 'opacity')
      el.style.opacity = el.alphaValue;
  }
}


function setVisible(el, value, displayMode) {
  if (displayMode != undefined) el.displayMode = displayMode;
  else if ('displayMode' in el) displayMode = el.displayMode;
  else displayMode = DEF_DISPLAY_MODE;
  if (displayMode != false) {
    if (displayMode == 'visible') el.style.visibility = (value ? 'visible' : 'hidden');
    else el.style.display = (value ? (displayMode == 'blank' ? '' : displayMode) : 'none');
  }
}


//-- Hint --------------------------------------
// v. 2.1, 02.03.2007

function Hint(type, defContent, w, h) {
  this.hintContainer = document.createElement('DIV');
//if (id) this.hintContainer.id = id;
  this.hintContainer.className = 'hint';
  this.type = type || 'text';
  this.defContent = defContent;
  this.width = w;
  this.height = h;
  this.inBody = false;
  this.visible = false;
  this.show = Hint_show;
  this.hide = Hint_hide;
  this.updatePos = Hint_updatePos;
  if (mouseMoveListeners.length == 0) addMouseMoveListener(null);  // add null handler to know mouse pos
}

function Hint_show(content) {
  if (content == null) content = this.defContent;
  var hintCont = this.hintContainer;
//hintCont.style.display = 'block';
//hintCont.style.visibility = 'hidden';

  hintCont.innerHTML = 
   '<table border="0" cellspacing="0" cellpadding="0"><tr><td>'+
     (this.type == 'image' ?
       '<img src="'+content+'"'+(this.w ? ' width="'+this.w+'"' : '')+(this.height ? ' height="'+this.height+'"' : '')+'>' :
       '<b></b><div>'+content+'</div><b></b>') +
   '</td></tr></table>';

  if (this.visible) return;
  hintCont.hintObject = this;
  if (!this.mouseHandlerAdded) {
    addMouseMoveListener(Hint_updatePos, this);
    this.mouseHandlerAdded = true;
  }

  if (!this.inBody) {
    document.body.appendChild(hintCont);
    this.inBody = true;
  }
  if (this.zIndex) hintCont.style.zIndex = this.zIndex;
  if (this.width) hintCont.style.width = this.width;
  if (this.fastShow) 
    setVisible(hintCont, true)
  else
    fade(hintCont, 'in');
  this.visible = true;
  var t = hintCont.getElementsByTagName('TABLE')[0];
  this.hintWidth = t.offsetWidth;
  this.hintHeight = t.offsetHeight;
  this.updatePos();
}

function Hint_hide(how) {
  if (!this.visible) return;
  this.visible = false;
  var hintCont = this.hintContainer;
  if (how == 'fast' || this.fastShow) {
    setVisible(hintCont, false);
    if (this.mouseHandlerAdded) {
      removeMouseMoveListener(Hint_updatePos, this);
      this.mouseHandlerAdded = false;
    }
    hintCont.hintObject = null;
  } else {
    if (this.mouseHandlerAdded) {
      hintCont.afterFade = function() {
        this.afterFade = null;
        var obj = this.hintObject;
        this.hintObject = null;
        obj.mouseHandlerAdded = false;
        removeMouseMoveListener(Hint_updatePos, obj);
      }
    }
    fade(hintCont, 'out');
  }
}

function Hint_updatePos() {
  var body = document.body;
  var d = (body.clientWidth + body.scrollLeft) - this.hintWidth;
  var x = Math.min(mouseAbsPosX, d);
  var y = mouseAbsPosY + 20;
  var h = this.hintHeight;
  var ch = body.clientHeight + body.scrollTop;
  if (y + h >= ch) y = Math.min(mouseAbsPosY, ch) - h;
  this.hintContainer.style.left = x;
  this.hintContainer.style.top  = y;
}

var
  hint = new Hint('');

