/*!
 * jQuery Tools v1.2.6 - The missing UI library for the Web
 * 
 * overlay/overlay.js
 * scrollable/scrollable.js
 * scrollable/scrollable.autoscroll.js
 * scrollable/scrollable.navigator.js
 * tabs/tabs.js
 * tabs/tabs.slideshow.js
 * tooltip/tooltip.js
 * tooltip/tooltip.dynamic.js
 * tooltip/tooltip.slide.js
 * validator/validator.js
 * 
 * NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE.
 * 
 * http://flowplayer.org/tools/
 * 
 */
/**
* @license
* jQuery Tools @VERSION Overlay - Overlay base. Extend it.
*
* NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE.
*
* http://flowplayer.org/tools/overlay/
*
* Since: March 2008
* Date: @DATE
*/
(function($) {

// static constructs
$.tools = $.tools || {version: '@VERSION'};

$.tools.overlay = {

addEffect: function(name, loadFn, closeFn) {
effects[name] = [loadFn, closeFn];
},

conf: {
close: null,
closeOnClick: true,
closeOnEsc: true,
closeSpeed: 'fast',
effect: 'default',

// since 1.2. fixed positioning not supported by IE6
fixed: !$.browser.msie || $.browser.version > 6,

left: 'center',
load: false, // 1.2
mask: null,
oneInstance: true,
speed: 'normal',
target: null, // target element to be overlayed. by default taken from [rel]
top: '10%'
}
};


var instances = [], effects = {};

// the default effect. nice and easy!
$.tools.overlay.addEffect('default',

/*
onLoad/onClose functions must be called otherwise none of the
user supplied callback methods won't be called
*/
function(pos, onLoad) {

var conf = this.getConf(),
w = $(window);

if (!conf.fixed) {
pos.top += w.scrollTop();
pos.left += w.scrollLeft();
}

pos.position = conf.fixed ? 'fixed' : 'absolute';
this.getOverlay().css(pos).fadeIn(conf.speed, onLoad);

}, function(onClose) {
this.getOverlay().fadeOut(this.getConf().closeSpeed, onClose);
}
);


function Overlay(trigger, conf) {

// private variables
var self = this,
fire = trigger.add(self),
w = $(window),
closers,
overlay,
opened,
maskConf = $.tools.expose && (conf.mask || conf.expose),
uid = Math.random().toString().slice(10);


// mask configuration
if (maskConf) {
if (typeof maskConf == 'string') { maskConf = {color: maskConf}; }
maskConf.closeOnClick = maskConf.closeOnEsc = false;
}

// get overlay and trigger
var jq = conf.target || trigger.attr("rel");
overlay = jq ? $(jq) : null || trigger;

// overlay not found. cannot continue
if (!overlay.length) { throw "Could not find Overlay: " + jq; }

// trigger's click event
if (trigger && trigger.index(overlay) == -1) {
trigger.click(function(e) {
self.load(e);
return e.preventDefault();
});
}

// API methods
$.extend(self, {

load: function(e) {

// can be opened only once
if (self.isOpened()) { return self; }

// find the effect
var eff = effects[conf.effect];
if (!eff) { throw "Overlay: cannot find effect : \"" + conf.effect + "\""; }

// close other instances?
if (conf.oneInstance) {
$.each(instances, function() {
this.close(e);
});
}

// onBeforeLoad
e = e || $.Event();
e.type = "onBeforeLoad";
fire.trigger(e);
if (e.isDefaultPrevented()) { return self; }

// opened
opened = true;

// possible mask effect
if (maskConf) { $(overlay).expose(maskConf); }

// position & dimensions
var top = conf.top,
left = conf.left,
oWidth = overlay.outerWidth({margin:true}),
oHeight = overlay.outerHeight({margin:true});

if (typeof top == 'string') {
top = top == 'center' ? Math.max((w.height() - oHeight) / 2, 0) :
parseInt(top, 10) / 100 * w.height();
}

if (left == 'center') { left = Math.max((w.width() - oWidth) / 2, 0); }


// load effect
eff[0].call(self, {top: top, left: left}, function() {
if (opened) {
e.type = "onLoad";
fire.trigger(e);
}
});

// mask.click closes overlay
if (maskConf && conf.closeOnClick) {
$.mask.getMask().one("click", self.close);
}

// when window is clicked outside overlay, we close
if (conf.closeOnClick) {
$(document).bind("click." + uid, function(e) {
if (!$(e.target).parents(overlay).length) {
self.close(e);
}
});
}

// keyboard::escape
if (conf.closeOnEsc) {

// one callback is enough if multiple instances are loaded simultaneously
$(document).bind("keydown." + uid, function(e) {
if (e.keyCode == 27) {
self.close(e);
}
});
}


return self;
},

close: function(e) {

if (!self.isOpened()) { return self; }

e = e || $.Event();
e.type = "onBeforeClose";
fire.trigger(e);
if (e.isDefaultPrevented()) { return; }

opened = false;

// close effect
effects[conf.effect][1].call(self, function() {
e.type = "onClose";
fire.trigger(e);
});

// unbind the keyboard / clicking actions
$(document).unbind("click." + uid).unbind("keydown." + uid);

if (maskConf) {
$.mask.close();
}

return self;
},

getOverlay: function() {
return overlay;
},

getTrigger: function() {
return trigger;
},

getClosers: function() {
return closers;
},

isOpened: function() {
return opened;
},

// manipulate start, finish and speeds
getConf: function() {
return conf;
}

});

// callbacks
$.each("onBeforeLoad,onStart,onLoad,onBeforeClose,onClose".split(","), function(i, name) {

// configuration
if ($.isFunction(conf[name])) {
$(self).bind(name, conf[name]);
}

// API
self[name] = function(fn) {
if (fn) { $(self).bind(name, fn); }
return self;
};
});

// close button
closers = overlay.find(conf.close || ".close");

if (!closers.length && !conf.close) {
closers = $('<a class="close"></a>');
overlay.prepend(closers);
}

closers.click(function(e) {
self.close(e);
});

// autoload
if (conf.load) { self.load(); }

}

// jQuery plugin initialization
$.fn.overlay = function(conf) {

// already constructed --> return API
var el = this.data("overlay");
if (el) { return el; }

if ($.isFunction(conf)) {
conf = {onBeforeLoad: conf};
}

conf = $.extend(true, {}, $.tools.overlay.conf, conf);

this.each(function() {
el = new Overlay($(this), conf);
instances.push(el);
$(this).data("overlay", el);
});

return conf.api ? el: this;
};

})(jQuery);

