/*
* jQuery Advanced Effect Queues
* Copyright 2007 John Resig, Luciano Germán Panaro <contact@decodeuri.com>
* Released under the MIT and GPL licenses.
*
* NOTE: Modified to meet some JSLint standards that enables this file to safely be minified.
*/

(function(jQuery){

  var fxQueue = function() {
    return {
      isFxQueue: true,

      paused: false,

      pause: function() {
        if (this[0]) {
          this.paused = true;
          var playing = (this[0].isScope)? this[0] : this[0].elem;
          playing.stop();
        }
      },

      stop: function() {
        if (this[0]) {
          this.paused = false;
          var playing = (this[0].isScope)? this[0] : this[0].elem;
          playing.stop();
          this.length = 0;
        }
      },

      start: function() {
        if (this[0]) {
          this.paused = false;
          this[0]();
        }
      },

      getScope: function( scopeName ) {
          for (var i = 0; i < this.length; i++) {
            if ( this[i].isScope && this[i].called == scopeName ) {
              return this[i];
            }
          }
          return false;
      }
    };
  };

  var fxScope = function ( scopeName ) {
      var newScope = function() {
          for (var i=0; i < newScope.items.length; i++) {
            newScope.items[i]();
          }
      };
      newScope.called = scopeName;
      newScope.isScope = true;
      newScope.finishedItems = 0;
      newScope.stop = function() {
          for (var i=0; i < newScope.items.length; i++) {
            newScope.items[i].elem.stop();
          }
      };
      newScope.items = [];

      return newScope;
  };

  // We need to overload the default animate method
  var animate = jQuery.fn.animate;

  jQuery.fn.animate = function( props, speed, easing, callback ){
    if (this.length < 1) {
      return this;
    }

    // Let normal animations just pass through
    if ( typeof speed == "object" && speed.queue === false ) {
      return animate.apply( this, arguments );

    // We'll handle everything else
    } else {
        var options = (typeof speed == "object")? speed: jQuery.speed(speed, easing, callback);

        // Load in the default options
        var opts = jQuery.extend({
            queue: "fx",
            position: "end",
            limit: -1,
            preDelay: 0,
            postDelay: 0,
            complete: function() {}
        }, options );

        var elem = this;

        // Get the name of the queue
        var queueName = opts.queue;

        // A global queue is centered on 'document'
        var root = opts.queue != "fx" ? document : this;

        // Get the effect queue
        var queue = jQuery(root).queue( opts.queue );

        // Extend the queue object if it's new.
        if ( !queue.isFxQueue ) {
          jQuery.extend(queue, fxQueue());
        }

        // Build in the dequeue operation
        var complete = opts.complete;

        opts.complete = function(){
            // Just dequeue once for every selection
            if(elem[0] == this) {
                var isScope = (queue[0] && queue[0].isScope);

                if (isScope) {
                    var queueItems = queue[0].items;
                    // Find the actual element in scope's items
                    for ( var i=0; i < queueItems.length; i++) {
                        if ( this == queueItems[i].elem[0] && !queueItems[i].finished ) {
                            queueItems[i].finished = true;
                            queue[0].finishedItems++;
                        }
                    }
                }

                // Dequeue
                setTimeout(function(){
                    // If it's not a scope, or if all scope items are finished
                    if ( !isScope || (queue[0] && queue[0].finishedItems == queueItems.length) ) {
                        jQuery(root).dequeue( queueName );
                    }
                }, opts.postDelay);
            }

            // Now let's apply the original callback function
            if (jQuery.isFunction(complete)) {
              return complete.apply( this, arguments );
            }
        };

        // We're overriding the default queueing behavior
        opts.queue = false;

        // The animation to queue
        var fn = function(){
          setTimeout(function(){
              jQuery(fn.elem).animate( props, opts );
          }, opts.preDelay);
        };
        fn.elem = this;

        // If scope exists, just add the animation and return
        var scope = queue.getScope( opts.scope );
        if ( scope ) {
            scope.items.push( fn );

            // Start the animation if the scope is already being played
            if ( queue[0].isScope && queue[0].called == opts.scope) {
              fn();
            }

            return this;
        }

        // Restrict the animation to a specifically sized queue
        if ( opts.limit < 0 || queue.length < opts.limit) {

            var add = null; //What we are going to add into the queue
            if ( opts.scope ) {
                add = fxScope( opts.scope );
                add.items.push(fn);
            } else {
                add = fn;
            }

            if ( opts.position == "end" ) {
              // Put the animation or scope in the right place on the queue
              queue.push( add );
            } else if ( opts.position == "front" ) {
              // Front is actually or scope just after the current animation
              queue.splice( 1, 0, add );
            }

            // If this is the first item in the queue, run immediately
            if ( queue.length == 1 ) {
              queue[0]();
            }
        }

        return this;
      }
  };


  jQuery.fn.stop = function(clearQueue, gotoEnd, playNext){
    var timers = jQuery.timers;

    if (clearQueue) {
      this.queue([]);
    }

    this.each(function(){
      // go in reverse order so anything added to the queue during the loop is ignored
      for ( var i = timers.length - 1; i >= 0; i-- ) {
        if ( timers[i].elem == this ) {
          if (gotoEnd) {
            // force the next step to be the last
            timers[i](true);
          }
          timers.splice(i, 1);
        }
      }
    });

    // start the next in the queue if the last step wasn't forced
    if (playNext) {
      this.dequeue();
    }

    return this;
  };

  // Remove from Speed the complete override, which we now do in Animate
  jQuery.speed = function(speed, easing, fn) {
    var opt = speed && speed.constructor == Object ? speed : {
      complete: fn || function() {} && easing ||
        jQuery.isFunction( speed ) && speed,
      duration: speed,
      easing: fn && easing || easing && easing.constructor != Function && easing
    };

    opt.duration = (opt.duration && opt.duration.constructor == Number ?
      opt.duration :
      { slow: 600, fast: 200 }[opt.duration]) || 400;

    return opt;
  };

  // A simple global fx queue getter
  jQuery.fxqueue = function(queueName) {
    return jQuery(document).queue( queueName );
  };

})(jQuery);
(function($){$.extend({create:function(element,attributes,children){var elem=$(document.createElement(element));if(typeof(attributes)=='object'){for(key in attributes){elem.attr(key,attributes[key]);}}if(typeof(children)=='string'){elem.text(children);}else if(typeof(children)=='object'){for(i=0;i<children.length;i++){elem.append(children[i]);}}return elem;}});})(jQuery);
/**
 * jQuery.Modularize
 * Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
 * Dual licensed under MIT and GPL.
 * Date:4/17/2008
 *
 * @projectDescription Modular methods for jQuery.
 *
 * @author Ariel Flesler
 * @version 1.0.0
 *
 * NOTE: Modified to meet some JSLint standards that enables this file to safely be minified.
 */

/**
 * Note
 *  The plugin is coded in such a way, that no closure is formed.
 *  There're also no references to variables in other scopes.
 *  This is garbage-collection friendly.
 */
/**
 * Note
 *  You can call $.modularize after adding all the methods or before.
 *  If no 'old' function is received, and there was one already stored, it is used.
 */

/**
 * Adds a module-method to jQuery.fn or another holder.
 * @param {String} name The name to take from jQuery.fn (or the holder).
 * @param {Function} old Optional function to be called when $.fn[name]( a, b, c, ... ); is called
 * @param {Function, Map} holder Where to save the function, if none is given, jQuery.fn is used.
 *
 * Note:
 */

jQuery.modularize = function( name, old, holder ){
  var $ = this;//use this instead of jQuery or $.
  holder = holder || $.fn;//the third argument allows you to use this not only on $.fn
  old = old || holder[name];//you can send the function to be used when $().foo( a, b, c ) is called
  holder[name] = $.extend(function(){//this function will be saved into $.fn.foo ('foo' == name)
    return this._modularize_( arguments );
  }, old, {_o_: old} );
};

