// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// Contributors:
//  Justin Palmer (http://encytemedia.com/)
//  Mark Pilgrim (http://diveintomark.org/)
//  Martin Bialasinki
// 
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/ 
// converts rgb() and #xxx to #xxxxxx format,  
// returns self (or first argument) if not convertable  
String.prototype.parseColor = function()
   {var color = '#';
    if (this.slice(0, 4) == 'rgb(')
       {var cols = this.slice(4, this.length - 1).split(',');
        var i = 0;
        do 
           {color += parseInt(cols[i]).toColorPart();
           }while ( ++ i < 3);
       }
    else 
       {if (this.slice(0, 1) == '#')
           {if (this.length == 4) for (var i = 1;i < 4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();
            if (this.length == 7) color = this.toLowerCase();
           }
       }
    return (color.length == 7 ? color: (arguments[0] || this));
   };/*--------------------------------------------------------------------------*/

Element.collectTextNodes = function(element)
   {return $A($(element).childNodes).collect(function(node)
       {return (node.nodeType == 3 ? node.nodeValue: (node.hasChildNodes() ? Element.collectTextNodes(node): ''));
       }).flatten().join('');
   };

Element.collectTextNodesIgnoreClass = function(element, className)
   {return $A($(element).childNodes).collect(function(node)
       {return (node.nodeType == 3 ? node.nodeValue: ((node.hasChildNodes() && ! Element.hasClassName(node, className)) ? Element.collectTextNodesIgnoreClass(node, className): ''));
       }).flatten().join('');
   };

Element.setContentZoom = function(element, percent)
   {   element = $(element);
    element.setStyle(    {fontSize: (percent / 100) + 'em'});
    if (Prototype.Browser.WebKit)    window.scrollBy(0, 0);
    return element;
   };

Element.getInlineOpacity = function(element)
   {return $(element).style.opacity || '';
   };

Element.forceRerendering = function(element)
   {try 
       {element = $(element);
        var n = document.createTextNode(' ');
        element.appendChild(n);
        element.removeChild(n);
       }
   catch (e){}
   };/*--------------------------------------------------------------------------*/
var Effect = {_elementDoesNotExistError: {name: 'ElementDoesNotExistError', message: 'The specified DOM element does not exist, but is required for this effect to operate' }, 
  Transitions: 
{linear: Prototype.K, sinoidal: function(pos)
   {
   return ( - Math.cos(pos * Math.PI) / 2) + 0.5;
   }
, reverse: function(pos)
   {
   return 1 - pos;
   }
, flicker: function(pos)
   {var pos = (( - Math.cos(pos * Math.PI) / 4) + 0.75) + Math.random() / 4;
   
    return pos > 1 ? 1: pos;
   }
, wobble: function(pos)
   {
   return ( - Math.cos(pos * Math.PI * (9 * pos)) / 2) + 0.5;
   }
, pulse: function(pos, pulses)
   {pulses = pulses || 5;
   
    return (((pos % (1 / pulses)) * pulses).round() == 0 ? ((pos * pulses * 2) - (pos * pulses * 2).floor()): 1 - ((pos * pulses * 2) - (pos * pulses * 2).floor()));
   }
, spring: function(pos)
   {
   return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp( - pos * 6));
   }
, none: function(pos)
   {
   return 0;
   }
, full: function(pos)
   {return 1;
   }
}, 
  DefaultOptions: 
{duration: 1.0, // seconds
  fps: 100, // 100= assume 66fps max.
  sync: false, // true for combining
  from: 0.0, to: 1.0, delay: 0.0, queue: 'parallel' }, 
  tagifyText: function(element)
   {var tagifyStyle = 'position:relative';
    if (Prototype.Browser.IE) tagifyStyle +=';zoom:1';
    element = $(element);
    $A(element.childNodes).each(function(child)
       {if (child.nodeType == 3)
           {child.nodeValue.toArray().each(function(character)
               {element.insertBefore(new Element('span',  {style: tagifyStyle}).update(character == ' ' ? String.fromCharCode(160): character), child);
               });
           
            Element.remove(child);
           }
       });
   }
, 
  multiple: function(element, 
  effect)
   {var elements;
    if (((typeof element == 'object') || Object.isFunction(element)) && (element.length))elements = element;
    else elements = $(element).childNodes;
    var options = Object.extend( {speed: 0.1, delay: 0.0 }, arguments[2] || {});
    var masterDelay = options.delay;
    $A(elements).each(function(element, index)
       {new effect(element, Object.extend(options,  {delay: index * options.speed + masterDelay}));
       });
   }
, 
  PAIRS: 
{'slide':['SlideDown', 'SlideUp'], 'blind':['BlindDown', 'BlindUp'], 'appear':['Appear', 'Fade']}, 
  toggle: function(element, 
  effect)
   {   element = $(element);
    effect = (effect || 'appear').toLowerCase();
    var options = Object.extend( {queue:  {position: 'end', scope: (element.id || 'global'), limit: 1}}, arguments[2] || {});
    Effect[element.visible() ? Effect.PAIRS[effect][1]: Effect.PAIRS[effect][0]](element, options);
   }
};
Effect.DefaultOptions.transition = Effect.Transitions.sinoidal;/* ------------- core effects ------------- */
Effect.ScopedQueue = Class.create(Enumerable, 
{initialize: function()
   {this.effects =[];
   
    this.interval = null;
   }
, _each: function(iterator)
   {
   this.effects._each(iterator);
   }
, add: function(effect)
   {var timestamp = new Date().getTime();
    var position = Object.isString(effect.options.queue) ? effect.options.queue: effect.options.queue.position;
    switch (position)
       {case 'front': // move unstarted effects after this effect  
            this.effects.findAll(function(e)
               {               return e.state == 'idle';
               }).each(function(e)
               {e.startOn += effect.finishOn;
                e.finishOn += effect.finishOn;
               });
            break;
        case 'with-last': 
            timestamp = this.effects.pluck('startOn').max() || timestamp;
            break;
        case 'end': // start effect after last queued effect has finished
            timestamp = this.effects.pluck('finishOn').max() || timestamp;
            break;
       }
    effect.startOn += timestamp;
    effect.finishOn += timestamp;
    if ( ! effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))this.effects.push(effect);
    if ( ! this.interval)
   this.interval = setInterval(this.loop.bind(this), 15);
   }
, remove: function(effect)
   {this.effects = this.effects.reject(function(e)
       {return e == effect;
       });
    if (this.effects.length == 0)
       {clearInterval(this.interval);
       
        this.interval = null;
       }
   }
, loop: function()
   {var timePos = new Date().getTime();
    for (var i = 0, len = this.effects.length;i < len;i++)this.effects[i] && this.effects[i].loop(timePos);
   }
});
Effect.Queues = 
{instances: $H(), get: function(queueName)
   {if ( ! Object.isString(queueName)) return queueName;
    return this.instances.get(queueName) || this.instances.set(queueName, new Effect.ScopedQueue());
   }
};
Effect.Queue = Effect.Queues.get('global');
Effect.Base = Class.create(
{position: null, start: function(options)
   {
   function codeForEvent(options, eventName)
       {return ((options[eventName + 'Internal'] ? 'this.options.' + eventName + 'Internal(this);': '') + (options[eventName] ? 'this.options.' + eventName + '(this);': ''));
       }
    if (options && options.transition === false) options.transition = Effect.Transitions.linear;
    this.options = Object.extend(Object.extend({}, Effect.DefaultOptions), options || {});
    this.currentFrame = 0;
    this.state = 'idle';
    this.startOn = this.options.delay * 1000;
    this.finishOn = this.startOn + (this.options.duration * 1000);
    this.fromToDelta = this.options.to - this.options.from;
    this.totalTime = this.finishOn - this.startOn;
    this.totalFrames = this.options.fps * this.options.duration;
    eval ('this.render = function(pos){ ' + 'if (this.state=="idle"){this.state="running";' + codeForEvent(this.options, 'beforeSetup') + (this.setup ? 'this.setup();': '') + codeForEvent(this.options, 'afterSetup') + '};if (this.state=="running"){' + 'pos=this.options.transition(pos)*' + this.fromToDelta + '+' + this.options.from +';' + 'this.position=pos;' + codeForEvent(this.options, 'beforeUpdate') + (this.update ? 'this.update(pos);': '') + codeForEvent(this.options, 'afterUpdate') + '}}');
    this.event('beforeStart');
    if ( ! this.options.sync)
   Effect.Queues.get(Object.isString(this.options.queue) ? 'global': this.options.queue.scope).add(this);
   }
, loop: function(timePos)
   {if (timePos >= this.startOn)
       {if (timePos >= this.finishOn)
           {this.render(1.0);
            this.cancel();
            this.event('beforeFinish');
            if (this.finish) this.finish();
            this.event('afterFinish');
            return;
           }
        var pos = (timePos - this.startOn) / this.totalTime, frame = (pos * this.totalFrames).round();
        if (frame > this.currentFrame)
           {this.render(pos);
           
            this.currentFrame = frame;
           }
       }
   }
, cancel: function()
   {if ( ! this.options.sync)Effect.Queues.get(Object.isString(this.options.queue) ? 'global': this.options.queue.scope).remove(this);
   
    this.state = 'finished';
   }
, event: function(eventName)
   {if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
    if (this.options[eventName]) 
   this.options[eventName](this);
   }
, inspect: function()
   {var data = $H();
    for (property in this)if ( ! Object.isFunction(this[property])) data.set(property, this[property]);
    return '#<Effect:' + data.inspect() +',options:' + $H(this.options).inspect() + '>';
   }
});
Effect.Parallel = Class.create(Effect.Base, 
{initialize: function(effects)
   {this.effects = effects ||[];
   
    this.start(arguments[1]);
   }
, update: function(position)
   {
   this.effects.invoke('render', position);
   }
, finish: function(position)
   {this.effects.each(function(effect)
       {effect.render(1.0);
        effect.cancel();
        effect.event('beforeFinish');
        if (effect.finish) effect.finish(position);
        effect.event('afterFinish');
       });
   }
});
Effect.Tween = Class.create(Effect.Base, 
{initialize: function(object, from, to)
   {object = Object.isString(object) ? $(object): object;
    var args = $A(arguments), method = args.last(), options = args.length == 5 ? args[3]: null;
   
    this.method = Object.isFunction(method) ? method.bind(object): Object.isFunction(object[method]) ? object[method].bind(object): function(value)
       {object[method] = value;
       };
    this.start(Object.extend(
    {from: from, to: to}, options || {}));
   }
, update: function(position)
   {this.method(position);
   }
});
Effect.Event = Class.create(Effect.Base, 
{initialize: function()
   {this.start(Object.extend( {duration: 0}, arguments[0] || {}));
   }
, update: Prototype.emptyFunction });
Effect.Opacity = Class.create(Effect.Base, 
{initialize: function(element)
   {this.element = $(element);
    if ( ! this.element) throw (Effect._elementDoesNotExistError);// make this work on IE on elements without 'layout'
    if (Prototype.Browser.IE && ( ! this.element.currentStyle.hasLayout))this.element.setStyle( {zoom: 1});
    var options = Object.extend( {from: this.element.getOpacity() || 0.0, to: 1.0 }, arguments[1] || {});
   
    this.start(options);
   }
, update: function(position)
   {this.element.setOpacity(position);
   }
});
Effect.Move = Class.create(Effect.Base, 
{initialize: function(element)
   {this.element = $(element);
    if ( ! this.element) throw (Effect._elementDoesNotExistError);
    var options = Object.extend( {x: 0, y: 0, mode: 'relative' }, arguments[1] || {});
   
    this.start(options);
   }
, setup: function()
   {this.element.makePositioned();
    this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
    this.originalTop = parseFloat(this.element.getStyle('top') || '0');
    if (this.options.mode == 'absolute')
       {this.options.x = this.options.x - this.originalLeft;
       
        this.options.y = this.options.y - this.originalTop;
       }
   }
, update: function(position)
   {this.element.setStyle( {left: (this.options.x * position + this.originalLeft).round() + 'px', top: (this.options.y * position + this.originalTop).round() + 'px' });
   }
});// for backwards compatibility

