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

};



/*--------------------------------------------------------------------------*/



Array.prototype.call = function() {

  var args = arguments;

  this.each(function(f){ f.apply(this, args) });

}



/*--------------------------------------------------------------------------*/



var Effect = {

  _elementDoesNotExistError: {

    name: 'ElementDoesNotExistError',

    message: 'The specified DOM element does not exist, but is required for this effect to operate'

  },

  tagifyText: function(element) {

    if(typeof Builder == 'undefined')

      throw("Effect.tagifyText requires including script.aculo.us' builder.js library");

      

    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(

            Builder.node('span',{style: tagifyStyle},

              character == ' ' ? String.fromCharCode(160) : character), 

              child);

        });

        Element.remove(child);

      }

    });

  },

  multiple: function(element, effect) {

    var elements;

    if(((typeof element == 'object') || 

        (typeof element == 'function')) && 

       (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);

  }

};



var Effect2 = Effect; // deprecated



/* ------------- transitions ------------- */



Effect.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 (

      Math.round((pos % (1/pulses)) * pulses) == 0 ? 

            ((pos * pulses * 2) - Math.floor(pos * pulses * 2)) : 

        1 - ((pos * pulses * 2) - Math.floor(pos * pulses * 2))

      );

  },

  none: function(pos) {

    return 0;

  },

  full: function(pos) {

    return 1;

  }

};



/* ------------- core effects ------------- */



Effect.ScopedQueue = Class.create();

Object.extend(Object.extend(Effect.ScopedQueue.prototype, 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 = (typeof effect.options.queue == 'string') ? 

      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(typeof queueName != 'string') return queueName;

    

    if(!this.instances[queueName])

      this.instances[queueName] = new Effect.ScopedQueue();

      

    return this.instances[queueName];

  }

}

Effect.Queue = Effect.Queues.get('global');



Effect.DefaultOptions = {

  transition: Effect.Transitions.sinoidal,

  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'

}



Effect.Base = function() {};

Effect.Base.prototype = {

  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.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(options,'beforeSetup')+

      (this.setup ? 'this.setup();':'')+ 

      codeForEvent(options,'afterSetup')+

      '};if(this.state=="running"){'+

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

      'this.position=pos;'+

      codeForEvent(options,'beforeUpdate')+

      (this.update ? 'this.update(pos);':'')+

      codeForEvent(options,'afterUpdate')+

      '}}');

    

    this.event('beforeStart');

    if(!this.options.sync)

      Effect.Queues.get(typeof this.options.queue == 'string' ? 

        '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 = Math.round(pos * this.totalFrames);

      if(frame > this.currentFrame) {

        this.render(pos);

        this.currentFrame = frame;

      }

    }

  },

  cancel: function() {

    if(!this.options.sync)

      Effect.Queues.get(typeof this.options.queue == 'string' ? 

        '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(typeof this[property] != 'function') data[property] = this[property];

    return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';

  }

}



Effect.Parallel = Class.create();

Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), {

  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.Event = Class.create();

Object.extend(Object.extend(Effect.Event.prototype, Effect.Base.prototype), {

  initialize: function() {

    var options = Object.extend({

      duration: 0

    }, arguments[0] || {});

    this.start(options);

  },

  update: Prototype.emptyFunction

});



Effect.Opacity = Class.create();

Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), {

  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();

Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), {

  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() {

    // Bug in Opera: Opera returns the "real" position of a static element or

    // relative element that does not have top/left explicitly set.

    // ==> Always set top and left for position relative elements in your stylesheets 

    // (to 0 if you do not need them) 

    this.element.makePositioned();

    this.originalLeft = parseFloat(this.element.getStyle('left') || '0');

    this.originalTop  = parseFloat(this.element.getStyle('top')  || '0');

    if(this.options.mode == 'absolute') {

      // absolute movement, so we need to calc deltaX and deltaY

      this.options.x = this.options.x - this.originalLeft;

      this.options.y = this.options.y - this.originalTop;

    }

  },

  update: function(position) {

    this.element.setStyle({

      left: Math.round(this.options.x  * position + this.originalLeft) + 'px',

      top:  Math.round(this.options.y  * position + this.originalTop)  + '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();

Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {

  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 = Math.round(width) + 'px';

    if(this.options.scaleY) d.height = Math.round(height) + '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();

Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), {

  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+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) });

  },

  finish: function() {

    this.element.setStyle(Object.extend(this.oldStyle, {

      backgroundColor: this.options.restorecolor

    }));

  }

});



