// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) 
// 
// Parts (c) 2005 Justin Palmer (http://encytemedia.com/) 
// Parts (c) 2005 Mark Pilgrim (http://diveintomark.org/) 
//  
// Permission is hereby granted, free of charge, to any person obtaining 
// a copy of this software and associated documentation files (the 
// "Software"), to deal in the Software without restriction, including 
// without limitation the rights to use, copy, modify, merge, publish, 
 // distribute, sublicense, and/or sell copies of the Software, and to 
 // permit persons to whom the Software is furnished to do so, subject to 
 // the following conditions: 
 //  
 // The above copyright notice and this permission notice shall be 
 // included in all copies or substantial portions of the Software. 
 //  
 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 
 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 
 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 
 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
  
  
 Effect = {} 
 Effect2 = Effect; // deprecated 
  
 /* ------------- transitions ------------- */ 
  
 Effect.Transitions = {} 
  
 Effect.Transitions.linear = function(pos) { 
   return pos; 
 } 
 Effect.Transitions.sinoidal = function(pos) { 
   return (-Math.cos(pos*Math.PI)/2) + 0.5; 
 } 
 Effect.Transitions.reverse  = function(pos) { 
   return 1-pos; 
 } 
 Effect.Transitions.flicker = function(pos) { 
   return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random(0.25); 
 } 
 Effect.Transitions.wobble = function(pos) { 
   return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5; 
 } 
 Effect.Transitions.pulse = function(pos) { 
   return (Math.floor(pos*10) % 2 == 0 ?  
     (pos*10-Math.floor(pos*10)) : 1-(pos*10-Math.floor(pos*10))); 
 } 
 Effect.Transitions.none = function(pos) { 
   return 0; 
 } 
 Effect.Transitions.full = function(pos) { 
   return 1; 
 } 
  
 /* ------------- element ext -------------- */ 
  
 Element.makePositioned = function(element) { 
   element = $(element); 
   if(element.style.position == "") 
     element.style.position = "relative"; 
 } 
  
 Element.makeClipping = function(element) { 
   element = $(element); 
   element._overflow = element.style.overflow || 'visible'; 
   if(element._overflow!='hidden') element.style.overflow = 'hidden'; 
 } 
  
 Element.undoClipping = function(element) { 
   element = $(element); 
   if(element._overflow!='hidden') element.style.overflow = element._overflow; 
 } 
  
 /* ------------- core effects ------------- */ 
  
 Effect.Base = function() {}; 
 Effect.Base.prototype = { 
   setOptions: function(options) { 
     this.options = Object.extend({ 
       transition: Effect.Transitions.sinoidal, 
       duration:   1.0,   // seconds 
       fps:        25.0,  // max. 100fps 
       sync:       false, // true for combining 
       from:       0.0, 
       to:         1.0 
     }, options || {}); 
   }, 
   start: function(options) { 
     this.setOptions(options || {}); 
     this.currentFrame = 0; 
     this.startOn      = new Date().getTime(); 
     this.finishOn     = this.startOn + (this.options.duration*1000); 
     if(this.options.beforeStart) this.options.beforeStart(this); 
     if(!this.options.sync) this.loop();   
   }, 
   loop: function() { 
     var timePos = new Date().getTime(); 
     if(timePos >= this.finishOn) { 
       this.render(this.options.to); 
       if(this.finish) this.finish();  
       if(this.options.afterFinish) this.options.afterFinish(this); 
       return;   
     } 
     var pos   = (timePos - this.startOn) / (this.finishOn - this.startOn); 
     var frame = Math.round(pos * this.options.fps * this.options.duration); 
     if(frame > this.currentFrame) { 
       this.render(pos); 
       this.currentFrame = frame; 
     } 
     this.timeout = setTimeout(this.loop.bind(this), 10); 
   }, 
   render: function(pos) { 
     if(this.options.transition) pos = this.options.transition(pos); 
     pos *= (this.options.to-this.options.from); 
     pos += this.options.from;  
     if(this.options.beforeUpdate) this.options.beforeUpdate(this); 
     if(this.update) this.update(pos); 
     if(this.options.afterUpdate) this.options.afterUpdate(this);   
   }, 
   cancel: function() { 
     if(this.timeout) clearTimeout(this.timeout); 
   } 
 } 
  
 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) { 
     for (var i = 0; i < this.effects.length; i++) 
       this.effects[i].render(position);   
   }, 
   finish: function(position) { 
     for (var i = 0; i < this.effects.length; i++) 
       if(this.effects[i].finish) this.effects[i].finish(position); 
   } 
 }); 
  
 // Internet Explorer caveat: works only on elements the have 
 // a 'layout', meaning having a given width or height.  
 // There is no way to safely set this automatically. 
 Effect.Opacity = Class.create(); 
 Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), { 
   initialize: function(element) { 
     this.element = $(element); 
     options = Object.extend({ 
       from: 0.0, 
       to:   1.0 
     }, arguments[1] || {}); 
     this.start(options); 
   }, 
   update: function(position) { 
     this.setOpacity(position); 
   },  
   setOpacity: function(opacity) { 
     opacity = (opacity == 1) ? 0.99999 : opacity; 
     this.element.style.opacity = opacity; 
     this.element.style.filter = "alpha(opacity:"+opacity*100+")"; 
   } 
 }); 
  
 Effect.MoveBy = Class.create(); 
 Object.extend(Object.extend(Effect.MoveBy.prototype, Effect.Base.prototype), { 
   initialize: function(element, toTop, toLeft) { 
     this.element      = $(element); 
     this.originalTop  = parseFloat(this.element.style.top || '0'); 
     this.originalLeft = parseFloat(this.element.style.left || '0'); 
     this.toTop        = toTop; 
     this.toLeft       = toLeft; 
     Element.makePositioned(this.element); 
     this.start(arguments[3]); 
   }, 
   update: function(position) { 
     topd  = this.toTop  * position + this.originalTop; 
     leftd = this.toLeft * position + this.originalLeft; 
     this.setPosition(topd, leftd); 
   }, 
   setPosition: function(topd, leftd) { 
     this.element.style.top  = topd  + "px"; 
     this.element.style.left = leftd + "px"; 
   } 
 }); 
  
 Effect.Scale = Class.create(); 
 Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), { 
   initialize: function(element, percent) { 
     this.element = $(element) 
     options = Object.extend({ 
       scaleX: true, 
       scaleY: true, 
       scaleContent: true, 
       scaleFromCenter: false, 
       scaleMode: 'box',        // 'box' or 'contents' or {} with provided values 
       scaleFrom: 100.0 
     }, arguments[2] || {}); 
     this.originalTop    = this.element.offsetTop; 
     this.originalLeft   = this.element.offsetLeft; 
     if(this.element.style.fontSize=="") this.sizeEm = 1.0; 
     if(this.element.style.fontSize && this.element.style.fontSize.indexOf("em")>0) 
       this.sizeEm      = parseFloat(this.element.style.fontSize); 
     this.factor = (percent/100.0) - (options.scaleFrom/100.0); 
     if(options.scaleMode=='box') { 
       this.originalHeight = this.element.clientHeight; 
       this.originalWidth  = this.element.clientWidth;  
     } else  
     if(options.scaleMode=='contents') { 
       this.originalHeight = this.element.scrollHeight; 
       this.originalWidth  = this.element.scrollWidth; 
     } else { 
       this.originalHeight = options.scaleMode.originalHeight; 
       this.originalWidth  = options.scaleMode.originalWidth; 
     } 
     this.start(options); 
   }, 
  
   update: function(position) { 
     currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); 
     if(this.options.scaleContent && this.sizeEm)  
       this.element.style.fontSize = this.sizeEm*currentScale + "em"; 
     this.setDimensions( 
       this.originalWidth * currentScale,  
       this.originalHeight * currentScale); 
   }, 
  
   setDimensions: function(width, height) { 
     if(this.options.scaleX) this.element.style.width = width + 'px'; 
     if(this.options.scaleY) this.element.style.height = height + 'px'; 
     if(this.options.scaleFromCenter) { 
       topd  = (height - this.originalHeight)/2; 
       leftd = (width  - this.originalWidth)/2; 
       if(this.element.style.position=='absolute') { 
         if(this.options.scaleY) this.element.style.top = this.originalTop-topd + "px"; 
         if(this.options.scaleX) this.element.style.left = this.originalLeft-leftd + "px"; 
       } else { 
         if(this.options.scaleY) this.element.style.top = -topd + "px"; 
         if(this.options.scaleX) this.element.style.left = -leftd + "px"; 
       } 
     } 
   } 
 }); 
  
 Effect.Highlight = Class.create(); 
 Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), { 
   initialize: function(element) { 
     this.element = $(element); 
      
     // try to parse current background color as default for endcolor 
     // browser stores this as: "rgb(255, 255, 255)", convert to "#ffffff" format 
     var endcolor = "#ffffff"; 
     var current = this.element.style.backgroundColor; 
     if(current && current.slice(0,4) == "rgb(") { 
       endcolor = "#"; 
       var cols = current.slice(4,current.length-1).split(','); 
       var i=0; do { endcolor += parseInt(cols[i]).toColorPart() } while (++i<3); } 
        
     var options = Object.extend({ 
       startcolor:   "#ffff99", 
       endcolor:     endcolor, 
       restorecolor: current  
     }, arguments[1] || {}); 
      
     // init color calculations 
     this.colors_base = [ 
       parseInt(options.startcolor.slice(1,3),16), 
       parseInt(options.startcolor.slice(3,5),16), 
       parseInt(options.startcolor.slice(5),16) ]; 
     this.colors_delta = [ 
       parseInt(options.endcolor.slice(1,3),16)-this.colors_base[0], 
       parseInt(options.endcolor.slice(3,5),16)-this.colors_base[1], 
       parseInt(options.endcolor.slice(5),16)-this.colors_base[2] ]; 
  
     this.start(options); 
   }, 
   update: function(position) { 
     var colors = [ 
       Math.round(this.colors_base[0]+(this.colors_delta[0]*position)), 
       Math.round(this.colors_base[1]+(this.colors_delta[1]*position)), 
       Math.round(this.colors_base[2]+(this.colors_delta[2]*position)) ]; 
     this.element.style.backgroundColor = "#" + 
       colors[0].toColorPart() + colors[1].toColorPart() + colors[2].toColorPart(); 
   }, 
   finish: function() { 
     this.element.style.backgroundColor = this.options.restorecolor; 
   } 
 }); 
  
 Effect.ScrollTo = Class.create(); 
 Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), { 
   initialize: function(element) { 
     this.element = $(element); 
     Position.prepare(); 
     var offsets = Position.cumulativeOffset(this.element); 
     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; 
     this.start(arguments[1] || {}); 
   }, 
   update: function(position) { 
     Position.prepare(); 
     window.scrollTo(Position.deltaX,  
       this.scrollStart + (position*this.delta)); 
   } 
 }); 
  
 /* ------------- prepackaged effects ------------- */ 
  
 Effect.Fade = function(element) { 
   options = Object.extend({ 
   from: 1.0, 
   to:   0.0, 
   afterFinish: function(effect)  
     { Element.hide(effect.element); 
       effect.setOpacity(1); }  
   }, arguments[1] || {}); 
   new Effect.Opacity(element,options); 
 } 
  
 Effect.Appear = function(element) { 
   options = Object.extend({ 
   from: 0.0, 
   to:   1.0, 
   beforeStart: function(effect)   
     { effect.setOpacity(0); 
       Element.show(effect.element); }, 
   afterUpdate: function(effect)   
     { Element.show(effect.element); } 
   }, arguments[1] || {}); 
   new Effect.Opacity(element,options); 
 } 
  
 Effect.Puff = function(element) { 
   new Effect.Parallel( 
    [ new Effect.Scale(element, 200, { sync: true, scaleFromCenter: true }),  
      new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0 } ) ],  
      { duration: 1.0,  
       afterUpdate: function(effect)  
        { effect.effects[0].element.style.position = 'absolute'; }, 
       afterFinish: function(effect) 
        { Element.hide(effect.effects[0].element); } 
      } 
    ); 
 } 
  
 Effect.BlindUp = function(element) { 
   Element.makeClipping(element); 
   new Effect.Scale(element, 0,  
     Object.extend({ scaleContent: false,  
       scaleX: false,  
       afterFinish: function(effect)  
         {  
           Element.hide(effect.element); 
           Element.undoClipping(effect.element); 
         }  
     }, arguments[1] || {}) 
   ); 
 } 
  
 Effect.BlindDown = function(element) { 
   $(element).style.height   = '0px'; 
   Element.makeClipping(element); 
   Element.show(element); 
   new Effect.Scale(element, 100,  
     Object.extend({ scaleContent: false,  
       scaleX: false,  
       scaleMode: 'contents', 
       scaleFrom: 0, 
       afterFinish: function(effect) { 
         Element.undoClipping(effect.element); 
       } 
     }, arguments[1] || {}) 
   ); 
 } 
  
 Effect.SwitchOff = function(element) { 
   new Effect.Appear(element, 
     { duration: 0.4, 
      transition: Effect.Transitions.flicker, 
      afterFinish: function(effect) 
       { effect.element.style.overflow = 'hidden'; 
         new Effect.Scale(effect.element, 1,  
          { duration: 0.3, scaleFromCenter: true, 
           scaleX: false, scaleContent: false, 
           afterUpdate: function(effect) {  
            if(effect.element.style.position=="") 
              effect.element.style.position = 'relative'; }, 
           afterFinish: function(effect) { Element.hide(effect.element); } 
          } ) 
       } 
     } ); 
 } 
  
 Effect.DropOut = function(element) { 
   new Effect.Parallel( 
     [ new Effect.MoveBy(element, 100, 0, { sync: true }),  
       new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0 } ) ],  
     { duration: 0.5,  
      afterFinish: function(effect) 
        { Element.hide(effect.effects[0].element); }  
     }); 
 } 
  
 Effect.Shake = function(element) { 
   new Effect.MoveBy(element, 0, 20,  
     { duration: 0.05, afterFinish: function(effect) { 
   new Effect.MoveBy(effect.element, 0, -40,  
     { duration: 0.1, afterFinish: function(effect) {  
   new Effect.MoveBy(effect.element, 0, 40,  
     { duration: 0.1, afterFinish: function(effect) {   
   new Effect.MoveBy(effect.element, 0, -40,  
     { duration: 0.1, afterFinish: function(effect) {   
   new Effect.MoveBy(effect.element, 0, 40,  
     { duration: 0.1, afterFinish: function(effect) {   
   new Effect.MoveBy(effect.element, 0, -20,  
     { duration: 0.05, afterFinish: function(effect) {   
   }}) }}) }}) }}) }}) }}); 
 } 
  
 Effect.SlideDown = function(element) { 
   element = $(element); 
   element.style.height   = '0px'; 
   Element.makeClipping(element); 
   Element.cleanWhitespace(element); 
   Element.makePositioned(element.firstChild); 
   Element.show(element); 
   new Effect.Scale(element, 100,  
    Object.extend({ scaleContent: false,  
     scaleX: false,  
     scaleMode: 'contents', 
     scaleFrom: 0, 
     afterUpdate: function(effect)  
       { effect.element.firstChild.style.bottom =  
           (effect.originalHeight - effect.element.clientHeight) + 'px'; }, 
     afterFinish: function(effect)  
       {  Element.undoClipping(effect.element); } 
     }, arguments[1] || {}) 
   ); 
 } 
    
 Effect.SlideUp = function(element) { 
   element = $(element); 
   Element.makeClipping(element); 
   Element.cleanWhitespace(element); 
   Element.makePositioned(element.firstChild); 
   Element.show(element); 
   new Effect.Scale(element, 0,  
    Object.extend({ scaleContent: false,  
     scaleX: false,  
     afterUpdate: function(effect)  
       { effect.element.firstChild.style.bottom =  
           (effect.originalHeight - effect.element.clientHeight) + 'px'; }, 
     afterFinish: function(effect) 
       {  
         Element.hide(effect.element); 
         Element.undoClipping(effect.element); 
       } 
    }, arguments[1] || {}) 
   ); 
 } 
  
 Effect.Squish = function(element) { 
  new Effect.Scale(element, 0,  
    { afterFinish: function(effect) { Element.hide(effect.element); } }); 
 } 
  
 Effect.Grow = function(element) { 
   element = $(element); 
   var options = arguments[1] || {}; 
    
   var originalWidth = element.clientWidth; 
   var originalHeight = element.clientHeight; 
   element.style.overflow = 'hidden'; 
   Element.show(element); 
    
   var direction = options.direction || 'center'; 
   var moveTransition = options.moveTransition || Effect.Transitions.sinoidal; 
   var scaleTransition = options.scaleTransition || Effect.Transitions.sinoidal; 
   var opacityTransition = options.opacityTransition || Effect.Transitions.full; 
    
   var initialMoveX, initialMoveY; 
   var moveX, moveY; 
    
   switch (direction) { 
     case 'top-left': 
       initialMoveX = initialMoveY = moveX = moveY = 0;  
       break; 
     case 'top-right': 
       initialMoveX = originalWidth; 
       initialMoveY = moveY = 0; 
       moveX = -originalWidth; 
       break; 
     case 'bottom-left': 
       initialMoveX = moveX = 0; 
       initialMoveY = originalHeight; 
       moveY = -originalHeight; 
       break; 
     case 'bottom-right': 
       initialMoveX = originalWidth; 
       initialMoveY = originalHeight; 
       moveX = -originalWidth; 
       moveY = -originalHeight; 
       break; 
     case 'center': 
       initialMoveX = originalWidth / 2; 
       initialMoveY = originalHeight / 2; 
       moveX = -originalWidth / 2; 
       moveY = -originalHeight / 2; 
       break; 
   } 
    
   new Effect.MoveBy(element, initialMoveY, initialMoveX, {  
     duration: 0.01,  
     beforeUpdate: function(effect) { $(element).style.height = '0px'; }, 
     afterFinish: function(effect) { 
       new Effect.Parallel( 
         [ new Effect.Opacity(element, { sync: true, to: 1.0, from: 0.0, transition: opacityTransition }), 
           new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: moveTransition }), 
           new Effect.Scale(element, 100, {  
             scaleMode: { originalHeight: originalHeight, originalWidth: originalWidth },  
             sync: true, scaleFrom: 0, scaleTo: 100, transition: scaleTransition })], 
         options); } 
     }); 
 } 
  
 Effect.Shrink = function(element) { 
   element = $(element); 
   var options = arguments[1] || {}; 
    
   var originalWidth = element.clientWidth; 
   var originalHeight = element.clientHeight; 
   element.style.overflow = 'hidden'; 
   Element.show(element); 
  
   var direction = options.direction || 'center'; 
   var moveTransition = options.moveTransition || Effect.Transitions.sinoidal; 
   var scaleTransition = options.scaleTransition || Effect.Transitions.sinoidal; 
   var opacityTransition = options.opacityTransition || Effect.Transitions.none; 
    
   var moveX, moveY; 
    
   switch (direction) { 
     case 'top-left': 
       moveX = moveY = 0; 
       break; 
     case 'top-right': 
       moveX = originalWidth; 
       moveY = 0; 
       break; 
     case 'bottom-left': 
       moveX = 0; 
       moveY = originalHeight; 
       break; 
     case 'bottom-right': 
       moveX = originalWidth; 
       moveY = originalHeight; 
       break; 
     case 'center':   
       moveX = originalWidth / 2; 
       moveY = originalHeight / 2; 
       break; 
   } 
    
   new Effect.Parallel( 
     [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: opacityTransition }), 
       new Effect.Scale(element, 0, { sync: true, transition: moveTransition }), 
       new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: scaleTransition }) ], 
     options); 
 } 
  
 Effect.Pulsate = function(element) { 
   var options    = arguments[1] || {}; 
   var transition = options.transition || Effect.Transitions.sinoidal; 
   var reverser   = function(pos){ return transition(1-Effect.Transitions.pulse(pos)) }; 
   reverser.bind(transition); 
   new Effect.Opacity(element,  
     Object.extend(Object.extend({  duration: 3.0, 
        afterFinish: function(effect) { Element.show(effect.element); } 
     }, options), {transition: reverser})); 
 } 
  
 Effect.Fold = function(element) { 
  $(element).style.overflow = 'hidden'; 
  new Effect.Scale(element, 5, Object.extend({    
    scaleContent: false, 
    scaleTo: 100, 
    scaleX: false, 
    afterFinish: function(effect) { 
    new Effect.Scale(element, 1, {  
      scaleContent: false,  
      scaleTo: 0, 
      scaleY: false, 
      afterFinish: function(effect) { Element.hide(effect.element) } }); 
  }}, arguments[1] || {})); 
 } 
  
 // old: new Effect.ContentZoom(element, percent) 
 // new: Element.setContentZoom(element, percent)  
  
 Element.setContentZoom = function(element, percent) { 
   var element = $(element); 
   element.style.fontSize = (percent/100) + "em";   
   if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); 
 } 