/* internal don't use */
jQuery.fn._modularize_ = function( args ){
  var me = args.callee;//args.callee == $.fn.foo (the exposed method)
  if( me._o_ && args.length ) { //if there are arguments, then the original is wanted
    return me._o_.apply( this, args );
  }

  //if you want this hash to be regenerated on each call, set $.fn.foo.lazy = true;
  if( !me._ || me.lazy ){
    me._ = {};//stores the "fake" methods
    for( var method in me ){
      if( me[method] && me[method].call && method != '_o_' ){
        me._[method] = function(){
          return arguments.callee._.apply( this._s_, arguments );
        };
        me._[method]._ = me[method];//give the fake function, access to the original
      }
    }
  }
  me._._s_ = this;//store the last collection
  return me._;//the hash with fake methods will be used now
};
/**
 * jQuery (PNG Fix) v1.2
 * Microsoft Internet Explorer 24bit PNG Fix
 *
 * The MIT License
 *
 * Copyright (c) 2007 Paul Campbell (pauljamescampbell.co.uk)
 *
 * 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.
 *
 * NOTE: Modified to meet some JSLint standards that enables this file to safely be minified.
 *
 * @param   Object
 * @return    Array
 */
(function($) {

  $.fn.pngfix = function(options) {

    // Review the Microsoft IE developer library for AlphaImageLoader reference
    // http://msdn2.microsoft.com/en-us/library/ms532969(VS.85).aspx

    // ECMA scope fix
    var elements  = this;
    var settings  = $.extend({
      imageFixSrc:  false,
      sizingMethod:   false
    }, options);

    if(!$.browser.msie || ($.browser.msie &&  $.browser.version >= 7)) {
      return(elements);
    }

    function setFilter(el, path, mode) {
      var fs = el.attr("filters");
      var alpha = "DXImageTransform.Microsoft.AlphaImageLoader";
      if (fs[alpha]) {
        fs[alpha].enabled = true;
        fs[alpha].src = path;
        fs[alpha].sizingMethod = mode;
      } else {
        el.css("filter", 'progid:' + alpha + '(enabled="true", sizingMethod="' + mode + '", src="' + path + '")');
      }
    }

    function setDOMElementWidth(el) {
      if(el.css("width") == "auto" & el.css("height") == "auto") {
        el.css("width", el.attr("offsetWidth") + "px");
      }
    }

    return(
      elements.each(function() {

        // Scope
        var el = $(this);

        if(el.attr("tagName").toUpperCase() == "IMG" && (/\.png/i).test(el.attr("src"))) {
          if(!settings.imageFixSrc) {

            // Wrap the <img> in a <span> then apply style/filters,
            // removing the <img> tag from the final render
            el.wrap("<span></span>");
            var par = el.parent();
            par.css({
              height:   el.height(),
              width:    el.width(),
              display:  "inline-block"
            });
            setFilter(par, el.attr("src"), "scale");
            el.remove();
          } else if((/\.gif/i).test(settings.imageFixSrc)) {

            // Replace the current image with a transparent GIF
            // and apply the filter to the background of the
            // <img> tag (not the preferred route)
            setDOMElementWidth(el);
            setFilter(el, el.attr("src"), "image");
            el.attr("src", settings.imageFixSrc);
          }

        } else {
          var bg = new String(el.css("backgroundImage"));
          var matches = bg.match(/^url\("(.*)"\)$/);
          if(matches && matches.length) {

            // Elements with a PNG as a backgroundImage have the
            // filter applied with a sizing method relevant to the
            // background repeat type
            setDOMElementWidth(el);
            el.css("backgroundImage", "none");

            // Restrict scaling methods to valid MSDN defintions (or one custom)
            var sc = "crop";
            if(settings.sizingMethod) {
              sc = settings.sizingMethod;
            }
            setFilter(el, matches[1], sc);

            // Fix IE peek-a-boo bug for internal links
            // within that DOM element
            el.find("a").each(function() {
              $(this).css("position", "relative");
            });
          }
        }

      })
    );
  };

})(jQuery);
/**
 * jQuery Templates
 *
 * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
 * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
 *
 * Written by: Stan Lemon <stanlemon@mac.com>
 *
 * Based off of the Ext.Template library, available at:
 * http://www.extjs.com
 *
 * This library provides basic templating functionality, allowing for macro-based
 * templates within jQuery.
 *
 * Basic Usage:
 *
 * var t = $.template('<div id="foo">Hello ${name}, how are you ${question}?  I am ${me:substr(0,10)}</div>');
 *
 * $(selector).append( t , {
 *     name: 'Stan',
 *     question: 'feeling',
 *     me: 'doing quite well myself, thank you very much!'
 * });
 *
 * Requires: jQuery 1.2+
 *
 * NOTE: Modified to meet some JSLint standards that enables this file to safely be minified.
 *
 *
 * @todo    Add callbacks to the DOM manipulation methods, so that events can be bound
 *          to template nodes after creation.
 */
