String.prototype.base64encode = function(){
  var base64s = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  var bits, dual, i = 0, encOut = '';
  while(this.length >= i + 3){
    bits =
    (	this.charCodeAt(i++) & 0xff) <<16
		| (this.charCodeAt(i++) & 0xff) <<8
		| this.charCodeAt(i++) & 0xff;

		encOut += base64s.charAt((bits & 0x00fc0000) >>18) +
			base64s.charAt((bits & 0x0003f000) >>12) +
			base64s.charAt((bits & 0x00000fc0) >> 6) +
			base64s.charAt((bits & 0x0000003f)
	);
  }
  if(this.length -i > 0 && this.length -i < 3){
    dual = Boolean(this.length -i -1);
    bits =
     ((this.charCodeAt(i++) & 0xff) <<16) |
     (dual ? (this.charCodeAt(i) & 0xff) <<8 : 0);
    encOut +=
      base64s.charAt((bits & 0x00fc0000) >>18) +
      base64s.charAt((bits & 0x0003f000) >>12) +
      (dual ? base64s.charAt((bits & 0x00000fc0) >>6) : '=') +
      '=';
    }
  return encOut
}

String.prototype.base64decode = function() {
  var base64s = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  var bits, decOut = '', i = 0;
  for(i=0; i<this.length; i += 4){
    bits = (base64s.indexOf(this.charAt(i)) & 0xff) <<18 | (base64s.indexOf(this.charAt(i +1)) & 0xff) <<12 | (base64s.indexOf(this.charAt(i +2)) & 0xff) << 6 | base64s.indexOf(this.charAt(i +3)) & 0xff;
    decOut += String.fromCharCode((bits & 0xff0000) >>16, (bits & 0xff00) >>8, bits & 0xff);
    }
  if(this.charCodeAt(i - 2) == 61)
    return decOut.substring(0, decOut.length -2);
  else if(this.charCodeAt(i -1) == 61)
    return decOut.substring(0, decOut.length -1);
  else 
    return decOut.substring(0, decOut.length -2);
}

Element.addMethods({
  	lazyload: function(element, options) {		
    		function $restore() {
      			// this function restores the original image source; called when above the fold
      			if ( true === $(element).hasAttribute('_src') ) {
      				  $(element).writeAttribute({ src: $(element).readAttribute('_src') });
      			}
    		}
    		function $scroll() {
      			// this function returns the amount the page is scrolled vertically
      			var scroll_y = self.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
      			return parseInt(scroll_y);
    		}
    		function $height() {
      			// this function returns the height of the viewport
      			var window_height = window.innerHeight || document.documentElement.clientHeight;
      			return parseInt(window_height);
    		}
    		var element     = $(element);
    		var options     = Object.extend({
    			threshold   : 0,
    			placeholder : '/img/blank.gif',
    			event       : 'scroll',
    			frequency   : 0.1
    		}, options || {});
    
    		var offset      = $(element).cumulativeOffset()[1];
    		var activate_on = (offset - options.threshold) - $height();
    
    		var old_source  = $(element).readAttribute('src');
    		var new_source  = options.placeholder;
    
    		$(element)
    			.writeAttribute({ src :  new_source })
    			.writeAttribute({ '_src' :  old_source });
    		
    		if ( 'scroll' === options.event ) {
      			new PeriodicalExecuter(function($executor) {
      				if ( activate_on <= $scroll() ){
      					 $restore(); $executor.stop();
      				}
      			}, options.frequency);
    		} else {
      			$(element).observe(options.event, function(event) {
      				  $restore(); $(element).stopObserving();
      			});
    		}
    
    		return $(element);
  	}
});