Effect.MoveBy = function(element, toTop, toLeft)
   {return new Effect.Move(element, Object.extend( {x: toLeft, y: toTop}, arguments[3] || {}));
   };
Effect.Scale = Class.create(Effect.Base, 
{initialize: function(element, percent)
   {this.element = $(element);
    if ( ! this.element) throw (Effect._elementDoesNotExistError);
    var options = Object.extend( {scaleX: true, scaleY: true, scaleContent: true, scaleFromCenter: false, scaleMode: 'box', // 'box' or 'contents' or { } with provided values
      scaleFrom: 100.0, scaleTo: percent }, arguments[2] || {});
   
    this.start(options);
   }
, setup: function()
   {this.restoreAfterFinish = this.options.restoreAfterFinish || false;
    this.elementPositioning = this.element.getStyle('position');
    this.originalStyle = {};
    ['top', 'left', 'width', 'height', 'fontSize'].each(function(k)
       {this.originalStyle[k] = this.element.style[k];
       }.bind(this));
    this.originalTop = this.element.offsetTop;
    this.originalLeft = this.element.offsetLeft;
    var fontSize = this.element.getStyle('font-size') || '100%';
    ['em', 'px', '%', 'pt'].each(function(fontSizeType)
       {if (fontSize.indexOf(fontSizeType) > 0)
           {this.fontSize = parseFloat(fontSize);
            this.fontSizeType = fontSizeType;
           }
       }.bind(this));
    this.factor = (this.options.scaleTo - this.options.scaleFrom) / 100;
    this.dims = null;
    if (this.options.scaleMode == 'box')this.dims =[this.element.offsetHeight, this.element.offsetWidth];
    if ( /^content/.test(this.options.scaleMode))this.dims =[this.element.scrollHeight, this.element.scrollWidth];
    if ( ! this.dims)
   this.dims =[this.options.scaleMode.originalHeight, this.options.scaleMode.originalWidth];
   }
, update: function(position)
   {var currentScale = (this.options.scaleFrom / 100.0) + (this.factor * position);
    if (this.options.scaleContent && this.fontSize)this.element.setStyle( {fontSize: this.fontSize * currentScale + this.fontSizeType});
   
    this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
   }
, finish: function(position)
   {if (this.restoreAfterFinish) 
   this.element.setStyle(this.originalStyle);
   }
, setDimensions: function(height, width)
   {var d = {};
    if (this.options.scaleX) d.width = width.round() + 'px';
    if (this.options.scaleY) d.height = height.round() + 'px';
    if (this.options.scaleFromCenter)
       {var topd = (height - this.dims[0]) / 2;
        var leftd = (width - this.dims[1]) / 2;
        if (this.elementPositioning == 'absolute')
           {if (this.options.scaleY) d.top = this.originalTop - topd + 'px';
            if (this.options.scaleX) d.left = this.originalLeft - leftd + 'px';
           }
        else 
           {if (this.options.scaleY) d.top = - topd + 'px';
            if (this.options.scaleX) d.left = - leftd + 'px';
           }
       }
    this.element.setStyle(d);
   }
});
Effect.Highlight = Class.create(Effect.Base, 
{initialize: function(element)
   {this.element = $(element);
    if ( ! this.element) throw (Effect._elementDoesNotExistError);
    var options = Object.extend( {startcolor: '#ffff99'}, arguments[1] || {});
   
    this.start(options);
   }
, setup: function()
   {// Prevent executing on elements not in the layout flow
   if (this.element.getStyle('display') == 'none')
       {this.cancel();
        return;
       }
   // Disable background image during the effect
    this.oldStyle = {};
    if ( ! this.options.keepBackgroundImage)
       {this.oldStyle.backgroundImage = this.element.getStyle('background-image');
        this.element.setStyle( {backgroundImage: 'none'});
       }
    if ( ! this.options.endcolor)this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
    if ( ! this.options.restorecolor)this.options.restorecolor = this.element.getStyle('background-color');// init color calculations
    this._base = $R(0, 2).map(function(i)
       {return parseInt(this.options.startcolor.slice(i * 2 + 1, i * 2 + 3), 16);
       }.bind(this));
    this._delta = $R(0, 2).map(function(i)
       {
       return parseInt(this.options.endcolor.slice(i * 2 + 1, i * 2 + 3), 16) - this._base[i];
       }.bind(this));
   }
, update: function(position)
   {this.element.setStyle( {backgroundColor: $R(0, 2).inject('#', function(m, v, i)
       {
       return m + ((this._base[i] + (this._delta[i] * position)).round().toColorPart());
       }
   .bind(this))});
   }
, finish: function()
   {this.element.setStyle(Object.extend(this.oldStyle,  {backgroundColor: this.options.restorecolor }));
   }
});

