// script.aculo.us effects.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009



// Copyright (c) 2005-2009 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) + .5;

    },

    reverse: function(pos) {

      return 1-pos;

    },

    flicker: function(pos) {

      var pos = ((-Math.cos(pos*Math.PI)/4) + .75) + Math.random()/4;

      return pos > 1 ? 1 : pos;

    },

    wobble: function(pos) {

      return (-Math.cos(pos*Math.PI*(9*pos))/2) + .5;

    },

    pulse: function(pos, pulses) {

      return (-Math.cos((pos*((pulses||5)-.5)*2)*Math.PI)/2) + .5;

    },

    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, options) {

    element = $(element);

    effect  = (effect || 'appear').toLowerCase();

    

    return Effect[ Effect.PAIRS[ effect ][ element.visible() ? 1 : 0 ] ](element, Object.extend({

      queue: { position:'end', scope:(element.id || 'global'), limit: 1 }

    }, 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) {

    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;



    this.render = (function() {

      function dispatch(effect, eventName) {

        if (effect.options[eventName + 'Internal'])

          effect.options[eventName + 'Internal'](effect);

        if (effect.options[eventName])

          effect.options[eventName](effect);

      }



      return function(pos) {

        if (this.state === "idle") {

          this.state = "running";

          dispatch(this, 'beforeSetup');

          if (this.setup) this.setup();

          dispatch(this, 'afterSetup');

        }

        if (this.state === "running") {

          pos = (this.options.transition(pos) * this.fromToDelta) + this.options.from;

          this.position = pos;

          dispatch(this, 'beforeUpdate');

          if (this.update) this.update(pos);

          dispatch(this, '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();



  if (options.offset) elementOffsets[1] += options.offset;



  return new Effect.Tween(null,

    scrollOffsets.top,

    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] || { },

    oldOpacity = element.getInlineOpacity(),

    transition = options.transition || Effect.Transitions.linear,

    reverser   = function(pos){

      return 1 - transition((-Math.cos((pos*(options.pulses||5)*2)*Math.PI)/2) + .5);

    };



  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(results, property) {

      results[property] = css[property];

      return results;

    });

    if (!styles.opacity) styles.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);