/**
* @license
* jQuery Tools @VERSION Scrollable - New wave UI design
*
* NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE.
*
* http://flowplayer.org/tools/scrollable.html
*
* Since: March 2008
* Date: @DATE
*/
(function($) {

// static constructs
$.tools = $.tools || {version: '@VERSION'};

$.tools.scrollable = {

conf: {
activeClass: 'active',
circular: false,
clonedClass: 'cloned',
disabledClass: 'disabled',
easing: 'swing',
initialIndex: 0,
item: '> *',
items: '.items',
keyboard: true,
mousewheel: false,
next: '.next',
prev: '.prev',
size: 1,
speed: 400,
vertical: false,
touch: true,
wheelSpeed: 0
}
};

// get hidden element's width or height even though it's hidden
function dim(el, key) {
var v = parseInt(el.css(key), 10);
if (v) { return v; }
var s = el[0].currentStyle;
return s && s.width && parseInt(s.width, 10);
}

function find(root, query) {
var el = $(query);
return el.length < 2 ? el : root.parent().find(query);
}

var current;

// constructor
function Scrollable(root, conf) {

// current instance
var self = this,
fire = root.add(self),
itemWrap = root.children(),
index = 0,
vertical = conf.vertical;

if (!current) { current = self; }
if (itemWrap.length > 1) { itemWrap = $(conf.items, root); }


// in this version circular not supported when size > 1
if (conf.size > 1) { conf.circular = false; }

// methods
$.extend(self, {

getConf: function() {
return conf;
},

getIndex: function() {
return index;
},

getSize: function() {
return self.getItems().size();
},

getNaviButtons: function() {
return prev.add(next);
},

getRoot: function() {
return root;
},

getItemWrap: function() {
return itemWrap;
},

getItems: function() {
return itemWrap.find(conf.item).not("." + conf.clonedClass);
},

move: function(offset, time) {
return self.seekTo(index + offset, time);
},

next: function(time) {
return self.move(conf.size, time);
},

prev: function(time) {
return self.move(-conf.size, time);
},

begin: function(time) {
return self.seekTo(0, time);
},

end: function(time) {
return self.seekTo(self.getSize() -1, time);
},

focus: function() {
current = self;
return self;
},

addItem: function(item) {
item = $(item);

if (!conf.circular) {
itemWrap.append(item);
next.removeClass("disabled");

} else {
itemWrap.children().last().before(item);
itemWrap.children().first().replaceWith(item.clone().addClass(conf.clonedClass));
}

fire.trigger("onAddItem", [item]);
return self;
},


/* all seeking functions depend on this */
seekTo: function(i, time, fn) {

// ensure numeric index
if (!i.jquery) { i *= 1; }

// avoid seeking from end clone to the beginning
if (conf.circular && i === 0 && index == -1 && time !== 0) { return self; }

// check that index is sane
if (!conf.circular && i < 0 || i > self.getSize() || i < -1) { return self; }

var item = i;

if (i.jquery) {
i = self.getItems().index(i);

} else {
item = self.getItems().eq(i);
}

// onBeforeSeek
var e = $.Event("onBeforeSeek");
if (!fn) {
fire.trigger(e, [i, time]);
if (e.isDefaultPrevented() || !item.length) { return self; }
}

var props = vertical ? {top: -item.position().top} : {left: -item.position().left};

index = i;
current = self;
if (time === undefined) { time = conf.speed; }

itemWrap.animate(props, time, conf.easing, fn || function() {
fire.trigger("onSeek", [i]);
});

return self;
}

});

// callbacks
$.each(['onBeforeSeek', 'onSeek', 'onAddItem'], function(i, name) {

// configuration
if ($.isFunction(conf[name])) {
$(self).bind(name, conf[name]);
}

self[name] = function(fn) {
if (fn) { $(self).bind(name, fn); }
return self;
};
});

// circular loop
if (conf.circular) {

var cloned1 = self.getItems().slice(-1).clone().prependTo(itemWrap),
cloned2 = self.getItems().eq(1).clone().appendTo(itemWrap);

cloned1.add(cloned2).addClass(conf.clonedClass);

self.onBeforeSeek(function(e, i, time) {

if (e.isDefaultPrevented()) { return; }

/*
1. animate to the clone without event triggering
2. seek to correct position with 0 speed
*/
if (i == -1) {
self.seekTo(cloned1, time, function() {
self.end(0);
});
return e.preventDefault();

} else if (i == self.getSize()) {
self.seekTo(cloned2, time, function() {
self.begin(0);
});
}

});

// seek over the cloned item

// if the scrollable is hidden the calculations for seekTo position
// will be incorrect (eg, if the scrollable is inside an overlay).
// ensure the elements are shown, calculate the correct position,
// then re-hide the elements. This must be done synchronously to
// prevent the hidden elements being shown to the user.

// See: https://github.com/jquerytools/jquerytools/issues#issue/87

var hidden_parents = root.parents().add(root).filter(function () {
if ($(this).css('display') === 'none') {
return true;
}
});
if (hidden_parents.length) {
hidden_parents.show();
self.seekTo(0, 0, function() {});
hidden_parents.hide();
}
else {
self.seekTo(0, 0, function() {});
}

}

// next/prev buttons
var prev = find(root, conf.prev).click(function(e) { e.stopPropagation(); self.prev(); }),
next = find(root, conf.next).click(function(e) { e.stopPropagation(); self.next(); });

if (!conf.circular) {
self.onBeforeSeek(function(e, i) {
setTimeout(function() {
if (!e.isDefaultPrevented()) {
prev.toggleClass(conf.disabledClass, i <= 0);
next.toggleClass(conf.disabledClass, i >= self.getSize() -1);
}
}, 1);
});

if (!conf.initialIndex) {
prev.addClass(conf.disabledClass);
}
}

if (self.getSize() < 2) {
prev.add(next).addClass(conf.disabledClass);
}

// mousewheel support
if (conf.mousewheel && $.fn.mousewheel) {
root.mousewheel(function(e, delta) {
if (conf.mousewheel) {
self.move(delta < 0 ? 1 : -1, conf.wheelSpeed || 50);
return false;
}
});
}

// touch event
if (conf.touch) {
var touch = {};

itemWrap[0].ontouchstart = function(e) {
var t = e.touches[0];
touch.x = t.clientX;
touch.y = t.clientY;
};

itemWrap[0].ontouchmove = function(e) {

// only deal with one finger
if (e.touches.length == 1 && !itemWrap.is(":animated")) {
var t = e.touches[0],
deltaX = touch.x - t.clientX,
deltaY = touch.y - t.clientY;

self[vertical && deltaY > 0 || !vertical && deltaX > 0 ? 'next' : 'prev']();
e.preventDefault();
}
};
}

if (conf.keyboard) {

$(document).bind("keydown.scrollable", function(evt) {

// skip certain conditions
if (!conf.keyboard || evt.altKey || evt.ctrlKey || evt.metaKey || $(evt.target).is(":input")) {
return;
}

// does this instance have focus?
if (conf.keyboard != 'static' && current != self) { return; }

var key = evt.keyCode;

if (vertical && (key == 38 || key == 40)) {
self.move(key == 38 ? -1 : 1);
return evt.preventDefault();
}

if (!vertical && (key == 37 || key == 39)) {
self.move(key == 37 ? -1 : 1);
return evt.preventDefault();
}

});
}

// initial index
if (conf.initialIndex) {
self.seekTo(conf.initialIndex, 0, function() {});
}
}


// jQuery plugin implementation
$.fn.scrollable = function(conf) {

// already constructed --> return API
var el = this.data("scrollable");
if (el) { return el; }

conf = $.extend({}, $.tools.scrollable.conf, conf);

this.each(function() {
el = new Scrollable($(this), conf);
$(this).data("scrollable", el);
});

return conf.api ? el: this;

};


})(jQuery);