Effect.ScrollTo = function(element)
   {var options = arguments[1] || {}, scrollOffsets = document.viewport.getScrollOffsets(), elementOffsets = $(element).cumulativeOffset(), max = (window.height || document.body.scrollHeight) - document.viewport.getHeight();
    if (options.offset) elementOffsets[1] += options.offset;
    return new Effect.Tween(null, scrollOffsets.top, elementOffsets[1] > max ? max: elementOffsets[1], options, function(p)
       {scrollTo(scrollOffsets.left, p.round());
       });
   };/* ------------- combination effects ------------- */

Effect.Fade = function(element)
   {element = $(element);
    var oldOpacity = element.getInlineOpacity();
    var options = Object.extend(
    {from: element.getOpacity() || 1.0, to: 0.0, afterFinishInternal: function(effect)
       {if (effect.options.to != 0) return;
        effect.element.hide().setStyle( {opacity: oldOpacity});
       }
   }, arguments[1] || {});
    return new Effect.Opacity(element, options);
   };

Effect.Appear = function(element)
   {element = $(element);
    var options = Object.extend(
    {from: (element.getStyle('display') == 'none' ? 0.0: element.getOpacity() || 0.0), to: 1.0, // force Safari to render floated elements properly
      afterFinishInternal: function(effect)
       {
       effect.element.forceRerendering();
       }
   , beforeSetup: function(effect)
       {effect.element.setOpacity(effect.options.from).show();
       }
   }, arguments[1] || {});
    return new Effect.Opacity(element, options);
   };