Effect.ScrollTo = Class.create();

Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), {

  initialize: function(element) {

    this.element = $(element);

    this.start(arguments[1] || {});

  },

  setup: function() {

    Position.prepare();

    var offsets = Position.cumulativeOffset(this.element);

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

    var max = window.innerHeight ? 

      window.height - window.innerHeight :

      document.body.scrollHeight - 

        (document.documentElement.clientHeight ? 

          document.documentElement.clientHeight : document.body.clientHeight);

    this.scrollStart = Position.deltaY;

    this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart;

  },

  update: function(position) {

    Position.prepare();

    window.scrollTo(Position.deltaX, 

      this.scrollStart + (position*this.delta));

  }

});



/* ------------- 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 oldStyle = {

    top: element.getStyle('top'),

    left: element.getStyle('left') };

    return new Effect.Move(element, 

      { x:  20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {

    new Effect.Move(effect.element,

      { x: -40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {

    new Effect.Move(effect.element,

      { x:  40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {

    new Effect.Move(effect.element,

      { x: -40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {

    new Effect.Move(effect.element,

      { x:  40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {

    new Effect.Move(effect.element,

      { x: -20, y: 0, duration: 0.05, 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');

  return new Effect.Scale(element, window.opera ? 0 : 1,

   Object.extend({ scaleContent: false, 

    scaleX: false, 

    scaleMode: 'box',

    scaleFrom: 100,

    restoreAfterFinish: true,

    beforeStartInternal: 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().setStyle({bottom: oldInnerBottom});

      effect.element.down().undoPositioned();

    }

   }, 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();

Object.extend(Object.extend(Effect.Morph.prototype, Effect.Base.prototype), {

  initialize: function(element) {

    this.element = $(element);

    if(!this.element) throw(Effect._elementDoesNotExistError);

    var options = Object.extend({

      style: {}

    }, arguments[1] || {});

    if (typeof options.style == 'string') {

      if(options.style.indexOf(':') == -1) {

        var cssText = '', selector = '.' + options.style;

        $A(document.styleSheets).reverse().each(function(styleSheet) {

          if (styleSheet.cssRules) cssRules = styleSheet.cssRules;

          else if (styleSheet.rules) cssRules = styleSheet.rules;

          $A(cssRules).reverse().each(function(rule) {

            if (selector == rule.selectorText) {

              cssText = rule.style.cssText;

              throw $break;

            }

          });

          if (cssText) throw $break;

        });

        this.style = cssText.parseStyle();

        options.afterFinishInternal = function(effect){

          effect.element.addClassName(effect.options.style);

          effect.transforms.each(function(transform) {

            if(transform.style != 'opacity')

              effect.element.style[transform.style] = '';

          });

        }

      } else this.style = options.style.parseStyle();

    } else this.style = $H(options.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 + Math.round(

          ((transform.targetValue - transform.originalValue) * position) * 1000)/1000 + transform.unit;

    this.element.setStyle(style, true);

  }

});



Effect.Transform = Class.create();

Object.extend(Effect.Transform.prototype, {

  initialize: function(tracks){

    this.tracks  = [];

    this.options = arguments[1] || {};

    this.addTracks(tracks);

  },

  addTracks: function(tracks){

    tracks.each(function(track){

      var data = $H(track).values().first();

      this.tracks.push($H({

        ids:     $H(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 elements = [$(track.ids) || $$(track.ids)].flatten();

        return elements.map(function(e){ return new track.effect(e, Object.extend({ sync:true }, track.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.prototype.parseStyle = function(){

  var element = document.createElement('div');

  element.innerHTML = '<div style="' + this + '"></div>';

  var style = element.childNodes[0].style, styleRules = $H();

  

  Element.CSS_PROPERTIES.each(function(property){

    if(style[property]) styleRules[property] = style[property]; 

  });

  if(Prototype.Browser.IE && this.indexOf('opacity') > -1) {

    styleRules.opacity = this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1];

  }

  return styleRules;

};



Element.morph = function(element, style) {

  new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || {}));

  return element;

};



['getInlineOpacity','forceRerendering','setContentZoom',

 'collectTextNodes','collectTextNodesIgnoreClass','morph'].each( 

  function(f) { Element.Methods[f] = Element[f]; }

);



Element.Methods.visualEffect = function(element, effect, options) {

  s = effect.dasherize().camelize();

  effect_class = s.charAt(0).toUpperCase() + s.substring(1);

  new Effect[effect_class](element, options);

  return $(element);

};



Element.addMethods();