/**
* @license
* jQuery Tools @VERSION / Scrollable Autoscroll
*
* NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE.
*
* http://flowplayer.org/tools/scrollable/autoscroll.html
*
* Since: September 2009
* Date: @DATE
*/
(function($) {

var t = $.tools.scrollable;

t.autoscroll = {

conf: {
autoplay: true,
interval: 3000,
autopause: true
}
};

// jQuery plugin implementation
$.fn.autoscroll = function(conf) {

if (typeof conf == 'number') {
conf = {interval: conf};
}

var opts = $.extend({}, t.autoscroll.conf, conf), ret;

this.each(function() {

var api = $(this).data("scrollable"),
root = api.getRoot(),
// interval stuff
     timer, stopped = false;

/**
*
* Function to run autoscroll through event binding rather than setInterval
* Fixes this bug: http://flowplayer.org/tools/forum/25/72029
*/
      function scroll(){
        timer = setTimeout(function(){
          api.next();
        }, opts.interval);
      }

if (api) { ret = api; }

api.play = function() {

// do not start additional timer if already exists
if (timer) { return; }

stopped = false;

        root.bind('onSeek', scroll);
        scroll();
};

api.pause = function() {
timer = clearTimeout(timer); // clear any queued items immediately
        root.unbind('onSeek', scroll);
};

// resume playing if not stopped
api.resume = function() {
stopped || api.play();
};

// when stopped - mouseover won't restart
api.stop = function() {
stopped = true;
api.pause();
};

/* when mouse enters, autoscroll stops */
if (opts.autopause) {
root.add(api.getNaviButtons()).hover(api.pause, api.resume);
}

if (opts.autoplay) {
api.play();
}

});

return opts.api ? ret : this;

};

})(jQuery); 

/**
* @license
* jQuery Tools @VERSION / Scrollable Navigator
*
* NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE.
*
* http://flowplayer.org/tools/scrollable/navigator.html
*
* Since: September 2009
* Date: @DATE
*/
(function($) {

var t = $.tools.scrollable;

t.navigator = {

conf: {
navi: '.navi',
naviItem: null,
activeClass: 'active',
indexed: false,
idPrefix: null,

// 1.2
history: false
}
};

function find(root, query) {
var el = $(query);
return el.length < 2 ? el : root.parent().find(query);
}

// jQuery plugin implementation
$.fn.navigator = function(conf) {

// configuration
if (typeof conf == 'string') { conf = {navi: conf}; }
conf = $.extend({}, t.navigator.conf, conf);

var ret;

this.each(function() {

var api = $(this).data("scrollable"),
navi = conf.navi.jquery ? conf.navi : find(api.getRoot(), conf.navi),
buttons = api.getNaviButtons(),
cls = conf.activeClass,
hashed = conf.history && !!history.pushState,
size = api.getConf().size;


// @deprecated stuff
if (api) { ret = api; }

api.getNaviButtons = function() {
return buttons.add(navi);
};


if (hashed) {
history.pushState({i: 0});

$(window).bind("popstate", function(evt) {
var s = evt.originalEvent.state;
if (s) { api.seekTo(s.i); }
});
}

function doClick(el, i, e) {
api.seekTo(i);
e.preventDefault();
if (hashed) { history.pushState({i: i}); }
}

function els() {
return navi.find(conf.naviItem || '> *');
}

function addItem(i) {

var item = $("<" + (conf.naviItem || 'a') + "/>").click(function(e) {
doClick($(this), i, e);
});

// index number / id attribute
if (i === 0) { item.addClass(cls); }
if (conf.indexed) { item.text(i + 1); }
if (conf.idPrefix) { item.attr("id", conf.idPrefix + i); }

return item.appendTo(navi);
}


// generate navigator
if (els().length) {
els().each(function(i) {
$(this).click(function(e) {
doClick($(this), i, e);
});
});

} else {
$.each(api.getItems(), function(i) {
if (i % size == 0) addItem(i);
});
}

// activate correct entry
api.onBeforeSeek(function(e, index) {
setTimeout(function() {
if (!e.isDefaultPrevented()) {
var i = index / size,
el = els().eq(i);

if (el.length) { els().removeClass(cls).eq(i).addClass(cls); }
}
}, 1);
});

// new item being added
api.onAddItem(function(e, item) {
var i = api.getItems().index(item);
if (i % size == 0) addItem(i);
});

});

return conf.api ? ret : this;

};

})(jQuery); 