Effect.Puff = function(element)
   {element = $(element);
    var oldStyle =  {opacity: element.getInlineOpacity(), position: element.getStyle('position'), top: element.style.top, left: element.style.left, width: element.style.width, height: element.style.height };
    return new Effect.Parallel([new Effect.Scale(element, 200,  {sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true}), new Effect.Opacity(element,  {sync: true, to: 0.0})], Object.extend(
    {duration: 1.0, beforeSetupInternal: function(effect)
       {
       Position.absolutize(effect.effects[0].element);
       }
   , afterFinishInternal: function(effect)
       {effect.effects[0].element.hide().setStyle(oldStyle);
       }
   }, arguments[1] || {}));
   };

Effect.BlindUp = function(element)
   {element = $(element);
    element.makeClipping();
    return new Effect.Scale(element, 0, Object.extend(
    {scaleContent: false, scaleX: false, restoreAfterFinish: true, afterFinishInternal: function(effect)
       {effect.element.hide().undoClipping();
       }
   }, arguments[1] || {}));
   };

Effect.BlindDown = function(element)
   {element = $(element);
    var elementDimensions = element.getDimensions();
    return new Effect.Scale(element, 100, Object.extend( {scaleContent: false, scaleX: false, scaleFrom: 0, scaleMode: 
    {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, restoreAfterFinish: true, afterSetup: function(effect)
       {effect.element.makeClipping().setStyle(
        {height: '0px'}).show();
       }
   , afterFinishInternal: function(effect)
       {effect.element.undoClipping();
       }
   }, arguments[1] || {}));
   };

Effect.SwitchOff = function(element)
   {element = $(element);
    var oldOpacity = element.getInlineOpacity();
    return new Effect.Appear(element, Object.extend(
    {duration: 0.4, from: 0, transition: Effect.Transitions.flicker, afterFinishInternal: function(effect)
       {new Effect.Scale(effect.element, 1, 
        {duration: 0.3, scaleFromCenter: true, scaleX: false, scaleContent: false, restoreAfterFinish: true, beforeSetup: function(effect)
           {
           effect.element.makePositioned().makeClipping();
           }
       , afterFinishInternal: function(effect)
           {effect.element.hide().undoClipping().undoPositioned().setStyle( {opacity: oldOpacity});
           }
       });
       }
   }, arguments[1] || {}));
   };

Effect.DropOut = function(element)
   {element = $(element);
    var oldStyle =  {top: element.getStyle('top'), left: element.getStyle('left'), opacity: element.getInlineOpacity()};
    return new Effect.Parallel([new Effect.Move(element,  {x: 0, y: 100, sync: true}), new Effect.Opacity(element,  {sync: true, to: 0.0})], Object.extend(
    {duration: 0.5, beforeSetup: function(effect)
       {
       effect.effects[0].element.makePositioned();
       }
   , afterFinishInternal: function(effect)
       {effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
       }
   }, arguments[1] || {}));
   };

Effect.Shake = function(element)
   {element = $(element);
    var options = Object.extend( {distance: 20, duration: 0.5 }, arguments[1] || {});
    var distance = parseFloat(options.distance);
    var split = parseFloat(options.duration) / 10.0;
    var oldStyle =  {top: element.getStyle('top'), left: element.getStyle('left')};
    return new Effect.Move(element, 
    {x: distance, y: 0, duration: split, afterFinishInternal: function(effect)
       {new Effect.Move(effect.element, 
        {x: - distance * 2, y: 0, duration: split * 2, afterFinishInternal: function(effect)
           {new Effect.Move(effect.element, 
            {x: distance * 2, y: 0, duration: split * 2, afterFinishInternal: function(effect)
               {new Effect.Move(effect.element, 
                {x: - distance * 2, y: 0, duration: split * 2, afterFinishInternal: function(effect)
                   {new Effect.Move(effect.element, 
                    {x: distance * 2, y: 0, duration: split * 2, afterFinishInternal: function(effect)
                       {new Effect.Move(effect.element, 
                        {x: - distance, y: 0, duration: split, afterFinishInternal: function(effect)
                           {effect.element.undoPositioned().setStyle(oldStyle);
                           }
                       });
                       }
                   });
                   }
               });
               }
           });
           }
       });
       }
   });
   };

Effect.SlideDown = function(element)
   {   element = $(element).cleanWhitespace();// SlideDown need to have the content of the element wrapped in a container element with fixed height!
    var oldInnerBottom = element.down().getStyle('bottom');
    var elementDimensions = element.getDimensions();
    return new Effect.Scale(element, 100, Object.extend( {scaleContent: false, 
      scaleX: false, 
      scaleFrom: window.opera ? 0: 1, 
      scaleMode: 
    {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, 
      restoreAfterFinish: true, 
      afterSetup: function(effect)
       {effect.element.makePositioned();
        effect.element.down().makePositioned();
        if (window.opera) effect.element.setStyle( {top: ''});
        effect.element.makeClipping().setStyle(
        {height: '0px'}).show();
       }
   , 
      afterUpdateInternal: function(effect)
       {effect.element.down().setStyle(
        {bottom: (effect.dims[0] - effect.element.clientHeight) + 'px'});
       }
   , 
      afterFinishInternal: function(effect)
       {effect.element.undoClipping().undoPositioned();
        effect.element.down().undoPositioned().setStyle( {bottom: oldInnerBottom});
       }
   }, arguments[1] || {}));
   };

Effect.SlideUp = function(element)
   {element = $(element).cleanWhitespace();
    var oldInnerBottom = element.down().getStyle('bottom');
    var elementDimensions = element.getDimensions();
    return new Effect.Scale(element, window.opera ? 0: 1, Object.extend( {scaleContent: false, 
      scaleX: false, 
      scaleMode: 'box', 
      scaleFrom: 100, 
      scaleMode: 
    {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, 
      restoreAfterFinish: true, 
      afterSetup: function(effect)
       {effect.element.makePositioned();
        effect.element.down().makePositioned();
        if (window.opera) effect.element.setStyle( {top: ''});
       
        effect.element.makeClipping().show();
       }
   , 
      afterUpdateInternal: function(effect)
       {effect.element.down().setStyle(
        {bottom: (effect.dims[0] - effect.element.clientHeight) + 'px'});
       }
   , 
      afterFinishInternal: function(effect)
       {effect.element.hide().undoClipping().undoPositioned();
        effect.element.down().undoPositioned().setStyle( {bottom: oldInnerBottom});
       }
   }, arguments[1] || {}));
   };// Bug in opera makes the TD containing this element expand for a instance after finish 

Effect.Squish = function(element)
   {return new Effect.Scale(element, window.opera ? 1: 0, 
    {restoreAfterFinish: true, beforeSetup: function(effect)
       {
       effect.element.makeClipping();
       }
   , afterFinishInternal: function(effect)
       {effect.element.hide().undoClipping();
       }
   });
   };

Effect.Grow = function(element)
   {element = $(element);
    var options = Object.extend( {direction: 'center', moveTransition: Effect.Transitions.sinoidal, scaleTransition: Effect.Transitions.sinoidal, opacityTransition: Effect.Transitions.full }, arguments[1] || {});
    var oldStyle =  {top: element.style.top, left: element.style.left, height: element.style.height, width: element.style.width, opacity: element.getInlineOpacity()};
    var dims = element.getDimensions();
    var initialMoveX, initialMoveY;
    var moveX, moveY;
    switch (options.direction)
       {case 'top-left': 
            initialMoveX = initialMoveY = moveX = moveY = 0;
            break;
        case 'top-right': 
            initialMoveX = dims.width;
            initialMoveY = moveY = 0;
            moveX = - dims.width;
            break;
        case 'bottom-left': 
            initialMoveX = moveX = 0;
            initialMoveY = dims.height;
            moveY = - dims.height;
            break;
        case 'bottom-right': 
            initialMoveX = dims.width;
            initialMoveY = dims.height;
            moveX = - dims.width;
            moveY = - dims.height;
            break;
        case 'center': 
            initialMoveX = dims.width / 2;
            initialMoveY = dims.height / 2;
            moveX = - dims.width / 2;
            moveY = - dims.height / 2;
            break;
       }
    return new Effect.Move(element, 
    {x: initialMoveX, y: initialMoveY, duration: 0.01, beforeSetup: function(effect)
       {
       effect.element.hide().makeClipping().makePositioned();
       }
   , afterFinishInternal: function(effect)
       {new Effect.Parallel([new Effect.Opacity(effect.element,  {sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition}), new Effect.Move(effect.element,  {x: moveX, y: moveY, sync: true, transition: options.moveTransition}), new Effect.Scale(effect.element, 100,  {scaleMode:  {originalHeight: dims.height, originalWidth: dims.width}, sync: true, scaleFrom: window.opera ? 1: 0, transition: options.scaleTransition, restoreAfterFinish: true})], Object.extend(
        {beforeSetup: function(effect)
           {effect.effects[0].element.setStyle(
            {height: '0px'}).show();
           }
       , afterFinishInternal: function(effect)
           {effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle);
           }
       }, options));
       }
   });
   };

Effect.Shrink = function(element)
   {element = $(element);
    var options = Object.extend( {direction: 'center', moveTransition: Effect.Transitions.sinoidal, scaleTransition: Effect.Transitions.sinoidal, opacityTransition: Effect.Transitions.none }, arguments[1] || {});
    var oldStyle =  {top: element.style.top, left: element.style.left, height: element.style.height, width: element.style.width, opacity: element.getInlineOpacity()};
    var dims = element.getDimensions();
    var moveX, moveY;
    switch (options.direction)
       {case 'top-left': 
            moveX = moveY = 0;
            break;
        case 'top-right': 
            moveX = dims.width;
            moveY = 0;
            break;
        case 'bottom-left': 
            moveX = 0;
            moveY = dims.height;
            break;
        case 'bottom-right': 
            moveX = dims.width;
            moveY = dims.height;
            break;
        case 'center': 
            moveX = dims.width / 2;
            moveY = dims.height / 2;
            break;
       }
    return new Effect.Parallel([new Effect.Opacity(element,  {sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition}), new Effect.Scale(element, window.opera ? 1: 0,  {sync: true, transition: options.scaleTransition, restoreAfterFinish: true}), new Effect.Move(element,  {x: moveX, y: moveY, sync: true, transition: options.moveTransition})], Object.extend(
    {beforeStartInternal: function(effect)
       {
       effect.effects[0].element.makePositioned().makeClipping();
       }
   , afterFinishInternal: function(effect)
       {effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle);
       }
   }, options));
   };

Effect.Pulsate = function(element)
   {element = $(element);
    var options = arguments[1] || {};
    var oldOpacity = element.getInlineOpacity();
    var transition = options.transition || Effect.Transitions.sinoidal;
   
    var reverser = function(pos)
       {return transition(1 - Effect.Transitions.pulse(pos, options.pulses));
       };
    reverser.bind(transition);
    return new Effect.Opacity(element, Object.extend(Object.extend(
    {duration: 2.0, from: 0, afterFinishInternal: function(effect)
       {effect.element.setStyle( {opacity: oldOpacity});
       }
   }, options),  {transition: reverser}));
   };

Effect.Fold = function(element)
   {element = $(element);
    var oldStyle =  {top: element.style.top, left: element.style.left, width: element.style.width, height: element.style.height};
    element.makeClipping();
    return new Effect.Scale(element, 5, Object.extend(
    {scaleContent: false, scaleX: false, afterFinishInternal: function(effect)
       {new Effect.Scale(element, 1, 
        {scaleContent: false, scaleY: false, afterFinishInternal: function(effect)
           {effect.element.hide().undoClipping().setStyle(oldStyle);
           }
       });
       }
   }, arguments[1] || {}));
   };
Effect.Morph = Class.create(Effect.Base, 
{initialize: function(element)
   {this.element = $(element);
    if ( ! this.element) throw (Effect._elementDoesNotExistError);
    var options = Object.extend( {style: {}}, arguments[1] || {});
    if ( ! Object.isString(options.style)) this.style = $H(options.style);
    else 
       {if (options.style.include(':'))this.style = options.style.parseStyle();
        else 
           {this.element.addClassName(options.style);
            this.style = $H(this.element.getStyles());
            this.element.removeClassName(options.style);
            var css = this.element.getStyles();
            this.style = this.style.reject(function(style)
               {return style.value == css[style.key];
               });
           
            options.afterFinishInternal = function(effect)
               {effect.element.addClassName(effect.options.style);
                effect.transforms.each(function(transform)
                   {effect.element.style[transform.style] = '';
                   });
               };
           }
       }
   
    this.start(options);
   }
, setup: function()
   {   
   function parseColor(color)
       {       if ( ! color ||['rgba(0, 0, 0, 0)', 'transparent'].include(color)) color = '#ffffff';
        color = color.parseColor();
        return $R(0, 2).map(function(i)
           {return parseInt(color.slice(i * 2 + 1, i * 2 + 3), 16);
           });
       }
    this.transforms = this.style.map(function(pair)
       {var property = pair[0], value = pair[1], unit = null;
        if (value.parseColor('#zzzzzz') != '#zzzzzz')
           {value = value.parseColor();
            unit = 'color';
           }
        else if (property == 'opacity')
           {value = parseFloat(value);
            if (Prototype.Browser.IE && ( ! this.element.currentStyle.hasLayout))this.element.setStyle( {zoom: 1});
           }
        else if (Element.CSS_LENGTH.test(value))
           {var components = value.match( /^([\+\-]?[0-9\.]+)(.*)$/);
            value = parseFloat(components[1]);
            unit = (components.length == 3) ? components[2]: null;
           }
        var originalValue = this.element.getStyle(property);
        return  {style: property.camelize(), originalValue: unit == 'color' ? parseColor(originalValue): parseFloat(originalValue || 0), targetValue: unit == 'color' ? parseColor(value): value, unit: unit };
       }.bind(this)).reject(function(transform)
       {
       return ((transform.originalValue == transform.targetValue) || (transform.unit != 'color' && (isNaN(transform.originalValue) || isNaN(transform.targetValue))));
       });
   }
, update: function(position)
   {var style = {}, transform, i = this.transforms.length;
    while (i--)style[(transform = this.transforms[i]).style] = transform.unit == 'color' ? '#' + (Math.round(transform.originalValue[0] + (transform.targetValue[0] - transform.originalValue[0]) * position)).toColorPart() + (Math.round(transform.originalValue[1] + (transform.targetValue[1] - transform.originalValue[1]) * position)).toColorPart() + (Math.round(transform.originalValue[2] + (transform.targetValue[2] - transform.originalValue[2]) * position)).toColorPart(): (transform.originalValue + (transform.targetValue - transform.originalValue) * position).toFixed(3) + (transform.unit === null ? '': transform.unit);
    this.element.setStyle(style, true);
   }
});
Effect.Transform = Class.create(
{initialize: function(tracks)
   {this.tracks =[];
    this.options = arguments[1] || {};
   
    this.addTracks(tracks);
   }
, addTracks: function(tracks)
   {tracks.each(function(track)
       {track = $H(track);
        var data = track.values().first();
        this.tracks.push($H( {ids: track.keys().first(), effect: Effect.Morph, options:  {style: data}}));
       }.bind(this));
   
    return this;
   }
, play: function()
   {return new Effect.Parallel(this.tracks.map(function(track)
       {var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options');
        var elements =[$(ids) || $$(ids)].flatten();
        return elements.map(function(e)
           {return new effect(e, Object.extend( {sync: true}, options));
           });
       }).flatten(), this.options );
   }
});
Element.CSS_PROPERTIES = $w('backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' + 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' + 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' + 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' + 'fontSize fontWeight height left letterSpacing lineHeight ' + 'marginBottom marginLeft marginRight marginTop markerOffset maxHeight ' + 'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' + 'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' + 'right textIndent top width wordSpacing zIndex');
Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
String.__parseStyleElement = document.createElement('div');

String.prototype.parseStyle = function()
   {   var style, styleRules = $H();
    if (Prototype.Browser.WebKit)   style = new Element('div',     {style: this}).style;
    else 
       {       String.__parseStyleElement.innerHTML = '<div style="' + this + '"></div>';
        style = String.__parseStyleElement.childNodes[0].style;
       }
    Element.CSS_PROPERTIES.each(function(property)
       {if (style[property]) styleRules.set(property, style[property]);
       });
    if (Prototype.Browser.IE && this.include('opacity'))styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]);
    return styleRules;
   };