(function($){

  /**
   * Create a New Template
   */
  $.template = function(html, options) {
    return new $.template.instance(html, options);
  };

  /**
   * Template constructor - Creates a new template instance.
   *
   * @param   html  The string of HTML to be used for the template.
   * @param   options An object of configurable options.  Currently
   *      you can toggle compile as a boolean value and set a custom
   *          template regular expression on the property regx by
   *          specifying the key of the regx to use from the regx object.
   */
  $.template.instance = function(html, options) {
        // If a custom regular expression has been set, grab it from the regx object
        if ( options && options['regx'] ) { options.regx = this.regx[ options.regx ]; }

    this.options = $.extend({
      compile:    false,
      regx:           this.regx.standard
    }, options || {});

    this.html = html;

    if (this.options.compile) {
      this.compile();
    }
    this.isTemplate = true;
  };

  /**
   * Regular Expression for Finding Variables
   *
   * The default pattern looks for variables in JSP style, the form of: ${variable}
   * There are also regular expressions available for ext-style variables and
   * jTemplate style variables.
   *
   * You can add your own regular expressions for variable ussage by doing.
   * $.extend({ $.template.re , {
   *     myvartype: /...../g
   * }
   *
   * Then when creating a template do:
   * var t = $.template("<div>...</div>", { regx: 'myvartype' });
   */
  $.template.regx = $.template.instance.prototype.regx = {
      jsp:        /\$\{([\w-]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?\}/g,
        ext:        /\{([\w-]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?\}/g,
        jtemplates: /\{\{([\w-]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?\}\}/g
  };

  /**
   * Set the standard regular expression to be used.
   */
  $.template.regx.standard = $.template.regx.jsp;

  /**
   * Variable Helper Methods
   *
   * This is a collection of methods which can be used within the variable syntax, ie:
   * ${variable:substr(0,30)} Which would only print a substring, 30 characters in length
   * begining at the first character for the variable named "variable".
   *
   * A basic substring helper is provided as an example of how you can define helpers.
   * To add more helpers simply do:
   * $.extend( $.template.helpers , {
   *   sampleHelper: function() { ... }
   * });
   */
  $.template.helpers = $.template.instance.prototype.helpers = {
    substr : function(value, start, length){
      return String(value).substr(start, length);
    }
  };


  /**
   * Template Instance Methods
   */
  $.extend( $.template.instance.prototype, {

    /**
     * Apply Values to a Template
     *
     * This is the macro-work horse of the library, it receives an object
     * and the properties of that objects are assigned to the template, where
     * the variables in the template represent keys within the object itself.
     *
     * @param   values  An object of properties mapped to template variables
     */
    apply: function(values) {
      if (this.options.compile) {
        return this.compiled(values);
      } else {
        var tpl = this;
        var fm = this.helpers;

        var fn = function(m, name, format, args) {
          if (format) {
            if (format.substr(0, 5) == "this."){
              return tpl.call(format.substr(5), values[name], values);
            } else {
              if (args) {
                // quoted values are required for strings in compiled templates,
                // but for non compiled we need to strip them
                // quoted reversed for jsmin
                var re = /^\s*['"](.*)["']\s*$/;
                args = args.split(',');

                for(var i = 0, len = args.length; i < len; i++) {
                  args[i] = args[i].replace(re, "$1");
                }
                args = [values[name]].concat(args);
              } else {
                args = [values[name]];
              }

              return fm[format].apply(fm, args);
            }
          } else {
            return values[name] !== undefined ? values[name] : "";
          }
        };

        return this.html.replace(this.options.regx, fn);
      }
    },

    /**
     * Compile a template for speedier usage
     */
    compile: function() {
      var sep = $.browser.mozilla ? "+" : ",";
      var fm = this.helpers;

      var fn = function(m, name, format, args){
        if (format) {
          args = args ? ',' + args : "";

          if (format.substr(0, 5) != "this.") {
            format = "fm." + format + '(';
          } else {
            format = 'this.call("'+ format.substr(5) + '", ';
            args = ", values";
          }
        } else {
          args= ''; format = "(values['" + name + "'] == undefined ? '' : ";
        }
        return "'"+ sep + format + "values['" + name + "']" + args + ")"+sep+"'";
      };

      var body;

      if ($.browser.mozilla) {
        body = "this.compiled = function(values){ return '" +
             this.html.replace(/\\/g, '\\\\').replace(/(\r\n|\n)/g, '\\n').replace(/'/g, "\\'").replace(this.options.regx, fn) +
            "';};";
      } else {
        body = ["this.compiled = function(values){ return ['"];
        body.push(this.html.replace(/\\/g, '\\\\').replace(/(\r\n|\n)/g, '\\n').replace(/'/g, "\\'").replace(this.options.regx, fn));
        body.push("'].join('');};");
        body = body.join('');
      }
      eval(body);
      return this;
    }
  });


  /**
   * Save a reference in this local scope to the original methods which we're
   * going to overload.
   **/
  var $_old = {
      domManip: $.fn.domManip,
      text: $.fn.text,
      html: $.fn.html
  };

  /**
   * Overwrite the domManip method so that we can use things like append() by passing a
   * template object and macro parameters.
   */
  $.fn.domManip = function( args, table, reverse, callback ) {
    if (args[0].isTemplate) {
      // Apply the template and it's arguments...
      args[0] = args[0].apply( args[1] );
      // Get rid of the arguements, we don't want to pass them on
      delete args[1];
    }

    // Call the original method
    var r = $_old.domManip.apply(this, arguments);

    return r;
  };

    /**
     * Overwrite the html() method
     */
  $.fn.html = function( value , o ) {
      if (value && value.isTemplate) { value = value.apply( o ); }

    var r = $_old.html.apply(this, [value]);

    return r;
  };

  /**
   * Overwrite the text() method
   */
  $.fn.text = function( value , o ) {
      if (value && value.isTemplate) { value = value.apply( o ); }

    var r = $_old.text.apply(this, [value]);

    return r;
  };

})(jQuery);
/*
 * FancyBox - simple jQuery plugin for fancy image zooming
 * Examples and documentation at: http://fancy.klade.lv/
 * Version: 1.0.0 (29/04/2008)
 * Copyright (c) 2008 Janis Skarnelis
 * Licensed under the MIT License: http://www.opensource.org/licenses/mit-license.php
 * Requires: jQuery v1.2.1 or later
 *
 * NOTE: Modified to meet some JSLint standards that enables this file to safely be minified.
*/
(function($) {
  var opts = {},
    imgPreloader = new Image, imgTypes = ['png', 'jpg', 'jpeg', 'gif'],
    loadingTimer, loadingFrame = 1;

   $.fn.fancybox = function(settings) {
    opts.settings = $.extend({}, $.fn.fancybox.defaults, settings);

    $.fn.fancybox.init();

    return this.each(function() {
      var $this = $(this);
      var o = $.metadata ? $.extend({}, opts.settings, $this.metadata()) : opts.settings;

      $this.unbind('click').click(function() {
        $.fn.fancybox.start(this, o); return false;
      });
    });
  };

  $.fn.fancybox.start = function(el, o) {
    if (opts.animating) { return false; }

    if (o.overlayShow) {
      $("#fancy_wrap").prepend('<div id="fancy_overlay"></div>');
      $("#fancy_overlay").css({'width': $(window).width(), 'height': $(document).height(), 'opacity': o.overlayOpacity});

      if ($.browser.msie) {
        $("#fancy_wrap").prepend('<iframe id="fancy_bigIframe" scrolling="no" frameborder="0"></iframe>');
        $("#fancy_bigIframe").css({'width': $(window).width(), 'height': $(document).height(), 'opacity': 0});
      }

      $("#fancy_overlay").click($.fn.fancybox.close);
    }

    opts.itemArray  = [];
    opts.itemNum  = 0;

    if (jQuery.isFunction(o.itemLoadCallback)) {
       o.itemLoadCallback.apply(this, [opts]);

      var c = $(el).children("img:first").length ? $(el).children("img:first") : $(el);
      var tmp = {'width': c.width(), 'height': c.height(), 'pos': $.fn.fancybox.getPosition(c)};

       for (var i = 0; i < opts.itemArray.length; i++) {
        opts.itemArray[i].o = $.extend({}, o, opts.itemArray[i].o);

        if (o.zoomSpeedIn > 0 || o.zoomSpeedOut > 0) {
          opts.itemArray[i].orig = tmp;
        }
       }

    } else {
      if (!el.rel || el.rel === '') {
        var item = {url: el.href, title: el.title, o: o};

        if (o.zoomSpeedIn > 0 || o.zoomSpeedOut > 0) {
          c = $(el).children("img:first").length ? $(el).children("img:first") : $(el);
          item.orig = {'width': c.width(), 'height': c.height(), 'pos': $.fn.fancybox.getPosition(c)};
        }

        opts.itemArray.push(item);

      } else {
        var arr = $("a[@rel=" + el.rel + "]").get();

        for (i = 0; i < arr.length; i++) {
          tmp   = $.metadata ? $.extend({}, o, $(arr[i]).metadata()) : o;
            item  = {url: arr[i].href, title: arr[i].title, o: tmp};

            if (o.zoomSpeedIn > 0 || o.zoomSpeedOut > 0) {
            c = $(arr[i]).children("img:first").length ? $(arr[i]).children("img:first") : $(el);

            item.orig = {'width': c.width(), 'height': c.height(), 'pos': $.fn.fancybox.getPosition(c)};
          }

          if (arr[i].href == el.href) { opts.itemNum = i; }

          opts.itemArray.push(item);
        }
      }
    }

    $.fn.fancybox.changeItem(opts.itemNum);
  };

  $.fn.fancybox.changeItem = function(n) {
    $.fn.fancybox.showLoading();

    opts.itemNum = n;

    $("#fancy_nav").empty();
    $("#fancy_outer").stop();
    $("#fancy_title").hide();
    $(document).unbind("keydown");

    imgRegExp = imgTypes.join('|');
      imgRegExp = new RegExp('\.' + imgRegExp + '$', 'i');

    var url = opts.itemArray[n].url;

    if (url.match(/#/)) {
      var target = window.location.href.split('#')[0]; target = url.replace(target,'');

          $.fn.fancybox.showItem('<div id="fancy_div">' + $(target).html() + '</div>');

          $("#fancy_loading").hide();

    } else if (url.match(imgRegExp)) {
      $(imgPreloader).unbind('load').bind('load', function() {
        $("#fancy_loading").hide();

        opts.itemArray[n].o.frameWidth  = imgPreloader.width;
        opts.itemArray[n].o.frameHeight = imgPreloader.height;

        $.fn.fancybox.showItem('<img id="fancy_img" src="' + imgPreloader.src + '" />');

      }).attr('src', url + '?rand=' + Math.floor(Math.random() * 999999999) );

    } else {
      $.fn.fancybox.showItem('<iframe id="fancy_frame" onload="jQuery.fn.fancybox.showIframe()" name="fancy_iframe' + Math.round(Math.random()*1000) + '" frameborder="0" hspace="0" src="' + url + '"></iframe>');
    }
  };

  $.fn.fancybox.showIframe = function() {
    $("#fancy_loading").hide();
    $("#fancy_frame").show();
  };

  $.fn.fancybox.showItem = function(val) {
    $.fn.fancybox.preloadNeighborImages();

    var viewportPos = $.fn.fancybox.getViewport();
    var itemSize  = $.fn.fancybox.getMaxSize(viewportPos[0] - 50, viewportPos[1] - 100, opts.itemArray[opts.itemNum].o.frameWidth, opts.itemArray[opts.itemNum].o.frameHeight);

    var itemLeft  = viewportPos[2] + Math.round((viewportPos[0] - itemSize[0]) / 2) - 20;
    var itemTop   = viewportPos[3] + Math.round((viewportPos[1] - itemSize[1]) / 2) - 40;

    var itemOpts = {
      'left':   itemLeft,
      'top':    itemTop,
      'width':  itemSize[0] + 'px',
      'height': itemSize[1] + 'px'
    };

    if (opts.active) {
      $('#fancy_content').fadeOut("normal", function() {
        $("#fancy_content").empty();

        $("#fancy_outer").animate(itemOpts, "normal", function() {
          $("#fancy_content").append($(val)).fadeIn("normal");
          $.fn.fancybox.updateDetails();
        });
      });

    } else {
      opts.active = true;

      $("#fancy_content").empty();

      if ($("#fancy_content").is(":animated")) {
        console.info('animated!');
      }

      if (opts.itemArray[opts.itemNum].o.zoomSpeedIn > 0) {
        opts.animating    = true;
        itemOpts.opacity  = "show";

        $("#fancy_outer").css({
          'top':    opts.itemArray[opts.itemNum].orig.pos.top - 18,
          'left':   opts.itemArray[opts.itemNum].orig.pos.left - 18,
          'height': opts.itemArray[opts.itemNum].orig.height,
          'width':  opts.itemArray[opts.itemNum].orig.width
        });

        $("#fancy_content").append($(val)).show();

        $("#fancy_outer").animate(itemOpts, opts.itemArray[opts.itemNum].o.zoomSpeedIn, function() {
          opts.animating = false;
          $.fn.fancybox.updateDetails();
        });

      } else {
        $("#fancy_content").append($(val)).show();
        $("#fancy_outer").css(itemOpts).show();
        $.fn.fancybox.updateDetails();
      }
     }
  };

  $.fn.fancybox.updateDetails = function() {
    $("#fancy_bg,#fancy_close").show();

    if (opts.itemArray[opts.itemNum].title !== undefined && opts.itemArray[opts.itemNum].title !== '') {
      $('#fancy_title div').html(opts.itemArray[opts.itemNum].title);
      $('#fancy_title').show();
    }

    if (opts.itemArray[opts.itemNum].o.hideOnContentClick) {
      $("#fancy_content").click($.fn.fancybox.close);
    } else {
      $("#fancy_content").unbind('click');
    }

    if (opts.itemNum !== 0) {
      $("#fancy_nav").append('<a id="fancy_left" href="javascript:;"></a>');

      $('#fancy_left').click(function() {
        $.fn.fancybox.changeItem(opts.itemNum - 1); return false;
      });
    }

    if (opts.itemNum != (opts.itemArray.length - 1)) {
      $("#fancy_nav").append('<a id="fancy_right" href="javascript:;"></a>');

      $('#fancy_right').click(function(){
        $.fn.fancybox.changeItem(opts.itemNum + 1); return false;
      });
    }

    $(document).keydown(function(event) {
      if (event.keyCode == 27) {
              $.fn.fancybox.close();

      } else if(event.keyCode == 37 && opts.itemNum !== 0) {
              $.fn.fancybox.changeItem(opts.itemNum - 1);

      } else if(event.keyCode == 39 && opts.itemNum != (opts.itemArray.length - 1)) {
              $.fn.fancybox.changeItem(opts.itemNum + 1);
      }
    });
  };

  $.fn.fancybox.preloadNeighborImages = function() {
    if ((opts.itemArray.length - 1) > opts.itemNum) {
      preloadNextImage = new Image();
      preloadNextImage.src = opts.itemArray[opts.itemNum + 1].url;
    }

    if (opts.itemNum > 0) {
      preloadPrevImage = new Image();
      preloadPrevImage.src = opts.itemArray[opts.itemNum - 1].url;
    }
  };

  $.fn.fancybox.close = function() {
    if (opts.animating) { return false; }

    $(imgPreloader).unbind('load');
    $(document).unbind("keydown");

    $("#fancy_loading,#fancy_title,#fancy_close,#fancy_bg").hide();

    $("#fancy_nav").empty();

    opts.active = false;

    if (opts.itemArray[opts.itemNum].o.zoomSpeedOut > 0) {
      var itemOpts = {
        'top':    opts.itemArray[opts.itemNum].orig.pos.top - 18,
        'left':   opts.itemArray[opts.itemNum].orig.pos.left - 18,
        'height': opts.itemArray[opts.itemNum].orig.height,
        'width':  opts.itemArray[opts.itemNum].orig.width,
        'opacity':  'hide'
      };

      opts.animating = true;

      $("#fancy_outer").animate(itemOpts, opts.itemArray[opts.itemNum].o.zoomSpeedOut, function() {
        $("#fancy_content").hide().empty();
        $("#fancy_overlay,#fancy_bigIframe").remove();
        opts.animating = false;
      });

    } else {
      $("#fancy_outer").hide();
      $("#fancy_content").hide().empty();
      $("#fancy_overlay,#fancy_bigIframe").fadeOut("fast").remove();
    }
  };

  $.fn.fancybox.showLoading = function() {
    clearInterval(loadingTimer);

    var pos = $.fn.fancybox.getViewport();

    $("#fancy_loading").css({'left': ((pos[0] - 40) / 2 + pos[2]), 'top': ((pos[1] - 40) / 2 + pos[3])}).show();
    $("#fancy_loading").bind('click', $.fn.fancybox.close);

    loadingTimer = setInterval($.fn.fancybox.animateLoading, 66);
  };

  $.fn.fancybox.animateLoading = function(el, o) {
    if (!$("#fancy_loading").is(':visible')){
      clearInterval(loadingTimer);
      return;
    }

    $("#fancy_loading > div").css('top', (loadingFrame * -40) + 'px');

    loadingFrame = (loadingFrame + 1) % 12;
  };

  $.fn.fancybox.init = function() {
    if (!$('#fancy_wrap').length) {
      $('<div id="fancy_wrap"><div id="fancy_loading"><div></div></div><div id="fancy_outer"><div id="fancy_inner"><div id="fancy_nav"></div><div id="fancy_close"></div><div id="fancy_content"></div><div id="fancy_title"></div></div></div></div>').appendTo("body");
      $('<div id="fancy_bg"><div class="fancy_bg fancy_bg_n"></div><div class="fancy_bg fancy_bg_ne"></div><div class="fancy_bg fancy_bg_e"></div><div class="fancy_bg fancy_bg_se"></div><div class="fancy_bg fancy_bg_s"></div><div class="fancy_bg fancy_bg_sw"></div><div class="fancy_bg fancy_bg_w"></div><div class="fancy_bg fancy_bg_nw"></div></div>').prependTo("#fancy_inner");

      $('<table cellspacing="0" cellpadding="0" border="0"><tr><td id="fancy_title_left"></td><td id="fancy_title_main"><div></div></td><td id="fancy_title_right"></td></tr></table>').appendTo('#fancy_title');
    }

    if ($.browser.msie) {
      $("#fancy_inner").prepend('<iframe id="fancy_freeIframe" scrolling="no" frameborder="0"></iframe>');
    }

    if (jQuery.fn.pngFix) { $(document).pngFix(); }

      $("#fancy_close").click($.fn.fancybox.close);
  };

  $.fn.fancybox.getPosition = function(el) {
    var pos = el.offset();

    pos.top += $.fn.fancybox.num(el, 'paddingTop');
    pos.top += $.fn.fancybox.num(el, 'borderTopWidth');

    pos.left += $.fn.fancybox.num(el, 'paddingLeft');
    pos.left += $.fn.fancybox.num(el, 'borderLeftWidth');

    return pos;
  };

  $.fn.fancybox.num = function (el, prop) {
    return parseInt($.curCSS(el.jquery?el[0]:el,prop,true), 10)||0;
  };

  $.fn.fancybox.getPageScroll = function() {
    var xScroll, yScroll;

    if (self.pageYOffset) {
      yScroll = self.pageYOffset;
      xScroll = self.pageXOffset;
    } else if (document.documentElement && document.documentElement.scrollTop) {
      yScroll = document.documentElement.scrollTop;
      xScroll = document.documentElement.scrollLeft;
    } else if (document.body) {
      yScroll = document.body.scrollTop;
      xScroll = document.body.scrollLeft;
    }

    return [xScroll, yScroll];
  };

  $.fn.fancybox.getViewport = function() {
    var scroll = $.fn.fancybox.getPageScroll();

    return [$(window).width(), $(window).height(), scroll[0], scroll[1]];
  };

  $.fn.fancybox.getMaxSize = function(maxWidth, maxHeight, imageWidth, imageHeight) {
    var r = Math.min(Math.min(maxWidth, imageWidth) / imageWidth, Math.min(maxHeight, imageHeight) / imageHeight);

    return [Math.round(r * imageWidth), Math.round(r * imageHeight)];
  };

  $.fn.fancybox.defaults = {
    hideOnContentClick: false,
    zoomSpeedIn:    500,
    zoomSpeedOut:   500,
    frameWidth:     600,
    frameHeight:    400,
    overlayShow:    false,
    overlayOpacity:   0.4,
    itemLoadCallback: null
  };
})(jQuery);
/* ===========================================================================
 *
 * JQuery URL Parser
 * Version 1.0
 * Parses URLs and provides easy access to information within them.
 *
 * Author: Mark Perkins
 * Author email: mark@allmarkedup.com
 *
 * For full documentation and more go to http://projects.allmarkedup.com/jquery_url_parser/
 *
 * ---------------------------------------------------------------------------
 *
 * CREDITS:
 *
 * Parser based on the Regex-based URI parser by Stephen Levithian.
 * For more information (including a detailed explaination of the differences
 * between the 'loose' and 'strict' pasing modes) visit http://blog.stevenlevithan.com/archives/parseuri
 *
 * ---------------------------------------------------------------------------
 *
 * LICENCE:
 *
 * Released under a MIT Licence. See licence.txt that should have been supplied with this file,
 * or visit http://projects.allmarkedup.com/jquery_url_parser/licence.txt
 *
 * ---------------------------------------------------------------------------
 *
 * EXAMPLES OF USE:
 *
 * Get the domain name (host) from the current page URL
 * jQuery.url.attr("host")
 *
 * Get the query string value for 'item' for the current page
 * jQuery.url.param("item") // null if it doesn't exist
 *
 * Get the second segment of the URI of the current page
 * jQuery.url.segment(2) // null if it doesn't exist
 *
 * Get the protocol of a manually passed in URL
 * jQuery.url.setUrl("http://allmarkedup.com/").attr("protocol") // returns 'http'
 *
 */

jQuery.url = function()
{
	var segments = {};

	var parsed = {};

	/**
    * Options object. Only the URI and strictMode values can be changed via the setters below.
    */
  	var options = {

		url : window.location, // default URI is the page in which the script is running

		strictMode: false, // 'loose' parsing by default

		key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], // keys available to query

		q: {
			name: "queryKey",
			parser: /(?:^|&)([^&=]*)=?([^&]*)/g
		},

		parser: {
			strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/, // more intuitive, fails on relative paths and deviates from specs
			loose:  /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/ //less intuitive, more accurate to the specs
		}

	};

    /**
     * Deals with the parsing of the URI according to the regex above.
 	 * Written by Steven Levithan - see credits at top.
     */
	var parseUri = function()
	{
		str = decodeURI( options.url );

		var m = options.parser[ options.strictMode ? "strict" : "loose" ].exec( str );
		var uri = {};
		var i = 14;

		while ( i-- ) {
			uri[ options.key[i] ] = m[i] || "";
		}

		uri[ options.q.name ] = {};
		uri[ options.key[12] ].replace( options.q.parser, function ( $0, $1, $2 ) {
			if ($1) {
				uri[options.q.name][$1] = $2;
			}
		});

		return uri;
	};

    /**
     * Returns the value of the passed in key from the parsed URI.
  	 *
	 * @param string key The key whose value is required
     */
	var key = function( key )
	{
		if ( ! parsed.length )
		{
			setUp(); // if the URI has not been parsed yet then do this first...
		}
		if ( key == "base" )
		{
			if ( parsed.port !== null && parsed.port !== "" )
			{
				return parsed.protocol+"://"+parsed.host+":"+parsed.port+"/";
			}
			else
			{
				return parsed.protocol+"://"+parsed.host+"/";
			}
		}

		return ( parsed[key] === "" ) ? null : parsed[key];
	};

	/**
     * Returns the value of the required query string parameter.
  	 *
	 * @param string item The parameter whose value is required
     */
	var param = function( item )
	{
		if ( ! parsed.length )
		{
			setUp(); // if the URI has not been parsed yet then do this first...
		}
		return ( parsed.queryKey[item] === null ) ? null : parsed.queryKey[item];
	};

    /**
     * 'Constructor' (not really!) function.
     *  Called whenever the URI changes to kick off re-parsing of the URI and splitting it up into segments.
     */
	var setUp = function()
	{
		parsed = parseUri();

		getSegments();
	};

    /**
     * Splits up the body of the URI into segments (i.e. sections delimited by '/')
     */
	var getSegments = function()
	{
		var p = parsed.path;
		segments = []; // clear out segments array
		segments = parsed.path.length == 1 ? {} : ( p.charAt( p.length - 1 ) == "/" ? p.substring( 1, p.length - 1 ) : path = p.substring( 1 ) ).split("/");
	};

	return {

	    /**
	     * Sets the parsing mode - either strict or loose. Set to loose by default.
	     *
	     * @param string mode The mode to set the parser to. Anything apart from a value of 'strict' will set it to loose!
	     */
		setMode : function( mode )
		{
			strictMode = mode == "strict" ? true : false;
			return this;
		},

		/**
	     * Sets URI to parse if you don't want to to parse the current page's URI.
		 * Calling the function with no value for newUri resets it to the current page's URI.
	     *
	     * @param string newUri The URI to parse.
	     */
		setUrl : function( newUri )
		{
			options.url = newUri === undefined ? window.location : newUri;
			setUp();
			return this;
		},

		/**
	     * Returns the value of the specified URI segment. Segments are numbered from 1 to the number of segments.
		 * For example the URI http://test.com/about/company/ segment(1) would return 'about'.
		 *
		 * If no integer is passed into the function it returns the number of segments in the URI.
	     *
	     * @param int pos The position of the segment to return. Can be empty.
	     */
		segment : function( pos )
		{
			if ( ! parsed.length )
			{
				setUp(); // if the URI has not been parsed yet then do this first...
			}
			if ( pos === undefined )
			{
				return segments.length;
			}
			return ( segments[pos] === "" || segments[pos] === undefined ) ? null : segments[pos];
		},

		attr : key, // provides public access to private 'key' function - see above

		param : param // provides public access to private 'param' function - see above

	};

}();
/*
 * brokenImage: a jQuery plugin
 *
 * brokenImage is a jQuery plugin that is able to detect and replace images
 * that are either broken or are taking a long time to load.  The default
 * replacement is a transparent GIF (no extra image file required).  The
 * replacement image and the timeout for slow-loading images are configurable.
 *
 * For usage and examples, visit:
 * http://github.com/alexrabarts/jquery-brokenimage/tree/master
 *
 * Licensed under the MIT:
 * http://www.opensource.org/licenses/mit-license.php
 *
 * Copyright (c) 2008 Stateless Systems (http://statelesssystems.com)
 *
 * @author   Alex Rabarts (alexrabarts -at- gmail -dawt- com)
 * @requires jQuery v1.2 or later
 * @version  0.2
 */

(function ($) {
  $.extend($.fn, {
    brokenImage: function (options) {
      var defaults = {timeout: 5000};

      options = $.extend(defaults, options);

      return this.each(function () {
        var image = this;

        $(image).bind('error', function () { insertPlaceholder(); });

        setTimeout(function () {
          var test = new Image(); // Virgin image with no styles to affect dimensions
          test.src = image.src;

          if (test.height === 0) { insertPlaceholder(); }
        }, options.timeout);

        function insertPlaceholder() {
          options.replacement ? image.src = options.replacement : $(image).css({visibility: 'hidden'});
        }
      });
    }
  });
})(jQuery);
/*
 * newWindow: a jQuery plugin
 *
 * newWindow is a super-simple jQuery plugin for opening new browser windows
 * when an anchor is clicked.
 *
 * For usage and examples, visit:
 * http://github.com/alexrabarts/jquery-newwindow
 *
 * Licensed under the MIT:
 * http://www.opensource.org/licenses/mit-license.php
 *
 * Copyright (c) 2008 Stateless Systems (http://statelesssystems.com)
 *
 * @author   Alex Rabarts (alexrabarts -at- gmail -dawt- com)
 * @requires jQuery v1.2 or later
 * @version  0.1
 */

(function ($) {
  $.extend($.fn, {
    newWindow: function (options) {
      var defaults = {open: function () {}};
      options = $.extend(defaults, options || {});

      return this.each(function () {
        $(this).click(function (e) {
          e.preventDefault();
          var newWindow = open(this.href);
          options.open.call(newWindow, e);
        });
      });
    }
  });
})(jQuery);
/*
 * clearingInput: a jQuery plugin
 *
 * clearingInput is a simple jQuery plugin that provides example/label text
 * inside text inputs that automatically clears when the input is focused.
 * Common uses are for a hint/example, or as a label when space is limited.
 *
 * For usage and examples, visit:
 * http://github.com/alexrabarts/jquery-clearinginput
 *
 * Licensed under the MIT:
 * http://www.opensource.org/licenses/mit-license.php
 *
 * Copyright (c) 2008 Stateless Systems (http://statelesssystems.com)
 *
 * @author   Alex Rabarts (alexrabarts -at- gmail -dawt- com)
 * @requires jQuery v1.2 or later
 * @version  0.1.1
 */

(function ($) {
  $.extend($.fn, {
    clearingInput: function (options) {
      var defaults = {blurClass: 'blur'};

      options = $.extend(defaults, options);

      return this.each(function () {
        var input = $(this).addClass(options.blurClass);
        var form  = input.parents('form:first');
        var label, text;

        text = options.text || textFromLabel() || input.val();

        if (text) {
          input.val(text);

          input.blur(function () {
            if (input.val() === '') {
              input.val(text).addClass(options.blurClass);
            }
          }).focus(function () {
            if (input.val() === text) {
              input.val('');
            }
            input.removeClass(options.blurClass);
          });

          form.submit(function() {
            if (input.hasClass(options.blurClass)) {
              input.val('');
            }
          });

          input.blur();
        }

        function textFromLabel() {
          label = form.find('label[for=' + input.attr('id') + ']');
          // Position label off screen and use it for the input text
          return label ? label.css({position: 'absolute', left: '-9999px'}).text() : '';
        }
      });
    }
  });
})(jQuery);
/*
 * cacheImage: a jQuery plugin
 *
 * cacheImage is a simple jQuery plugin for pre-caching images.  The
 * plugin can be used to eliminate flashes of unstyled content (FOUC) and
 * improve perceived page load time.  Callbacks for load, error and abort
 * events are provided.
 *
 * For usage and examples, visit:
 * http://github.com/alexrabarts/jquery-cacheimage
 *
 * Licensed under the MIT:
 * http://www.opensource.org/licenses/mit-license.php
 *
 * Copyright (c) 2008 Stateless Systems (http://statelesssystems.com)
 *
 * @author   Alex Rabarts (alexrabarts -at- gmail -dawt- com)
 * @requires jQuery v1.2 or later
 * @version  0.1
 */

 (function ($) {
   $.extend($, {
     cacheImage: function (src, options) {
       if (typeof src === 'object') {
         $.each(src, function () {
           $.cacheImage(String(this), options);
         });
       }

       var image = new Image();

       options = options || {};

       $.each(['load', 'error', 'abort'], function () { // Callbacks
         var e = String(this);
         if (typeof options[e] === 'function') { $(image)[e](options[e]); }
       });

       image.src = src;

       return image;
     }
   });

   $.extend($.fn, {
     cacheImage: function (options) {
       return this.each(function () {
         $.cacheImage(this.src, options);
       });
     }
   });
 })(jQuery);
/*
 * platformSelector: a jQuery plugin
 *
 * platformSelector is a simple jQuery plugin that adds classes to the body
 * element representing the browser's environment.  It adds classes for browser
 * type, browser version, operating system, rendering engine and JS-enabled.
 *
 * For full documentation, visit:
 * http://github.com/alexrabarts/jquery-platformselector
 *
 * Licensed under the MIT:
 * http://www.opensource.org/licenses/mit-license.php
 *
 * Copyright (c) 2008 Stateless Systems (http://statelesssystems.com)
 *
 * @author   Alex Rabarts (alexrabarts -at- gmail -dawt- com)
 * @requires jQuery v1.2 or later
 * @version  0.1
 */

(function ($) {
  function t(str, test) { return str.indexOf(test) !== -1; }
  function uaIs(test) { return t(ua, test); }

  var ua = navigator.userAgent.toLowerCase();

  var ie = (!uaIs('opera') && !uaIs('webtv') && (/msie (\d+)/.test(ua))) ? 'ie ie_' + RegExp.$1 : '';

  var os =
    (uaIs('x11') || uaIs('linux')) ? 'linux' :
    uaIs('mac')                    ? 'mac'   :
    uaIs('win')                    ? 'win'   : '';

  var classNames = [ie, os, 'js'];

  var agents = ua.split(' ');

  $.each(agents, function () {
    if (!this.match(/(\w+)\/([^\s]+)/)) {
      return;
    }

    var agent   = RegExp.$1;
    var fullVersion = RegExp.$2.replace(/[\/\.]/g, '_');
    var majorVersion = fullVersion.replace(/_.*/, '');

    classNames  = classNames.concat([agent, agent + '_' + fullVersion, agent + '_' + majorVersion]);
  });

  // Safari splits the user agent into Safairi/<build> and Version/<version> so rename Version -> Safari
  if ($.inArray('safari', classNames) && $.inArray('version', classNames)) {
    classNames = $.map(classNames, function (c) {
      return c.replace('version', 'safari');
    });
  }

  // Chrome incorrectly identifies itself as Safari
  if ($.inArray('chrome', classNames)) {
    classNames = $.map(classNames, function (c) {
      return c.match(/^safari/) ? null : c;
    });
  }

  var engineVersion = (
    (ua.match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/) || [0,'0'])[1] // From jQuery 1.2.6
  ).replace(/\./g, '_');

  $.each(['opera', 'applewebkit', 'gecko'], function () {
    if ($.inArray(String(this), classNames) !== -1) {
      classNames.push(this + '_' + engineVersion);
      return false;
    }
  });

  // Deduplicate.  $.unique is only for DOM elements :-(
  var seen = {};
  classNames = $.map(classNames, function (c) {
    if (seen[c] || c.match(/^mozilla/)) { // Mozilla so abused it's pretty much useless
      return null;
    } else {
      seen[c] = true;
      return c;
    }
  }).join(' ');

  var html = $('html');
  html.addClass(classNames); // Add to the html element now to avoid any FOUC

  $(function () {
    html.removeAttr('class');
    $('body').addClass(classNames);
  });
})(jQuery);
jQuery.noConflict();

(function ($) {
  $.fn.bmp = {};
  $.modularize('bmp');

  $.bmp = {
    bind: function (method, obj) {
      var temp = function () {
        return method.apply(obj, arguments);
      };

      return temp;
    }
  };
})(jQuery);
// truncate sourced from Prototype
// (c) 2005-2008 Sam Stephenson
// Prototype is freely distributable under the terms of an MIT-style license.
//  For details, see the Prototype web site: http://www.prototypejs.org/
(function ($) {
  $.extend($.bmp, {
    truncate: function (text, length, truncation) {
      length = length || 30;
      truncation = (typeof truncation === 'undefined') ? '...' : truncation;
      return text.length > length ?
        text.slice(0, length - truncation.length) + truncation : String(text);
    },

    // Wrapper on $.template for purely string operations
    interpolate: function (template, data) {
      return $.template(template).apply(data);
    }
  });
})(jQuery);
// The idea here is that if you have an <input id="name" /> then the help with id #h_name will be
// displayed when the input is focused and hidden when it is blurred.
(function ($) {
  $.extend($.fn.bmp, {
    inputHelpTip: function (options) {
      var defaults = {
        idPrefix    : 'h_', // String to prefix to input id to create help id
        defaultInput: null  // Default input to show help for
      };

      options = $.extend(defaults, options);

      var total       = this.length;
      var defaultHelp = helpFromInput(defaults.defaultInput);

      function helpFromInput(input) {
        return $('#' + options.idPrefix + $(input).attr('id'));
      }

      return this.each(function () {
        var input = $(this);
        var help  = helpFromInput(input);
        if (!isDefault()) {
          help.hide();
        }

        input.blur(function () {
          if (!isDefault()) {
            help.fadeOut();
          }
          $.fn.bmp.inputHelpTip._active = false;
          // Going from one input straight to another causes an instant blur/focus event which
          // makes 'avtive' momentarily change to true.  The setTimeout works around this.
          setTimeout(function () {
            if (!$.fn.bmp.inputHelpTip._active) {
              defaultHelp.fadeIn();
            }
          }, 0);
        })
        .focus(function () {
          if (!isDefault()) {
            defaultHelp.fadeOut();
          }
          help.fadeIn();
          $.fn.bmp.inputHelpTip._active = true;
        });

        function isDefault() {
          return (help.attr('id') === defaultHelp.attr('id'));
        }
      });
    }
  });
})(jQuery);
(function ($) {
  $.extend($.fn.bmp, {
    progressBar: function (options) {
      var bar             = this;
      var percentComplete = 0;
      var defaults        = {
        queue: 'progressBar'
      };

      var percentBar = function () {
        return bar.find(':first-child');
      };

      options = $.extend(defaults, options);

      if (bar.children().length === 0) {
        bar.append(jQuery.create('div'));
      }

      this.percent = function (percent, percentOptions) {
        if (arguments.length === 0) {
          return percentComplete;
        }

        percentComplete = percent;

        var defaults = {
          duration: 400
        };

        percentOptions = $.extend(defaults, percentOptions);

        var newWidth = bar.width() * percent / 100;

        percentBar().animate({ width: newWidth + 'px' }, $.extend(options, percentOptions));

        return bar;
      };

      this.queue = function() {
        return options.queue;
      };

      percentBar().width(0);

      // TODO: Return for multiple elements, not just the one
      return this;
    }
  });
})(jQuery);
// Note, unload AJAX calls only work in IE/Firefox.
// We may want to move final_click events server-side at some point.

(function ($) {
  $.extend($.bmp, {
    // Data for final_click event, which tracks the last product a user clicked on
    finalClickData: {type: 'final_click'}
  });

  $.extend($.fn.bmp, {
    searchResults: function () {
      // Add behaviours for hide store links on cached pages
      $('#products .product').bmp().addHideStoreBehaviours();

      // Add behaviours for trash can links on cached pages
      $('#products .product').bmp().addTrashBehaviours();

      // Detect broken images
      $('a.thumb img').bmp().detectBrokenProductImage();

      // Track final_click events on cached pages
      $('#products .product a').click($.bmp.updateFinalClick);

      // Fade out the slider if the search input is focused since it's no longer relevant to the search
      $('#name').focus(function () {
        $('#price_point_slider').fadeOut();
      });

      // Trigger final click event on search results unload
      $(window).unload(function (e) {
        if ($.bmp.finalClickData.product_id) { // Only log if something was clicked
          $.get('/event/new', $.bmp.finalClickData);
        }
      });

      return this;
    }
  });

  $.extend($.bmp, {
    updateFinalClick: function (options) {
      if (options && options.search) { // Update called from template
        $.bmp.search = options.search;

        if ($.bmp.search.id) {
          $.bmp.finalClickData.search_id = $.bmp.search.id;
        }
        if ($.bmp.search.price_usd) {
          $.bmp.finalClickData.price_usd = $.bmp.search.price_usd.amount;
        }
      }

      var clickUrl = $.url.setUrl(this.href);

      $.bmp.finalClickData = $.extend($.bmp.finalClickData, {product_id: clickUrl.param('product_id')});
    },

    initSlider: function (price) {
      price                    = price || {};
      var startValue           = price.amount ? percentFromValue(price.amount) : 1;
      var sliderValueContainer = $('#price_point_slider_value');

      var slider = $('#price_point_slider .ui_slider').slider({
        startValue: startValue,
        slide:      function () { update(); },
        change:     function () {
          url = window.location.pathname + '?price=' + sliderValueContainer.html();
          var nameParam = $.url.param('name');
          if (nameParam) {
            url += '&name=' + nameParam;
          }

          setTimeout(function () { window.location = url; }, 0); // setTimeout required for IE6
        }
      });

      update(parseIntFromPrice(price.amount) || '?');

      function update(value) {
        var newValue = parseIntFromPrice(value) || value || round(valueFromPercent(slider.slider('value')));
        sliderValueContainer.attr('className', 'digits_' + String(newValue).length).html('$' + addCommas(newValue));
      }

      function parseIntFromPrice(value) {
        return parseInt(String(value).replace(/[^\d\.]/g, ''), 10);
      }

      function round(value) {
        strValue        = String(value);
        var length      = strValue.length;
        var secondDigit;

        if (length > 1 && value < 5000) { // Don't round to the 5's above 5000
          secondDigit = parseInt(strValue.substring(1, 2), 10);
          // Round to the nearest 5 on the second digit
          secondDigit = secondDigit < 5 ? 0 : 5;
          strValue    = strValue.substring(0, 1) + secondDigit;
        } else if (length > 1) {
          strValue = strValue.substring(0, 1);
        }

        while (strValue.length < length) {
          strValue += '0';
        }

        return parseInt(strValue, 10);
      }

      function valueFromPercent(percent) {
        // Tweaks for values at the bottom end of the slider
        // threshold => return value
        var tweaks = {
          2:  1,
          5:  5,
          8:  10,
          12: 15
        };
        var threshold;

        for (threshold in tweaks) {
          if (percent < threshold) {
            return tweaks[threshold];
          }
        }

        return parseInt(1000000 / Math.pow(101 - percent, 2.4), 10);
      }

      function percentFromValue(value) {
        var i, firstMatchIndex, firstMatchValue, thisValue, roundedValue;
        value = parseIntFromPrice(value);

        for (i = 0; i < 100; i++) {
          thisValue = valueFromPercent(i);

          if (thisValue > value) {
            roundedValue = round(thisValue);
            firstMatchIndex = firstMatchIndex || i - 1;
            firstMatchValue = firstMatchValue || roundedValue;
            if (roundedValue !== firstMatchValue) {
              return parseInt(0.5 * (firstMatchIndex + i), 10) - 1;
            }
          }
        }

        return 100;
      }

      function reverse(str) {
        return str.split('').reverse().join('');
      }

      function addCommas(value) {
        value        = reverse(String(value));
        var length   = value.length;
        var newValue = value.substring(0, 3);
        var i;
        for (i = 3; i < length; i++) {
          if (i % 3 === 0) {
            newValue += ',' + value.substring(i, i + 3);
          }
        }
        return reverse(newValue);
      }
    }
  });

  $.extend($.fn.bmp, {
    populateProducts: function (options) {
      var defaults = {
        progressBar: '#progress',
        search: {
          id:        null,
          name:      null,
          url:       null,
          price_usd: {amount: null}
        },
        products: []
      };

      options.search.price_usd = options.search.price_usd || defaults.search.price_usd;
      options                  = $.extend(defaults, options);

      // Update search variable that is used by the trash link
      $.bmp.search = options.search;

      var container = this;

      // TODO: Pass in APIs as arguments from template?
      var apis              = ['ebay', 'google', 'amazon', 'cnet', 'yahoo', 'shopping'];
      var total             = apis.length;
      var active            = 0;
      var results           = options.products;
      var done              = 0;
      var slideDuration     = 100; // ms
      var progressBar       = $(options.progressBar).show().bmp().progressBar().percent(5); // Start at 5% to give user some immediate feedback
      var sellerNodes       = {}; // Product HTML fragments keyed on the seller (for hiding all from one seller)
      var priceSlider       = $('#price_point_slider').hide(); // Hide slider to make way for the progress bar
      var priceSliderHandle = $('.ui_slider_handle').css({zIndex: 0}); // Put below progress bar until fully faded in

      $.bmp.finalClickData = $.extend($.bmp.finalClickData, {search_id: options.search.id});

      $.each(apis, fetchApi);

      function fetchApi() {
        var api      = this;
        var ajaxData = {
          api:    api,
          format: 'json',
          name:   options.search.name,
          price:  options.search.price_usd.amount || ''
        };

        $.ajax({
          type:       'GET',
          dataType:   'json',
          url:        '/product',
          data:       ajaxData,
          beforeSend: function () { active++; },
          success:    function (json) { pushProducts(json); },
          complete:   function () {
            done++;
            active--;
            if (!active) { end(results.length); }
          }
        });
      }

      function fadeInPriceSlider() {
        priceSlider.fadeIn(1000, function () {
          priceSliderHandle.css({zIndex: 1}); // Put the z-index back so the handle is draggable
        });
      }

      function update(options) {
        var maxPercent     = done * 100 / total; // Max possible for current api
        var added          = container.children(':visible').length;
        var inQueue        = $.fxqueue('product').length - 1;
        var totalProducts  = added + inQueue;
        var queuePercent   = Math.min(maxPercent, added * 100 / totalProducts);
        var currentPercent = progressBar.percent();

        if (queuePercent > currentPercent) { // Don't want the progress bar going backwards ;-)
          progressBar.percent(queuePercent, {duration: slideDuration, easing: 'linear'});
        } else if (totalProducts < 0 && maxPercent > currentPercent) { // No products added for this API
          progressBar.percent(maxPercent, {duration: 400});
        }

        if (progressBar.percent() === 100 && inQueue < totalProducts) {
          end();
        }
      }

      function end(totalResults) {
        var progressQueue;

        if (container.children().length === 0) {
          progressQueue = $.fxqueue(progressBar.queue());
          if (progressQueue.length > 0) {
            progressQueue.stop();
          }

          progressBar.percent(100, { queue: 'noProducts' });
          progressBar.fadeOut({ queue: 'noProducts' });
          $(container).append(nilNode()).hide().fadeIn();
          fadeInPriceSlider();
        } else {
          progressBar.percent(100);
          progressBar.fadeOut({queue: progressBar.queue(), duration: 1000});
          fadeInPriceSlider();
        }

        function nilNode() {
          return $.create('div', { id: 'product_nil' },
            "Unfortunately no products match your search criteria. This is pretty uncommon but you could try searching for something less specific and see if that helps."
          );
        }
      }

      function pushProducts(products) {
        try {
          if (products.length > 0) {
            $.each(products, pushProduct);
          } else {
            update();
          }
        } catch (e) {}

        function pushProduct() {
          var domEl = $('#product_' + this.id);
          var el    = domEl.length ? domEl : node(this, options.search);
          var uid   = this.uid;

          sellerNodes[this.seller] = sellerNodes[this.seller] || [];
          sellerNodes[this.seller].push(el);

          // Duplicate?
          dup = $.grep(results, function (m) { return m.uid == uid; });
          if (dup.length > 0) {
            if (rank(this) > rank(dup)) { dup.attr('id').replaceWith(el); }
            return; // Either way we are done.
          }

          // Insert sorted by price.
          results.push(this);
          var sorted = $.map(results.sort(sort), function (s) {
            return s.id;
          });
          var position = $.inArray(this.id, sorted);

          if (after = sorted[position - 1]) {
            $('#product_' + after).after(el);
          } else if (before = sorted[position + 1]) {
            $('#product_' + before).before(el);
          } else {
            container.append(el);
          }

          el.slideDown({duration: slideDuration, queue: 'product', complete: update});

          // Build product HTML from JSON.
          // TODO: I'm sure this could be much neater. Perhaps some use of Template?
          function node(product) {
            function image() {
              var imgUrl = product.image ? product.image : '/images/product.jpg';
              var img = $.create('img', {src: imgUrl, width: 75, alt: product.name}).bmp().detectBrokenProductImage();

              if (product.image_url) {
                img = $.create(
                  'a',
                  {href: product.url, className: 'thumb', rel: 'nofollow external'},
                  img
                ).newWindow().click($.bmp.updateFinalClick);
              }

              return $.create('div', {className: 'picture'}, img);
            }

            function description() {
              function name() {
                return $.create(
                  'a',
                  {href: product.url, rel: 'nofollow external'},
                  $.bmp.truncate(product.name, 60)
                ).newWindow().click($.bmp.updateFinalClick);
              }

              function coupon() {
                if (!(product.seller && product.coupon)) { return; }
                return $.create('div', {className: 'coupon'}).append(
                  product.coupon.total + ' coupons found: ',
                  $.create(
                    'a',
                    {href: 'http://www.retailmenot.com/view/' + product.coupon.site, rel: 'nofollow external'},
                    $.bmp.truncate(product.coupon.description, 60)
                ).newWindow().click($.bmp.updateFinalClick));
              }

              var node = $.create('div', {className: 'description'}).append(
                $.create('ul').append(
                  $.create('li', {className: 'name'}, name(product, $.bmp.search)),
                  $.create('li', {className: 'merchant'}).append(
                    $.create('img', {src: 'http://www.google.com/s2/favicons?domain=' + product.seller, width: '16'}),
                    ' ',
                    $.create('span', {className: 'seller_name'}).append(product.seller),
                    ' ',
                    $.create('a', {href: '#', className: 'hide_store'}).append('[Hide all from this store]')
                  ),
                  $.create('li', {className: 'coupon'}, coupon(product))
                )
              );

              return node;
            }

            function price() {
              var currencySymbol = product.price.code == 'USD' ? '$' : '';
              var currencyNode = $.create('span', {
                className: 'price ' + product.price.code.toLowerCase()
              }).append(
                $.create('span', {className: 'currency'}, product.price.code),
                ' ',
                $.create('span', {className: 'symbol'}, currencySymbol),
                $.create('span', {className: 'major'}, String(product.price.amount.split('.')[0]))
              );

              if (minor = product.price.amount.split('.')[1]) {
                currencyNode.append($.create('span', {className: 'minor'}, '.' + minor));
              }

              return $.create('div', {className: 'moolah'}, currencyNode);
            }

            function trash() {
              return $.create('a', {className: 'trash'})
                // IE hack - fixes strange bug where IE doesn't pick up the :hover pseudo class styles
                .mouseover(function () { $(this).addClass('hoverTrash'); })
                .mouseout(function () { $(this).removeClass('hoverTrash'); });
            }

            function bodyClasses() {
              var classes = ['product'];
              var diff;

              if ($.bmp.search.price_usd.amount && (product.price_usd.amount)) {
                diff = product.price_usd.amount - $.bmp.search.price_usd.amount;
              }

              if (diff === 0 && $.bmp.search.seller === product.seller) {
                classes.push('beat_this');
              } else if ((diff <= 0) || (!$.bmp.search.price_usd.amount)) { // Display as cheaper if no price set
                // Price and seller are the same
                classes.push('cheaper');
              }

              return classes.join(' ');
            }

            return $.create(
              'div',
              {className: bodyClasses(), id: 'product_' + product.id, style: 'display: none'}
            )
            .append(image())
            .append(description())
            .append(price())
            .append(trash())
            .bmp().addHideStoreBehaviours()
            .bmp().addTrashBehaviours();
          }
        }

        function rank(product) {
          // Rank based on order of APIs
          $.inArray(product.api, apis);
        }

        function sort(a, b) {
          var price = a.price_usd.amount - b.price_usd.amount;
          return price === 0 ? rank(a) - rank(b) : price;
        }
      }
    },

    addHideStoreBehaviours: function () {
      return $(this).each(function () {
        var productNode   = $(this);
        var hideStoreLink = productNode.find('a.hide_store');

        productNode.mouseover(function () {
          hideStoreLink.css({display: 'inline'});
        }).mouseout(function () {
          hideStoreLink.hide();
        });

        hideStoreLink.click(function (e) {
          var sellerName = $(productNode.find('.seller_name')[0]).html();

          e.preventDefault();

          $(productNode.parents('#product')[0]).find('.seller_name').each(function () {
            var thisNode = $(this);
            if (thisNode.html() === sellerName) {
              $(thisNode.parents('.product')[0]).slideUp();
            }
          });
        });
      });
    },

    addTrashBehaviours: function () {
      return $(this).each(function () {
        var productNode   = $(this);
        var trashLink = productNode.find('a.trash');

        productNode.mouseover(function () {
          trashLink.css({display: 'inline'});
        }).mouseout(function () {
          trashLink.hide();
        });
        trashLink.click(function (e) {
          e.preventDefault();
          productNode.slideUp(1000);
          $.get('/event/new', {
            type: 'trash',
            search_id:  $.bmp.search.id,
            product_id: productNode.attr('id').replace(/[^\d]+/, '')
          });
        });
      });
    },

    detectBrokenProductImage: function () {
      return $(this).brokenImage({replacement: '/images/product.jpg'});
    }
  });
})(jQuery);
jQuery(function ($) {
  // Behaviours for help tips on homepage
  var inputs = {
    name : 'ipod classic 120GB',
    url  : 'http://www.apple.com/ipodclassic/',
    price: '$249'
  };

  var id, selector;
  var prefix = '#full_search input#';

  for (id in inputs) {
    selector = prefix + id;
    $(selector).clearingInput({text: inputs[id]}).bmp().inputHelpTip({defaultInput: prefix + 'name'});
  }

  // Open all rel="external" links in a new window
  $('a[rel~=external]').newWindow();

  $('body.products.products_show').bmp().searchResults();

  $('#poll').fancybox({
     zoomSpeedIn : 0,
     zoomSpeedOut: 0,
     frameWidth  : 635,
     frameHeight : 405,
     overlayShow : true
  });

  // Patch PNGs for IE6
  $([
    '#logo img',
    '#poll img',
    '#introduction',
    '#full_search form button',
    '#header form#search button',
    '#related',
    '#fancy_loading div',
    '#fancy_close',
    '#fancy_title_left',
    '#fancy_title_main',
    '#fancy_title_right',
    '.ui_slider_handle'
  ].join(', ')).pngfix();

  // Pre-cache product background images (prevents FOUC on results page)
  $.cacheImage('/images/product_bg.png');
});