/**
* @license
* jQuery Tools @VERSION Tabs- The basics of UI design.
*
* NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE.
*
* http://flowplayer.org/tools/tabs/
*
* Since: November 2008
* Date: @DATE
*/
(function($) {

// static constructs
$.tools = $.tools || {version: '@VERSION'};

$.tools.tabs = {

conf: {
tabs: 'a',
current: 'current',
onBeforeClick: null,
onClick: null,
effect: 'default',
initialIndex: 0,
event: 'click',
rotate: false,

      // slide effect
      slideUpSpeed: 400,
      slideDownSpeed: 400,

// 1.2
history: false
},

addEffect: function(name, fn) {
effects[name] = fn;
}

};

var effects = {

// simple "toggle" effect
'default': function(i, done) {
this.getPanes().hide().eq(i).show();
done.call();
},

/*
configuration:
- fadeOutSpeed (positive value does "crossfading")
- fadeInSpeed
*/
fade: function(i, done) {

var conf = this.getConf(),
speed = conf.fadeOutSpeed,
panes = this.getPanes();

if (speed) {
panes.fadeOut(speed);
} else {
panes.hide();
}

panes.eq(i).fadeIn(conf.fadeInSpeed, done);
},

// for basic accordions
slide: function(i, done) {
var conf = this.getConf();

this.getPanes().slideUp(conf.slideUpSpeed);
this.getPanes().eq(i).slideDown(conf.slideDownSpeed, done);
},

/**
* AJAX effect
*/
ajax: function(i, done) {
this.getPanes().eq(0).load(this.getTabs().eq(i).attr("href"), done);
}
};

/**
* Horizontal accordion
*
* @deprecated will be replaced with a more robust implementation
*/

var
/**
* @type {Boolean}
*
* Mutex to control horizontal animation
* Disables clicking of tabs while animating
* They mess up otherwise as currentPane gets set *after* animation is done
*/
animating,
/**
* @type {Number}
*
* Initial width of tab panes
*/
w;

$.tools.tabs.addEffect("horizontal", function(i, done) {
if (animating) return; // don't allow other animations

var nextPane = this.getPanes().eq(i),
currentPane = this.getCurrentPane();

// store original width of a pane into memory
w || ( w = this.getPanes().eq(0).width() );
animating = true;

nextPane.show(); // hidden by default

// animate current pane's width to zero
    // animate next pane's width at the same time for smooth animation
    currentPane.animate({width: 0}, {
      step: function(now){
        nextPane.css("width", w-now);
      },
      complete: function(){
        $(this).hide();
        done.call();
        animating = false;
     }
    });
    // Dirty hack... onLoad, currentPant will be empty and nextPane will be the first pane
    // If this is the case, manually run callback since the animation never occured, and reset animating
    if (!currentPane.length){
      done.call();
      animating = false;
    }
});


function Tabs(root, paneSelector, conf) {

var self = this,
trigger = root.add(this),
tabs = root.find(conf.tabs),
panes = paneSelector.jquery ? paneSelector : root.children(paneSelector),
current;


// make sure tabs and panes are found
if (!tabs.length) { tabs = root.children(); }
if (!panes.length) { panes = root.parent().find(paneSelector); }
if (!panes.length) { panes = $(paneSelector); }


// public methods
$.extend(this, {
click: function(i, e) {

var tab = tabs.eq(i);

if (typeof i == 'string' && i.replace("#", "")) {
tab = tabs.filter("[href*=" + i.replace("#", "") + "]");
i = Math.max(tabs.index(tab), 0);
}

if (conf.rotate) {
var last = tabs.length -1;
if (i < 0) { return self.click(last, e); }
if (i > last) { return self.click(0, e); }
}

if (!tab.length) {
if (current >= 0) { return self; }
i = conf.initialIndex;
tab = tabs.eq(i);
}

// current tab is being clicked
if (i === current) { return self; }

// possibility to cancel click action
e = e || $.Event();
e.type = "onBeforeClick";
trigger.trigger(e, [i]);
if (e.isDefaultPrevented()) { return; }

// call the effect
effects[conf.effect].call(self, i, function() {
current = i;
// onClick callback
e.type = "onClick";
trigger.trigger(e, [i]);
});

// default behaviour
tabs.removeClass(conf.current);
tab.addClass(conf.current);

return self;
},

getConf: function() {
return conf;
},

getTabs: function() {
return tabs;
},

getPanes: function() {
return panes;
},

getCurrentPane: function() {
return panes.eq(current);
},

getCurrentTab: function() {
return tabs.eq(current);
},

getIndex: function() {
return current;
},

next: function() {
return self.click(current + 1);
},

prev: function() {
return self.click(current - 1);
},

destroy: function() {
tabs.unbind(conf.event).removeClass(conf.current);
panes.find("a[href^=#]").unbind("click.T");
return self;
}

});

// callbacks
$.each("onBeforeClick,onClick".split(","), function(i, name) {

// configuration
if ($.isFunction(conf[name])) {
$(self).bind(name, conf[name]);
}

// API
self[name] = function(fn) {
if (fn) { $(self).bind(name, fn); }
return self;
};
});


if (conf.history && $.fn.history) {
$.tools.history.init(tabs);
conf.event = 'history';
}

// setup click actions for each tab
tabs.each(function(i) {
$(this).bind(conf.event, function(e) {
self.click(i, e);
return e.preventDefault();
});
});

// cross tab anchor link
panes.find("a[href^=#]").bind("click.T", function(e) {
self.click($(this).attr("href"), e);
});

// open initial tab
if (location.hash && conf.tabs == "a" && root.find("[href=" +location.hash+ "]").length) {
self.click(location.hash);

} else {
if (conf.initialIndex === 0 || conf.initialIndex > 0) {
self.click(conf.initialIndex);
}
}

}


// jQuery plugin implementation
$.fn.tabs = function(paneSelector, conf) {

// return existing instance
var el = this.data("tabs");
if (el) {
el.destroy();
this.removeData("tabs");
}

if ($.isFunction(conf)) {
conf = {onBeforeClick: conf};
}

// setup conf
conf = $.extend({}, $.tools.tabs.conf, conf);


this.each(function() {
el = new Tabs($(this), paneSelector, conf);
$(this).data("tabs", el);
});

return conf.api ? el: this;
};

}) (jQuery); 

/**
* @license
* jQuery Tools @VERSION Slideshow - Extend it.
*
* NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE.
*
* http://flowplayer.org/tools/tabs/slideshow.html
*
* Since: September 2009
* Date: @DATE
*/
(function($) {

var tool;

tool = $.tools.tabs.slideshow = {

conf: {
next: '.forward',
prev: '.backward',
disabledClass: 'disabled',
autoplay: false,
autopause: true,
interval: 3000,
clickable: true,
api: false
}
};

function Slideshow(root, conf) {

var self = this,
fire = root.add(this),
tabs = root.data("tabs"),
timer,
stopped = true;

// next / prev buttons
function find(query) {
var el = $(query);
return el.length < 2 ? el : root.parent().find(query);
}

var nextButton = find(conf.next).click(function() {
tabs.next();
});

var prevButton = find(conf.prev).click(function() {
tabs.prev();
});

    /**
*
* Similar fix for autoscroll animation queue problem
*/
    function next(){
      timer = setTimeout(function(){
        tabs.next();
      }, conf.interval);
    }

// extend the Tabs API with slideshow methods
$.extend(self, {

// return tabs API
getTabs: function() {
return tabs;
},

getConf: function() {
return conf;
},

play: function() {

// do not start additional timer if already exists
if (timer) { return self; }

// onBeforePlay
var e = $.Event("onBeforePlay");
fire.trigger(e);
if (e.isDefaultPrevented()) { return self; }

stopped = false;

// onPlay
fire.trigger("onPlay");

fire.bind('onClick', next);
next();

return self;
},

pause: function() {

if (!timer) { return self; }

// onBeforePause
var e = $.Event("onBeforePause");
fire.trigger(e);
if (e.isDefaultPrevented()) { return self; }

timer = clearTimeout(timer);

// onPause
fire.trigger("onPause");

fire.unbind('onClick', next);

return self;
},

// resume playing if not stopped
resume: function() {
stopped || self.play();
},

// when stopped - mouseover won't restart
stop: function() {
self.pause();
stopped = true;
}

});

// callbacks
$.each("onBeforePlay,onPlay,onBeforePause,onPause".split(","), function(i, name) {

// configuration
if ($.isFunction(conf[name])) {
$(self).bind(name, conf[name]);
}

// API methods
self[name] = function(fn) {
return $(self).bind(name, fn);
};
});


/* when mouse enters, slideshow stops */
if (conf.autopause) {
tabs.getTabs().add(nextButton).add(prevButton).add(tabs.getPanes()).hover(self.pause, self.resume);
}

if (conf.autoplay) {
self.play();
}

if (conf.clickable) {
tabs.getPanes().click(function() {
tabs.next();
});
}

// manage disabling of next/prev buttons
if (!tabs.getConf().rotate) {

var disabled = conf.disabledClass;

if (!tabs.getIndex()) {
prevButton.addClass(disabled);
}

tabs.onBeforeClick(function(e, i) {
prevButton.toggleClass(disabled, !i);
nextButton.toggleClass(disabled, i == tabs.getTabs().length -1);
});
}
}

// jQuery plugin implementation
$.fn.slideshow = function(conf) {

// return existing instance
var el = this.data("slideshow");
if (el) { return el; }
 
conf = $.extend({}, tool.conf, conf);

this.each(function() {
el = new Slideshow($(this), conf);
$(this).data("slideshow", el);
});

return conf.api ? el : this;
};

})(jQuery);