if (document.defaultView && document.defaultView.getComputedStyle)
   {
   Element.getStyles = function(element)
       {var css = document.defaultView.getComputedStyle($(element), null);
        return Element.CSS_PROPERTIES.inject({}, function(styles, property)
           {styles[property] = css[property];
            return styles;
           });
       };
   }
else 
   {
   Element.getStyles = function(element)
       {element = $(element);
        var css = element.currentStyle, styles;
        styles = Element.CSS_PROPERTIES.inject({}, function(hash, property)
           {hash.set(property, css[property]);
            return hash;
           });
        if ( ! styles.opacity) styles.set('opacity', element.getOpacity());
        return styles;
       };
   }
Effect.Methods = 
{morph: function(element, style)
   {element = $(element);
    new Effect.Morph(element, Object.extend( {style: style}, arguments[2] || {}));
   
    return element;
   }
, visualEffect: function(element, effect, options)
   {element = $(element);
    var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1);
    new Effect[klass](element, options);
   
    return element;
   }
, highlight: function(element, options)
   {element = $(element);
    new Effect.Highlight(element, options);
    return element;
   }
};
$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown ' + 'pulsate shake puff squish switchOff dropOut').each(function(effect)
   {
   Effect.Methods[effect] = function(element, options)
       {element = $(element);
        Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options);
        return element;
       };
   });
$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each(function(f)
   {Effect.Methods[f] = Element[f];
   });
Element.addMethods(Effect.Methods);