/**
* @license
* jQuery Tools @VERSION Tooltip - UI essentials
*
* NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE.
*
* http://flowplayer.org/tools/tooltip/
*
* Since: November 2008
* Date: @DATE
*/
(function($) {
// static constructs
$.tools = $.tools || {version: '@VERSION'};

$.tools.tooltip = {

conf: {

// default effect variables
effect: 'toggle',
fadeOutSpeed: "fast",
predelay: 0,
delay: 30,
opacity: 1,
tip: 0,
            fadeIE: false, // enables fade effect in IE

// 'top', 'bottom', 'right', 'left', 'center'
position: ['top', 'center'],
offset: [0, 0],
relative: false,
cancelDefault: true,

// type to event mapping
events: {
def: "mouseenter,mouseleave",
input: "focus,blur",
widget: "focus mouseenter,blur mouseleave",
tooltip: "mouseenter,mouseleave"
},

// 1.2
layout: '<div/>',
tipClass: 'tooltip'
},

addEffect: function(name, loadFn, hideFn) {
effects[name] = [loadFn, hideFn];
}
};


var effects = {
toggle: [
function(done) {
var conf = this.getConf(), tip = this.getTip(), o = conf.opacity;
if (o < 1) { tip.css({opacity: o}); }
tip.show();
done.call();
},

function(done) {
this.getTip().hide();
done.call();
}
],

fade: [
function(done) {
var conf = this.getConf();
if (!$.browser.msie || conf.fadeIE) {
this.getTip().fadeTo(conf.fadeInSpeed, conf.opacity, done);
}
else {
this.getTip().show();
done();
}
},
function(done) {
var conf = this.getConf();
if (!$.browser.msie || conf.fadeIE) {
this.getTip().fadeOut(conf.fadeOutSpeed, done);
}
else {
this.getTip().hide();
done();
}
}
]
};


/* calculate tip position relative to the trigger */
function getPosition(trigger, tip, conf) {


// get origin top/left position
var top = conf.relative ? trigger.position().top : trigger.offset().top,
left = conf.relative ? trigger.position().left : trigger.offset().left,
pos = conf.position[0];

top -= tip.outerHeight() - conf.offset[0];
left += trigger.outerWidth() + conf.offset[1];

// iPad position fix
if (/iPad/i.test(navigator.userAgent)) {
top -= $(window).scrollTop();
}

// adjust Y
var height = tip.outerHeight() + trigger.outerHeight();
if (pos == 'center') { top += height / 2; }
if (pos == 'bottom') { top += height; }


// adjust X
pos = conf.position[1];
var width = tip.outerWidth() + trigger.outerWidth();
if (pos == 'center') { left -= width / 2; }
if (pos == 'left') { left -= width; }

return {top: top, left: left};
}



function Tooltip(trigger, conf) {

var self = this,
fire = trigger.add(self),
tip,
timer = 0,
pretimer = 0,
title = trigger.attr("title"),
tipAttr = trigger.attr("data-tooltip"),
effect = effects[conf.effect],
shown,

// get show/hide configuration
isInput = trigger.is(":input"),
isWidget = isInput && trigger.is(":checkbox, :radio, select, :button, :submit"),
type = trigger.attr("type"),
evt = conf.events[type] || conf.events[isInput ? (isWidget ? 'widget' : 'input') : 'def'];


// check that configuration is sane
if (!effect) { throw "Nonexistent effect \"" + conf.effect + "\""; }

evt = evt.split(/,\s*/);
if (evt.length != 2) { throw "Tooltip: bad events configuration for " + type; }


// trigger --> show
trigger.bind(evt[0], function(e) {

clearTimeout(timer);
if (conf.predelay) {
pretimer = setTimeout(function() { self.show(e); }, conf.predelay);

} else {
self.show(e);
}

// trigger --> hide
}).bind(evt[1], function(e) {
clearTimeout(pretimer);
if (conf.delay) {
timer = setTimeout(function() { self.hide(e); }, conf.delay);

} else {
self.hide(e);
}

});


// remove default title
if (title && conf.cancelDefault) {
trigger.removeAttr("title");
trigger.data("title", title);
}

$.extend(self, {

show: function(e) {

// tip not initialized yet
if (!tip) {

// data-tooltip
if (tipAttr) {
tip = $(tipAttr);

// single tip element for all
} else if (conf.tip) {
tip = $(conf.tip).eq(0);

// autogenerated tooltip
} else if (title) {
tip = $(conf.layout).addClass(conf.tipClass).appendTo(document.body)
.hide().append(title);

// manual tooltip
} else {
tip = trigger.next();
if (!tip.length) { tip = trigger.parent().next(); }
}

if (!tip.length) { throw "Cannot find tooltip for " + trigger; }
}

if (self.isShown()) { return self; }

// stop previous animation
tip.stop(true, true);

// get position
var pos = getPosition(trigger, tip, conf);

// restore title for single tooltip element
if (conf.tip) {
tip.html(trigger.data("title"));
}

// onBeforeShow
e = $.Event();
e.type = "onBeforeShow";
fire.trigger(e, [pos]);
if (e.isDefaultPrevented()) { return self; }


// onBeforeShow may have altered the configuration
pos = getPosition(trigger, tip, conf);

// set position
tip.css({position:'absolute', top: pos.top, left: pos.left});

shown = true;

// invoke effect
effect[0].call(self, function() {
e.type = "onShow";
shown = 'full';
fire.trigger(e);
});


// tooltip events
var event = conf.events.tooltip.split(/,\s*/);

if (!tip.data("__set")) {

tip.unbind(event[0]).bind(event[0], function() {
clearTimeout(timer);
clearTimeout(pretimer);
});

if (event[1] && !trigger.is("input:not(:checkbox, :radio), textarea")) {
tip.unbind(event[1]).bind(event[1], function(e) {

// being moved to the trigger element
if (e.relatedTarget != trigger[0]) {
trigger.trigger(evt[1].split(" ")[0]);
}
});
}

// bind agein for if same tip element
if (!conf.tip) tip.data("__set", true);
}

return self;
},

hide: function(e) {

if (!tip || !self.isShown()) { return self; }

// onBeforeHide
e = $.Event();
e.type = "onBeforeHide";
fire.trigger(e);
if (e.isDefaultPrevented()) { return; }

shown = false;

effects[conf.effect][1].call(self, function() {
e.type = "onHide";
fire.trigger(e);
});

return self;
},

isShown: function(fully) {
return fully ? shown == 'full' : shown;
},

getConf: function() {
return conf;
},

getTip: function() {
return tip;
},

getTrigger: function() {
return trigger;
}

});

// callbacks
$.each("onHide,onBeforeShow,onShow,onBeforeHide".split(","), function(i, name) {

// configuration
if ($.isFunction(conf[name])) {
$(self).bind(name, conf[name]);
}

// API
self[name] = function(fn) {
if (fn) { $(self).bind(name, fn); }
return self;
};
});

}


// jQuery plugin implementation
$.fn.tooltip = function(conf) {

// return existing instance
var api = this.data("tooltip");
if (api) { return api; }

conf = $.extend(true, {}, $.tools.tooltip.conf, conf);

// position can also be given as string
if (typeof conf.position == 'string') {
conf.position = conf.position.split(/,?\s/);
}

// install tooltip for each entry in jQuery object
this.each(function() {
api = new Tooltip($(this), conf);
$(this).data("tooltip", api);
});

return conf.api ? api: this;
};

}) (jQuery);

/**
* @license
* jQuery Tools @VERSION / Tooltip Dynamic Positioning
*
* NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE.
*
* http://flowplayer.org/tools/tooltip/dynamic.html
*
* Since: July 2009
* Date: @DATE
*/
(function($) {

// version number
var t = $.tools.tooltip;

t.dynamic = {
conf: {
classNames: "top right bottom left"
}
};

/*
* See if element is on the viewport. Returns an boolean array specifying which
* edges are hidden. Edges are in following order:
*
* [top, right, bottom, left]
*
* For example following return value means that top and right edges are hidden
*
* [true, true, false, false]
*
*/
function getCropping(el) {

var w = $(window);
var right = w.width() + w.scrollLeft();
var bottom = w.height() + w.scrollTop();

return [
el.offset().top <= w.scrollTop(), // top
right <= el.offset().left + el.width(), // right
bottom <= el.offset().top + el.height(), // bottom
w.scrollLeft() >= el.offset().left // left
];
}

/*
Returns true if all edges of an element are on viewport. false if not
@param crop the cropping array returned by getCropping function
*/
function isVisible(crop) {
var i = crop.length;
while (i--) {
if (crop[i]) { return false; }
}
return true;
}

// dynamic plugin
$.fn.dynamic = function(conf) {

if (typeof conf == 'number') { conf = {speed: conf}; }

conf = $.extend({}, t.dynamic.conf, conf);

var confOrigin = $.extend(true,{},conf),
cls = conf.classNames.split(/\s/),
orig;

this.each(function() {

var api = $(this).tooltip().onBeforeShow(function(e, pos) {

// get nessessary variables
var tip = this.getTip(), tipConf = this.getConf();

/*
We store the original configuration and use it to restore back to the original state.
*/
if (!orig) {
orig = [
tipConf.position[0],
tipConf.position[1],
tipConf.offset[0],
tipConf.offset[1],
$.extend({}, tipConf)
];
}

/*
display tip in it's default position and by setting visibility to hidden.
this way we can check whether it will be on the viewport
*/
$.extend(tipConf, orig[4]);
tipConf.position = [orig[0], orig[1]];
tipConf.offset = [orig[2], orig[3]];

tip.css({
visibility: 'hidden',
position: 'absolute',
top: pos.top,
left: pos.left
}).show();

var conf = $.extend(true,{},confOrigin),

// now let's see for hidden edges
crop = getCropping(tip);

// possibly alter the configuration
if (!isVisible(crop)) {

// change the position and add class
if (crop[2]) { $.extend(tipConf, conf.top); tipConf.position[0] = 'top'; tip.addClass(cls[0]); }
if (crop[3]) { $.extend(tipConf, conf.right); tipConf.position[1] = 'right'; tip.addClass(cls[1]); }
if (crop[0]) { $.extend(tipConf, conf.bottom); tipConf.position[0] = 'bottom'; tip.addClass(cls[2]); }
if (crop[1]) { $.extend(tipConf, conf.left); tipConf.position[1] = 'left'; tip.addClass(cls[3]); }

// vertical offset
if (crop[0] || crop[2]) { tipConf.offset[0] *= -1; }

// horizontal offset
if (crop[1] || crop[3]) { tipConf.offset[1] *= -1; }
}

tip.css({visibility: 'visible'}).hide();

});

// restore positioning as soon as possible
api.onBeforeShow(function() {
var c = this.getConf(), tip = this.getTip();
setTimeout(function() {
c.position = [orig[0], orig[1]];
c.offset = [orig[2], orig[3]];
}, 0);
});

// remove custom class names and restore original effect
api.onHide(function() {
var tip = this.getTip();
tip.removeClass(conf.classNames);
});

ret = api;

});

return conf.api ? ret : this;
};

}) (jQuery);

/**
* @license
* jQuery Tools @VERSION / Tooltip Slide Effect
*
* NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE.
*
* http://flowplayer.org/tools/tooltip/slide.html
*
* Since: September 2009
* Date: @DATE
*/
(function($) {

// version number
var t = $.tools.tooltip;

// extend global configuragion with effect specific defaults
$.extend(t.conf, {
direction: 'up', // down, left, right
bounce: false,
slideOffset: 10,
slideInSpeed: 200,
slideOutSpeed: 200,
slideFade: !$.browser.msie
});

// directions for slide effect
var dirs = {
up: ['-', 'top'],
down: ['+', 'top'],
left: ['-', 'left'],
right: ['+', 'left']
};

/* default effect: "slide" */
t.addEffect("slide",

// show effect
function(done) {

// variables
var conf = this.getConf(),
tip = this.getTip(),
params = conf.slideFade ? {opacity: conf.opacity} : {},
dir = dirs[conf.direction] || dirs.up;

// direction
params[dir[1]] = dir[0] +'='+ conf.slideOffset;

// perform animation
if (conf.slideFade) { tip.css({opacity:0}); }
tip.show().animate(params, conf.slideInSpeed, done);
},

// hide effect
function(done) {

// variables
var conf = this.getConf(),
offset = conf.slideOffset,
params = conf.slideFade ? {opacity: 0} : {},
dir = dirs[conf.direction] || dirs.up;

// direction
var sign = "" + dir[0];
if (conf.bounce) { sign = sign == '+' ? '-' : '+'; }
params[dir[1]] = sign +'='+ offset;

// perform animation
this.getTip().animate(params, conf.slideOutSpeed, function() {
$(this).hide();
done.call();
});
}
);

})(jQuery); 

/**
* @license
* jQuery Tools Validator @VERSION - HTML5 is here. Now use it.
*
* NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE.
*
* http://flowplayer.org/tools/form/validator/
*
* Since: Mar 2010
* Date: @DATE
*/
/*jslint evil: true */
(function($) {

$.tools = $.tools || {version: '@VERSION'};

// globals
var typeRe = /\[type=([a-z]+)\]/,
numRe = /^-?[0-9]*(\.[0-9]+)?$/,
dateInput = $.tools.dateinput,

// http://net.tutsplus.com/tutorials/other/8-regular-expressions-you-should-know/
emailRe = /^([a-z0-9_\.\-\+]+)@([\da-z\.\-]+)\.([a-z\.]{2,6})$/i,
urlRe = /^(https?:\/\/)?[\da-z\.\-]+\.[a-z\.]{2,6}[#&+_\?\/\w \.\-=]*$/i,
v;

v = $.tools.validator = {

conf: {
grouped: false, // show all error messages at once inside the container
effect: 'default', // show/hide effect for error message. only 'default' is built-in
errorClass: 'invalid', // input field class name in case of validation error

// when to check for validity?
inputEvent: null, // change, blur, keyup, null
errorInputEvent: 'keyup', // change, blur, keyup, null
formEvent: 'submit', // submit, null

lang: 'en', // default language for error messages
message: '<div/>',
messageAttr: 'data-message', // name of the attribute for overridden error message
messageClass: 'error', // error message element's class name
offset: [0, 0],
position: 'center right',
singleError: false, // validate all inputs at once
speed: 'normal' // message's fade-in speed
},


/* The Error Messages */
messages: {
"*": { en: "Please correct this value" }
},

localize: function(lang, messages) {
$.each(messages, function(key, msg) {
v.messages[key] = v.messages[key] || {};
v.messages[key][lang] = msg;
});
},

localizeFn: function(key, messages) {
v.messages[key] = v.messages[key] || {};
$.extend(v.messages[key], messages);
},

/**
* Adds a new validator
*/
fn: function(matcher, msg, fn) {

// no message supplied
if ($.isFunction(msg)) {
fn = msg;

// message(s) on second argument
} else {
if (typeof msg == 'string') { msg = {en: msg}; }
this.messages[matcher.key || matcher] = msg;
}

// check for "[type=xxx]" (not supported by jQuery)
var test = typeRe.exec(matcher);
if (test) { matcher = isType(test[1]); }

// add validator to the arsenal
fns.push([matcher, fn]);
},

/* Add new show/hide effect */
addEffect: function(name, showFn, closeFn) {
effects[name] = [showFn, closeFn];
}

};

/* calculate error message position relative to the input */
function getPosition(trigger, el, conf) {

// get origin top/left position
var top = trigger.offset().top,
left = trigger.offset().left,
pos = conf.position.split(/,?\s+/),
y = pos[0],
x = pos[1];

top -= el.outerHeight() - conf.offset[0];
left += trigger.outerWidth() + conf.offset[1];


// iPad position fix
if (/iPad/i.test(navigator.userAgent)) {
top -= $(window).scrollTop();
}

// adjust Y
var height = el.outerHeight() + trigger.outerHeight();
if (y == 'center') { top += height / 2; }
if (y == 'bottom') { top += height; }

// adjust X
var width = trigger.outerWidth();
if (x == 'center') { left -= (width + el.outerWidth()) / 2; }
if (x == 'left') { left -= width; }

return {top: top, left: left};
}



// $.is("[type=xxx]") or $.filter("[type=xxx]") not working in jQuery 1.3.2 or 1.4.2
function isType(type) {
function fn() {
return this.getAttribute("type") == type;
}
fn.key = "[type=" + type + "]";
return fn;
}


var fns = [], effects = {

'default' : [

// show errors function
function(errs) {

var conf = this.getConf();

// loop errors
$.each(errs, function(i, err) {

// add error class
var input = err.input;
input.addClass(conf.errorClass);

// get handle to the error container
var msg = input.data("msg.el");

// create it if not present
if (!msg) {
msg = $(conf.message).addClass(conf.messageClass).appendTo(document.body);
input.data("msg.el", msg);
}

// clear the container
msg.css({visibility: 'hidden'}).find("p").remove();

// populate messages
$.each(err.messages, function(i, m) {

$("<p/>").html(m).appendTo(msg);
});

// make sure the width is not full body width so it can be positioned correctly
if (msg.outerWidth() == msg.parent().width()) {
msg.add(msg.find("p")).css({display: 'inline'});
}

// insert into correct position (relative to the field)
var pos = getPosition(input, msg, conf);

msg.css({ visibility: 'visible', position: 'absolute', top: pos.top, left: pos.left })
.fadeIn(conf.speed);
});


// hide errors function
}, function(inputs) {

var conf = this.getConf();
inputs.removeClass(conf.errorClass).each(function() {
var msg = $(this).data("msg.el");
if (msg) { msg.css({visibility: 'hidden'}); }
});
}
]
};


/* sperial selectors */
$.each("email,url,number".split(","), function(i, key) {
$.expr[':'][key] = function(el) {
return el.getAttribute("type") === key;
};
});


/*
oninvalid() jQuery plugin.
Usage: $("input:eq(2)").oninvalid(function() { ... });
*/
$.fn.oninvalid = function( fn ){
return this[fn ? "bind" : "trigger"]("OI", fn);
};


/******* built-in HTML5 standard validators *********/

v.fn(":email", "Please enter a valid email address", function(el, v) {
return !v || emailRe.test(v);
});

v.fn(":url", "Please enter a valid URL", function(el, v) {
return !v || urlRe.test(v);
});

v.fn(":number", "Please enter a numeric value.", function(el, v) {
return numRe.test(v);
});

v.fn("[max]", "Please enter a value no larger than $1", function(el, v) {

// skip empty values and dateinputs
if (v === '' || dateInput && el.is(":date")) { return true; }

var max = el.attr("max");
return parseFloat(v) <= parseFloat(max) ? true : [max];
});

v.fn("[min]", "Please enter a value of at least $1", function(el, v) {

// skip empty values and dateinputs
if (v === '' || dateInput && el.is(":date")) { return true; }

var min = el.attr("min");
return parseFloat(v) >= parseFloat(min) ? true : [min];
});

v.fn("[required]", "Please complete this mandatory field.", function(el, v) {
if (el.is(":checkbox")) { return el.is(":checked"); }
return !!v;
});

v.fn("[pattern]", function(el) {
var p = new RegExp("^" + el.attr("pattern") + "$");
return p.test(el.val());
});


function Validator(inputs, form, conf) {

// private variables
var self = this,
fire = form.add(self);

// make sure there are input fields available
inputs = inputs.not(":button, :image, :reset, :submit");

    // Prevent default Firefox validation
    form.attr("novalidate", "novalidate");

// utility function
function pushMessage(to, matcher, returnValue) {

// only one message allowed
if (!conf.grouped && to.length) { return; }

// the error message
var msg;

// substitutions are returned
if (returnValue === false || $.isArray(returnValue)) {
msg = v.messages[matcher.key || matcher] || v.messages["*"];
msg = msg[conf.lang] || v.messages["*"].en;

// substitution
var matches = msg.match(/\$\d/g);

if (matches && $.isArray(returnValue)) {
$.each(matches, function(i) {
msg = msg.replace(this, returnValue[i]);
});
}

// error message is returned directly
} else {
msg = returnValue[conf.lang] || returnValue;
}

to.push(msg);
}


// API methods
$.extend(self, {

getConf: function() {
return conf;
},

getForm: function() {
return form;
},

getInputs: function() {
return inputs;
},

reflow: function() {
inputs.each(function() {
var input = $(this),
msg = input.data("msg.el");

if (msg) {
var pos = getPosition(input, msg, conf);
msg.css({ top: pos.top, left: pos.left });
}
});
return self;
},

/* @param e - for internal use only */
invalidate: function(errs, e) {

// errors are given manually: { fieldName1: 'message1', fieldName2: 'message2' }
if (!e) {
var errors = [];
$.each(errs, function(key, val) {
var input = inputs.filter("[name='" + key + "']");
if (input.length) {

// trigger HTML5 ininvalid event
input.trigger("OI", [val]);

errors.push({ input: input, messages: [val]});
}
});

errs = errors;
e = $.Event();
}

// onFail callback
e.type = "onFail";
fire.trigger(e, [errs]);

// call the effect
if (!e.isDefaultPrevented()) {
effects[conf.effect][0].call(self, errs, e);
}

return self;
},

reset: function(els) {
els = els || inputs;
els.removeClass(conf.errorClass).each(function() {
var msg = $(this).data("msg.el");
if (msg) {
msg.remove();
$(this).data("msg.el", null);
}
}).unbind(conf.errorInputEvent || '');
return self;
},

destroy: function() {
form.unbind(conf.formEvent + ".V").unbind("reset.V");
inputs.unbind(conf.inputEvent + ".V").unbind("change.V");
return self.reset();
},


//{{{ checkValidity() - flesh and bone of this tool

/* @returns boolean */
checkValidity: function(els, e) {

els = els || inputs;
els = els.not(":disabled");
if (!els.length) { return true; }

e = e || $.Event();

// onBeforeValidate
e.type = "onBeforeValidate";
fire.trigger(e, [els]);
if (e.isDefaultPrevented()) { return e.result; }

// container for errors
var errs = [];
 
// loop trough the inputs
els.not(":radio:not(:checked)").each(function() {

// field and it's error message container
var msgs = [],
el = $(this).data("messages", msgs),
event = dateInput && el.is(":date") ? "onHide.v" : conf.errorInputEvent + ".v";

// cleanup previous validation event
el.unbind(event);


// loop all validator functions
$.each(fns, function() {
var fn = this, match = fn[0];

// match found
if (el.filter(match).length) {

// execute a validator function
var returnValue = fn[1].call(self, el, el.val());


// validation failed. multiple substitutions can be returned with an array
if (returnValue !== true) {

// onBeforeFail
e.type = "onBeforeFail";
fire.trigger(e, [el, match]);
if (e.isDefaultPrevented()) { return false; }

// overridden custom message
var msg = el.attr(conf.messageAttr);
if (msg) {
msgs = [msg];
return false;
} else {
pushMessage(msgs, match, returnValue);
}
}
}
});

if (msgs.length) {

errs.push({input: el, messages: msgs});

// trigger HTML5 ininvalid event
el.trigger("OI", [msgs]);

// begin validating upon error event type (such as keyup)
if (conf.errorInputEvent) {
el.bind(event, function(e) {
self.checkValidity(el, e);
});
}
}

if (conf.singleError && errs.length) { return false; }

});


// validation done. now check that we have a proper effect at hand
var eff = effects[conf.effect];
if (!eff) { throw "Validator: cannot find effect \"" + conf.effect + "\""; }

// errors found
if (errs.length) {
self.invalidate(errs, e);
return false;

// no errors
} else {

// call the effect
eff[1].call(self, els, e);

// onSuccess callback
e.type = "onSuccess";
fire.trigger(e, [els]);

els.unbind(conf.errorInputEvent + ".v");
}

return true;
}
//}}}

});

// callbacks
$.each("onBeforeValidate,onBeforeFail,onFail,onSuccess".split(","), function(i, name) {

// configuration
if ($.isFunction(conf[name])) {
$(self).bind(name, conf[name]);
}

// API methods
self[name] = function(fn) {
if (fn) { $(self).bind(name, fn); }
return self;
};
});


// form validation
if (conf.formEvent) {
form.bind(conf.formEvent + ".V", function(e) {
if (!self.checkValidity(null, e)) {
return e.preventDefault();
}
// Reset event type and target
e.target = form;
e.type = conf.formEvent;
});
}

// form reset
form.bind("reset.V", function() {
self.reset();
});

// disable browser's default validation mechanism
if (inputs[0] && inputs[0].validity) {
inputs.each(function() {
this.oninvalid = function() {
return false;
};
});
}

// Web Forms 2.0 compatibility
if (form[0]) {
form[0].checkValidity = self.checkValidity;
}

// input validation
if (conf.inputEvent) {
inputs.bind(conf.inputEvent + ".V", function(e) {
self.checkValidity($(this), e);
});
}

// checkboxes, selects and radios are checked separately
inputs.filter(":checkbox, select").filter("[required]").bind("change.V", function(e) {
var el = $(this);
if (this.checked || (el.is("select") && $(this).val())) {
effects[conf.effect][1].call(self, el, e);
}
});

var radios = inputs.filter(":radio").change(function(e) {
self.checkValidity(radios, e);
});

// reposition tooltips when window is resized
$(window).resize(function() {
self.reflow();
});
}


// jQuery plugin initialization
$.fn.validator = function(conf) {

var instance = this.data("validator");

// destroy existing instance
if (instance) {
instance.destroy();
this.removeData("validator");
}

// configuration
conf = $.extend(true, {}, v.conf, conf);

// selector is a form
if (this.is("form")) {
return this.each(function() {
var form = $(this);
instance = new Validator(form.find(":input"), form, conf);
form.data("validator", instance);
});

} else {
instance = new Validator(this, this.eq(0).closest("form"), conf);
return this.data("validator", instance);
}

};

})(jQuery);


