[Pkg-mozext-commits] [wot] 57/226: Alpha version of FBL for Firefox / incomplete

David Prévot taffit at moszumanska.debian.org
Fri May 1 00:35:34 UTC 2015


This is an automated email from the git hooks/post-receive script.

taffit pushed a commit to branch master
in repository wot.

commit 61e03254fefe4ed7c9855c592a05f6f8ba37d565
Author: Sergey Andryukhin <sorgoz at yandex.com>
Date:   Fri Dec 21 14:18:16 2012 +0200

    Alpha version of FBL for Firefox / incomplete
---
 chrome.manifest                                  |    4 +-
 content/api.js                                   |   70 +
 content/cache.js                                 |   52 +
 content/config.js                                |   12 +-
 content/injections/ga_configure.js               |  119 +
 content/injections/ga_init.js                    |   37 +
 content/injections/jquery-ui-1.9.2.custom.js     | 2544 ++++++++++++++++++++++
 content/injections/jquery.js                     |    4 +
 content/injections/surveys.widgets.js            |  385 ++++
 content/injections/wot_proxy.js                  |  356 +++
 content/overlay.xul                              |    1 +
 content/search.js                                |    6 +-
 content/surveys.js                               |  548 +++++
 content/ui.js                                    |    6 +-
 content/util.js                                  |  193 ++
 extra/feedback/assets/close-button-secondary.png |  Bin 0 -> 3511 bytes
 extra/feedback/assets/close-button.png           |  Bin 0 -> 3533 bytes
 extra/feedback/assets/logo.png                   |  Bin 0 -> 1706 bytes
 extra/feedback/assets/slide-sep.png              |  Bin 0 -> 2949 bytes
 extra/feedback/assets/slider-handle.png          |  Bin 0 -> 4005 bytes
 extra/feedback/assets/submit-button.png          |  Bin 0 -> 11440 bytes
 extra/feedback/surveys.html                      |   69 +
 extra/feedback/surveys.widgets.css               |  390 ++++
 extra/feedback/surveys_test.html                 |    9 +
 extra/feedback/surveys_test.js                   |   34 +
 25 files changed, 4831 insertions(+), 8 deletions(-)

diff --git a/chrome.manifest b/chrome.manifest
index 4181614..6e2f945 100644
--- a/chrome.manifest
+++ b/chrome.manifest
@@ -1,5 +1,5 @@
-content	wot	content/
-content	wot	content/ contentaccessible=yes
+content	    wot    content/
+content     wot    content/ contentaccessible=yes
 resource	wot-base-dir content/
 overlay	chrome://browser/content/browser.xul	chrome://wot/content/overlay.xul
 overlay	chrome://navigator/content/navigatorOverlay.xul	chrome://wot/content/overlay.xul
diff --git a/content/api.js b/content/api.js
index b9eb339..f846582 100644
--- a/content/api.js
+++ b/content/api.js
@@ -230,6 +230,8 @@ var wot_api_query =
 					wot_cache.set(hostname, "time", Date.now());
 
 					if (request.status == 200) {
+
+						// here is the point to process /query response and extract a FBL question
 						wot_cache.add_query(
 							request.responseXML.getElementsByTagName(
 								WOT_SERVICE_XML_QUERY),
@@ -800,6 +802,74 @@ var wot_api_submit =
 	}
 };
 
+var wot_api_feedback =
+{
+	send: function(url, question, choice)
+	{
+		try {
+			if (!wot_util.isenabled() || !url || !choice || !question) {
+				dump("wot_api_feedback.send() - invalid params were given\n");
+				return;
+			}
+
+			var nonce = wot_crypto.nonce();
+
+			var context = wot_arc4.create(wot_hash.hmac_sha1hex(
+				wot_prefs.witness_key, nonce));
+
+			if (!context) {
+				dump("wot_api_feedback.send() - no context was given\n");
+				return;
+			}
+
+			var crypted = wot_arc4.crypt(context,
+				wot_hash.strtobin(url));
+
+			if (!crypted) {
+				dump("wot_api_feedback.send() - url encryption failed\n");
+				return;
+			}
+
+			var qs = WOT_SERVICE_API_FEEDBACK +
+				"?question=" + String(question) +
+				"&choice=" + String(choice) +
+				"&url=" + encodeURIComponent(btoa(wot_hash.bintostr(crypted))) +
+				"&id="		+ wot_prefs.witness_id +
+				"&nonce="	+ nonce;
+
+			qs += wot_url.getapiparams();
+
+			var request = new XMLHttpRequest();
+
+			if (!request) {
+				dump("wot_api_feedback.send() - failed to create Request object\n");
+				return;
+			}
+
+			request.open("GET", wot_core.wot_service_url() + wot_crypto.authenticate_query(qs));
+
+			new wot_cookie_remover(request);
+
+			request.onload = function(event)
+			{
+				try {
+					if (request.status == 200) {
+						dump("wot_api_feedback.onload: answer submitted successfully\n");
+					}
+				} catch (e) {
+					dump("wot_api_feedback.onload: failed with " + e + "\n");
+				}
+			};
+
+			request.send(null);
+			dump("wot_api_feedback.send() feedback was sent\n");
+
+		} catch (e) {
+			dump("wot_api_feedback.send: failed with " + e + "\n");
+		}
+	}
+};
+
 var wot_api_update =
 {
 	send: function(force)
diff --git a/content/cache.js b/content/cache.js
index 9bd9012..8168624 100644
--- a/content/cache.js
+++ b/content/cache.js
@@ -379,6 +379,9 @@ var wot_cache =
 					}
 				}
 
+				// process Feedback Question
+				this.add_question(name, child);
+
 				child = child.nextSibling;
 			}
 		} catch (e) {
@@ -386,6 +389,55 @@ var wot_cache =
 		}
 	},
 
+	add_question: function (hostname, target_node)
+	{
+		if (target_node.localName == WOT_SERVICE_XML_QUERY_QUESTION) {
+
+			try {
+				var doc = target_node.ownerDocument;
+				var id_node = doc.getElementsByTagName(WOT_SERVICE_XML_QUERY_QUESTION_ID).item(0),
+					text_node = doc.getElementsByTagName(WOT_SERVICE_XML_QUERY_QUESTION_TEXT).item(0),
+					choices_nodes = doc.getElementsByTagName(WOT_SERVICE_XML_QUERY_CHOICE_TEXT);
+
+				if (id_node && id_node.firstChild && text_node && text_node.firstChild) {
+					var id = String(id_node.firstChild.nodeValue),
+						text = String(text_node.firstChild.nodeValue);
+
+					if (id && text) {
+
+						var choice = choices_nodes.item(0),
+							choices = [];
+
+						while(choice) {
+
+							var choice_text = choice.firstChild.nodeValue;
+							var choice_value = choice.attributes.getNamedItem("value").value;
+
+							if (choice_text && choice_value) {
+								choices.push({ value: choice_value, text: choice_text });
+							}
+
+							choice = choice.nextSibling;
+						}
+
+						// now store question data to global WOT cache (if there are any choices)
+						if (choices.length) {
+							this.set(hostname, "question_id", id);
+							this.set(hostname, "question_text", text);
+							this.set(hostname, "choices_number", Number(choices.length));
+							for(var j=0; j < choices.length; j++) {
+								this.set(hostname, "choice_value_" + String(j), choices[j]['value']);
+								this.set(hostname, "choice_text_" + String(j), choices[j]['text']);
+							}
+						}
+					}
+				}
+			} catch(e) {
+				dump("Failed to extract Question data from XML\n");
+			}
+		}
+	},
+
 	add_query: function(queries, targets, islink)
 	{
 		try {
diff --git a/content/config.js b/content/config.js
index 1889594..cc4c137 100644
--- a/content/config.js
+++ b/content/config.js
@@ -70,6 +70,7 @@ const WOT_SERVICE_API_REGISTER	= WOT_SERVICE_API_VERSION + "register";
 const WOT_SERVICE_API_RELOAD	= WOT_SERVICE_API_VERSION + "reload";
 const WOT_SERVICE_API_SUBMIT	= WOT_SERVICE_API_VERSION + "submit";
 const WOT_SERVICE_API_UPDATE    = WOT_SERVICE_API_VERSION + "update";
+const WOT_SERVICE_API_FEEDBACK    = WOT_SERVICE_API_VERSION + "feedback";
 
 /* API XML tags and attributes */
 const WOT_SERVICE_XML_LINK						= "link";
@@ -85,6 +86,10 @@ const WOT_SERVICE_XML_QUERY_APPLICATION_I		= "inherited";
 const WOT_SERVICE_XML_QUERY_APPLICATION_L		= "lowered";
 const WOT_SERVICE_XML_QUERY_APPLICATION_E		= "excluded";
 const WOT_SERVICE_XML_QUERY_APPLICATION_T		= "t";
+const WOT_SERVICE_XML_QUERY_QUESTION			= "question";
+const WOT_SERVICE_XML_QUERY_QUESTION_ID			= "questionId";
+const WOT_SERVICE_XML_QUERY_QUESTION_TEXT		= "questionText";
+const WOT_SERVICE_XML_QUERY_CHOICE_TEXT 		= "choiceText";
 const WOT_SERVICE_XML_QUERY_MSG					= "message";
 const WOT_SERVICE_XML_QUERY_MSG_ID				= "id";
 const WOT_SERVICE_XML_QUERY_MSG_ID_MAINT		= "downtime";
@@ -229,7 +234,9 @@ const wot_prefs_bool = [
 	[ "warning_unknown_2",			false ],
 	[ "warning_unknown_3",			false ],
 	[ "warning_unknown_4",			false ],
-	[ "warning_unknown_5",			false ]
+	[ "warning_unknown_5",			false ],
+	[ "feedback_enabled",			true  ],
+	[ "feedback_optedout",			false ]
 ];
 
 const wot_prefs_char = [
@@ -244,7 +251,8 @@ const wot_prefs_char = [
 	[ "update_checked",				"0"	],
 	[ "warning_opacity",			""	],
 	[ "witness_id",					""	],
-	[ "witness_key",				""	]
+	[ "witness_key",				""	],
+	[ "feedback_lasttimeasked",  	""	]
 ];
 
 const wot_prefs_int = [
diff --git a/content/injections/ga_configure.js b/content/injections/ga_configure.js
new file mode 100644
index 0000000..dbb66fb
--- /dev/null
+++ b/content/injections/ga_configure.js
@@ -0,0 +1,119 @@
+/*
+ wot.js
+ Copyright © 2009 - 2012  WOT Services Oy <info at mywot.com>
+
+ This file is part of WOT.
+
+ WOT is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ WOT is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
+ License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with WOT. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+// this script must be placed right after wot.js
+// it is separated from html to conform strict content policy
+
+var _gaq = _gaq || [];
+_gaq.push(['_setAccount', wot.ga_id]);
+_gaq.push(['_trackPageview']);
+
+// provide version number to GA
+_gaq.push(['_setCustomVar', 1, 'Version', String(wot.version), 2]); // scope = 2 (session level)
+
+
+/* This adds logic for counting events to wot object */
+
+$.extend(wot, { ga: {
+
+	disable: false, // general switcher to stop counting stats
+	_tracker: null,
+
+	categories: {
+		WS: "WarningScreen",
+		RW: "RatingWindow",
+		GEN: "General",
+		INJ: "Injections",
+		WT: "WelcomeTips",
+		FBL: "FeedbackLoop"
+	},
+
+	actions: {
+		RW_TESTIMONY:   "RW_testimony",
+		RW_BTN_CLOSE:   "RW_btn_close",
+		RW_MSG_CLICKED: "RW_msg_clicked",
+
+		WS_SHOW:        "WS_shown",
+		WS_BTN_ENTER:   "WS_btn_enter",
+		WS_BTN_CLOSE:   "WS_btn_close",
+
+		D_POPUP_SHOWN:  "D_popup_shown",
+		GEN_INSTALLED:  "WOT_installed",
+		GEN_LAUNCHED:   "WOT_launched",
+
+		WT_INTRO_0_SHOWN: "WT_Intro0_shown",
+		WT_INTRO_0_OK:  "WT_Intro0_ok",
+		WT_WS_SHOWN:    "WT_WS_shown",
+		WT_WS_OK:       "WT_WS_ok",
+		WT_WS_OPTEDOUT: "WT_WS_optedout",
+		WT_RW_SHOWN:    "WT_RW_shown",
+		WT_RW_OK:       "WT_RW_ok",
+		WT_DONUTS_SHOWN:"WT_Donuts_shown",
+		WT_DONUTS_OK:   "WT_Donuts_ok",
+
+		FBL_shown:       "FBL_shown",
+		FBL_submit:      "FBL_submit",
+		FBL_closed:      "FBL_closed",
+		FBL_optout_shown:"FBL_optout_shown",
+		FBL_optout_yes:  "FBL_optout_yes",
+		FBL_optout_no:   "FBL_optout_no",
+		FBL_whatisthis:  "FBL_whatisthis",
+		FBL_bottom_close:"FBL_bottom_close",
+		FBL_slidered:    "FBL_slidered",
+		FBL_directclick: "FBL_directclick",
+		FBL_logo:        "FBL_logo",
+		FBL_opportunity: "FBL_opportunity"  // we could show the survey, but conditions are not met
+	},
+
+	init_tracker: function () {
+		if (!wot.ga._tracker) {
+			if (_gat) {
+				try {
+					wot.ga._tracker = _gat.getTrackerByName();
+					wot.ga._tracker._setAccount(wot.ga_id);
+				} catch (e) {
+					// failed. No Problem.
+				}
+			}
+		}
+
+		return !!wot.ga._tracker;
+	},
+
+	fire_event: function (category, action, label, value) {
+
+		if (wot.ga.disable) return;
+
+		try {
+			if (wot.ga.init_tracker()) {
+				wot.ga._tracker._trackEvent(category, action, label, value);
+			} else {
+				// backup option, if AsyncTracker still isn't inited
+				_gaq.push(['_trackEvent', category, action, label, value]);
+			}
+		} catch (e) {
+			// silence...
+			//console.log("Error in wot.ga.fire_event(). Msg: ", e);
+		}
+	}
+
+}});
+
diff --git a/content/injections/ga_init.js b/content/injections/ga_init.js
new file mode 100644
index 0000000..0e4d226
--- /dev/null
+++ b/content/injections/ga_init.js
@@ -0,0 +1,37 @@
+/*
+ wot.js
+ Copyright © 2009 - 2012  WOT Services Oy <info at mywot.com>
+
+ This file is part of WOT.
+
+ WOT is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ WOT is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
+ License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with WOT. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+// for analytics
+(function() {
+	if(!wot.ga.disable) {
+		try {
+			var ga = document.createElement('script');
+			ga.type = 'text/javascript';
+			ga.async = true;
+			ga.src = 'https://ssl.google-analytics.com/ga.js';
+
+			var s = document.getElementsByTagName('script')[0];
+			s.parentNode.insertBefore(ga, s);
+		} catch (e) {
+			// silence, please
+		}
+	}
+})();
diff --git a/content/injections/jquery-ui-1.9.2.custom.js b/content/injections/jquery-ui-1.9.2.custom.js
new file mode 100755
index 0000000..b4bae10
--- /dev/null
+++ b/content/injections/jquery-ui-1.9.2.custom.js
@@ -0,0 +1,2544 @@
+/*! jQuery UI - v1.9.2 - 2012-11-29
+* http://jqueryui.com
+* Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.position.js, jquery.ui.slider.js, jquery.ui.tooltip.js
+* Copyright (c) 2012 jQuery Foundation and other contributors Licensed MIT */
+
+(function( $, undefined ) {
+
+var uuid = 0,
+	runiqueId = /^ui-id-\d+$/;
+
+// prevent duplicate loading
+// this is only a problem because we proxy existing functions
+// and we don't want to double proxy them
+$.ui = $.ui || {};
+if ( $.ui.version ) {
+	return;
+}
+
+$.extend( $.ui, {
+	version: "1.9.2",
+
+	keyCode: {
+		BACKSPACE: 8,
+		COMMA: 188,
+		DELETE: 46,
+		DOWN: 40,
+		END: 35,
+		ENTER: 13,
+		ESCAPE: 27,
+		HOME: 36,
+		LEFT: 37,
+		NUMPAD_ADD: 107,
+		NUMPAD_DECIMAL: 110,
+		NUMPAD_DIVIDE: 111,
+		NUMPAD_ENTER: 108,
+		NUMPAD_MULTIPLY: 106,
+		NUMPAD_SUBTRACT: 109,
+		PAGE_DOWN: 34,
+		PAGE_UP: 33,
+		PERIOD: 190,
+		RIGHT: 39,
+		SPACE: 32,
+		TAB: 9,
+		UP: 38
+	}
+});
+
+// plugins
+$.fn.extend({
+	_focus: $.fn.focus,
+	focus: function( delay, fn ) {
+		return typeof delay === "number" ?
+			this.each(function() {
+				var elem = this;
+				setTimeout(function() {
+					$( elem ).focus();
+					if ( fn ) {
+						fn.call( elem );
+					}
+				}, delay );
+			}) :
+			this._focus.apply( this, arguments );
+	},
+
+	scrollParent: function() {
+		var scrollParent;
+		if (($.ui.ie && (/(static|relative)/).test(this.css('position'))) || (/absolute/).test(this.css('position'))) {
+			scrollParent = this.parents().filter(function() {
+				return (/(relative|absolute|fixed)/).test($.css(this,'position')) && (/(auto|scroll)/).test($.css(this,'overflow')+$.css(this,'overflow-y')+$.css(this,'overflow-x'));
+			}).eq(0);
+		} else {
+			scrollParent = this.parents().filter(function() {
+				return (/(auto|scroll)/).test($.css(this,'overflow')+$.css(this,'overflow-y')+$.css(this,'overflow-x'));
+			}).eq(0);
+		}
+
+		return (/fixed/).test(this.css('position')) || !scrollParent.length ? $(document) : scrollParent;
+	},
+
+	zIndex: function( zIndex ) {
+		if ( zIndex !== undefined ) {
+			return this.css( "zIndex", zIndex );
+		}
+
+		if ( this.length ) {
+			var elem = $( this[ 0 ] ), position, value;
+			while ( elem.length && elem[ 0 ] !== document ) {
+				// Ignore z-index if position is set to a value where z-index is ignored by the browser
+				// This makes behavior of this function consistent across browsers
+				// WebKit always returns auto if the element is positioned
+				position = elem.css( "position" );
+				if ( position === "absolute" || position === "relative" || position === "fixed" ) {
+					// IE returns 0 when zIndex is not specified
+					// other browsers return a string
+					// we ignore the case of nested elements with an explicit value of 0
+					// <div style="z-index: -10;"><div style="z-index: 0;"></div></div>
+					value = parseInt( elem.css( "zIndex" ), 10 );
+					if ( !isNaN( value ) && value !== 0 ) {
+						return value;
+					}
+				}
+				elem = elem.parent();
+			}
+		}
+
+		return 0;
+	},
+
+	uniqueId: function() {
+		return this.each(function() {
+			if ( !this.id ) {
+				this.id = "ui-id-" + (++uuid);
+			}
+		});
+	},
+
+	removeUniqueId: function() {
+		return this.each(function() {
+			if ( runiqueId.test( this.id ) ) {
+				$( this ).removeAttr( "id" );
+			}
+		});
+	}
+});
+
+// selectors
+function focusable( element, isTabIndexNotNaN ) {
+	var map, mapName, img,
+		nodeName = element.nodeName.toLowerCase();
+	if ( "area" === nodeName ) {
+		map = element.parentNode;
+		mapName = map.name;
+		if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) {
+			return false;
+		}
+		img = $( "img[usemap=#" + mapName + "]" )[0];
+		return !!img && visible( img );
+	}
+	return ( /input|select|textarea|button|object/.test( nodeName ) ?
+		!element.disabled :
+		"a" === nodeName ?
+			element.href || isTabIndexNotNaN :
+			isTabIndexNotNaN) &&
+		// the element and all of its ancestors must be visible
+		visible( element );
+}
+
+function visible( element ) {
+	return $.expr.filters.visible( element ) &&
+		!$( element ).parents().andSelf().filter(function() {
+			return $.css( this, "visibility" ) === "hidden";
+		}).length;
+}
+
+$.extend( $.expr[ ":" ], {
+	data: $.expr.createPseudo ?
+		$.expr.createPseudo(function( dataName ) {
+			return function( elem ) {
+				return !!$.data( elem, dataName );
+			};
+		}) :
+		// support: jQuery <1.8
+		function( elem, i, match ) {
+			return !!$.data( elem, match[ 3 ] );
+		},
+
+	focusable: function( element ) {
+		return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) );
+	},
+
+	tabbable: function( element ) {
+		var tabIndex = $.attr( element, "tabindex" ),
+			isTabIndexNaN = isNaN( tabIndex );
+		return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN );
+	}
+});
+
+// support
+$(function() {
+	var body = document.body,
+		div = body.appendChild( div = document.createElement( "div" ) );
+
+	// access offsetHeight before setting the style to prevent a layout bug
+	// in IE 9 which causes the element to continue to take up space even
+	// after it is removed from the DOM (#8026)
+	div.offsetHeight;
+
+	$.extend( div.style, {
+		minHeight: "100px",
+		height: "auto",
+		padding: 0,
+		borderWidth: 0
+	});
+
+	$.support.minHeight = div.offsetHeight === 100;
+	$.support.selectstart = "onselectstart" in div;
+
+	// set display to none to avoid a layout bug in IE
+	// http://dev.jquery.com/ticket/4014
+	body.removeChild( div ).style.display = "none";
+});
+
+// support: jQuery <1.8
+if ( !$( "<a>" ).outerWidth( 1 ).jquery ) {
+	$.each( [ "Width", "Height" ], function( i, name ) {
+		var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ],
+			type = name.toLowerCase(),
+			orig = {
+				innerWidth: $.fn.innerWidth,
+				innerHeight: $.fn.innerHeight,
+				outerWidth: $.fn.outerWidth,
+				outerHeight: $.fn.outerHeight
+			};
+
+		function reduce( elem, size, border, margin ) {
+			$.each( side, function() {
+				size -= parseFloat( $.css( elem, "padding" + this ) ) || 0;
+				if ( border ) {
+					size -= parseFloat( $.css( elem, "border" + this + "Width" ) ) || 0;
+				}
+				if ( margin ) {
+					size -= parseFloat( $.css( elem, "margin" + this ) ) || 0;
+				}
+			});
+			return size;
+		}
+
+		$.fn[ "inner" + name ] = function( size ) {
+			if ( size === undefined ) {
+				return orig[ "inner" + name ].call( this );
+			}
+
+			return this.each(function() {
+				$( this ).css( type, reduce( this, size ) + "px" );
+			});
+		};
+
+		$.fn[ "outer" + name] = function( size, margin ) {
+			if ( typeof size !== "number" ) {
+				return orig[ "outer" + name ].call( this, size );
+			}
+
+			return this.each(function() {
+				$( this).css( type, reduce( this, size, true, margin ) + "px" );
+			});
+		};
+	});
+}
+
+// support: jQuery 1.6.1, 1.6.2 (http://bugs.jquery.com/ticket/9413)
+if ( $( "<a>" ).data( "a-b", "a" ).removeData( "a-b" ).data( "a-b" ) ) {
+	$.fn.removeData = (function( removeData ) {
+		return function( key ) {
+			if ( arguments.length ) {
+				return removeData.call( this, $.camelCase( key ) );
+			} else {
+				return removeData.call( this );
+			}
+		};
+	})( $.fn.removeData );
+}
+
+
+
+
+
+// deprecated
+
+(function() {
+	var uaMatch = /msie ([\w.]+)/.exec( navigator.userAgent.toLowerCase() ) || [];
+	$.ui.ie = uaMatch.length ? true : false;
+	$.ui.ie6 = parseFloat( uaMatch[ 1 ], 10 ) === 6;
+})();
+
+$.fn.extend({
+	disableSelection: function() {
+		return this.bind( ( $.support.selectstart ? "selectstart" : "mousedown" ) +
+			".ui-disableSelection", function( event ) {
+				event.preventDefault();
+			});
+	},
+
+	enableSelection: function() {
+		return this.unbind( ".ui-disableSelection" );
+	}
+});
+
+$.extend( $.ui, {
+	// $.ui.plugin is deprecated.  Use the proxy pattern instead.
+	plugin: {
+		add: function( module, option, set ) {
+			var i,
+				proto = $.ui[ module ].prototype;
+			for ( i in set ) {
+				proto.plugins[ i ] = proto.plugins[ i ] || [];
+				proto.plugins[ i ].push( [ option, set[ i ] ] );
+			}
+		},
+		call: function( instance, name, args ) {
+			var i,
+				set = instance.plugins[ name ];
+			if ( !set || !instance.element[ 0 ].parentNode || instance.element[ 0 ].parentNode.nodeType === 11 ) {
+				return;
+			}
+
+			for ( i = 0; i < set.length; i++ ) {
+				if ( instance.options[ set[ i ][ 0 ] ] ) {
+					set[ i ][ 1 ].apply( instance.element, args );
+				}
+			}
+		}
+	},
+
+	contains: $.contains,
+
+	// only used by resizable
+	hasScroll: function( el, a ) {
+
+		//If overflow is hidden, the element might have extra content, but the user wants to hide it
+		if ( $( el ).css( "overflow" ) === "hidden") {
+			return false;
+		}
+
+		var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop",
+			has = false;
+
+		if ( el[ scroll ] > 0 ) {
+			return true;
+		}
+
+		// TODO: determine which cases actually cause this to happen
+		// if the element doesn't have the scroll set, see if it's possible to
+		// set the scroll
+		el[ scroll ] = 1;
+		has = ( el[ scroll ] > 0 );
+		el[ scroll ] = 0;
+		return has;
+	},
+
+	// these are odd functions, fix the API or move into individual plugins
+	isOverAxis: function( x, reference, size ) {
+		//Determines when x coordinate is over "b" element axis
+		return ( x > reference ) && ( x < ( reference + size ) );
+	},
+	isOver: function( y, x, top, left, height, width ) {
+		//Determines when x, y coordinates is over "b" element
+		return $.ui.isOverAxis( y, top, height ) && $.ui.isOverAxis( x, left, width );
+	}
+});
+
+})( jQuery );
+(function( $, undefined ) {
+
+var uuid = 0,
+	slice = Array.prototype.slice,
+	_cleanData = $.cleanData;
+$.cleanData = function( elems ) {
+	for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
+		try {
+			$( elem ).triggerHandler( "remove" );
+		// http://bugs.jquery.com/ticket/8235
+		} catch( e ) {}
+	}
+	_cleanData( elems );
+};
+
+$.widget = function( name, base, prototype ) {
+	var fullName, existingConstructor, constructor, basePrototype,
+		namespace = name.split( "." )[ 0 ];
+
+	name = name.split( "." )[ 1 ];
+	fullName = namespace + "-" + name;
+
+	if ( !prototype ) {
+		prototype = base;
+		base = $.Widget;
+	}
+
+	// create selector for plugin
+	$.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) {
+		return !!$.data( elem, fullName );
+	};
+
+	$[ namespace ] = $[ namespace ] || {};
+	existingConstructor = $[ namespace ][ name ];
+	constructor = $[ namespace ][ name ] = function( options, element ) {
+		// allow instantiation without "new" keyword
+		if ( !this._createWidget ) {
+			return new constructor( options, element );
+		}
+
+		// allow instantiation without initializing for simple inheritance
+		// must use "new" keyword (the code above always passes args)
+		if ( arguments.length ) {
+			this._createWidget( options, element );
+		}
+	};
+	// extend with the existing constructor to carry over any static properties
+	$.extend( constructor, existingConstructor, {
+		version: prototype.version,
+		// copy the object used to create the prototype in case we need to
+		// redefine the widget later
+		_proto: $.extend( {}, prototype ),
+		// track widgets that inherit from this widget in case this widget is
+		// redefined after a widget inherits from it
+		_childConstructors: []
+	});
+
+	basePrototype = new base();
+	// we need to make the options hash a property directly on the new instance
+	// otherwise we'll modify the options hash on the prototype that we're
+	// inheriting from
+	basePrototype.options = $.widget.extend( {}, basePrototype.options );
+	$.each( prototype, function( prop, value ) {
+		if ( $.isFunction( value ) ) {
+			prototype[ prop ] = (function() {
+				var _super = function() {
+						return base.prototype[ prop ].apply( this, arguments );
+					},
+					_superApply = function( args ) {
+						return base.prototype[ prop ].apply( this, args );
+					};
+				return function() {
+					var __super = this._super,
+						__superApply = this._superApply,
+						returnValue;
+
+					this._super = _super;
+					this._superApply = _superApply;
+
+					returnValue = value.apply( this, arguments );
+
+					this._super = __super;
+					this._superApply = __superApply;
+
+					return returnValue;
+				};
+			})();
+		}
+	});
+	constructor.prototype = $.widget.extend( basePrototype, {
+		// TODO: remove support for widgetEventPrefix
+		// always use the name + a colon as the prefix, e.g., draggable:start
+		// don't prefix for widgets that aren't DOM-based
+		widgetEventPrefix: existingConstructor ? basePrototype.widgetEventPrefix : name
+	}, prototype, {
+		constructor: constructor,
+		namespace: namespace,
+		widgetName: name,
+		// TODO remove widgetBaseClass, see #8155
+		widgetBaseClass: fullName,
+		widgetFullName: fullName
+	});
+
+	// If this widget is being redefined then we need to find all widgets that
+	// are inheriting from it and redefine all of them so that they inherit from
+	// the new version of this widget. We're essentially trying to replace one
+	// level in the prototype chain.
+	if ( existingConstructor ) {
+		$.each( existingConstructor._childConstructors, function( i, child ) {
+			var childPrototype = child.prototype;
+
+			// redefine the child widget using the same prototype that was
+			// originally used, but inherit from the new version of the base
+			$.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto );
+		});
+		// remove the list of existing child constructors from the old constructor
+		// so the old child constructors can be garbage collected
+		delete existingConstructor._childConstructors;
+	} else {
+		base._childConstructors.push( constructor );
+	}
+
+	$.widget.bridge( name, constructor );
+};
+
+$.widget.extend = function( target ) {
+	var input = slice.call( arguments, 1 ),
+		inputIndex = 0,
+		inputLength = input.length,
+		key,
+		value;
+	for ( ; inputIndex < inputLength; inputIndex++ ) {
+		for ( key in input[ inputIndex ] ) {
+			value = input[ inputIndex ][ key ];
+			if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {
+				// Clone objects
+				if ( $.isPlainObject( value ) ) {
+					target[ key ] = $.isPlainObject( target[ key ] ) ?
+						$.widget.extend( {}, target[ key ], value ) :
+						// Don't extend strings, arrays, etc. with objects
+						$.widget.extend( {}, value );
+				// Copy everything else by reference
+				} else {
+					target[ key ] = value;
+				}
+			}
+		}
+	}
+	return target;
+};
+
+$.widget.bridge = function( name, object ) {
+	var fullName = object.prototype.widgetFullName || name;
+	$.fn[ name ] = function( options ) {
+		var isMethodCall = typeof options === "string",
+			args = slice.call( arguments, 1 ),
+			returnValue = this;
+
+		// allow multiple hashes to be passed on init
+		options = !isMethodCall && args.length ?
+			$.widget.extend.apply( null, [ options ].concat(args) ) :
+			options;
+
+		if ( isMethodCall ) {
+			this.each(function() {
+				var methodValue,
+					instance = $.data( this, fullName );
+				if ( !instance ) {
+					return $.error( "cannot call methods on " + name + " prior to initialization; " +
+						"attempted to call method '" + options + "'" );
+				}
+				if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) {
+					return $.error( "no such method '" + options + "' for " + name + " widget instance" );
+				}
+				methodValue = instance[ options ].apply( instance, args );
+				if ( methodValue !== instance && methodValue !== undefined ) {
+					returnValue = methodValue && methodValue.jquery ?
+						returnValue.pushStack( methodValue.get() ) :
+						methodValue;
+					return false;
+				}
+			});
+		} else {
+			this.each(function() {
+				var instance = $.data( this, fullName );
+				if ( instance ) {
+					instance.option( options || {} )._init();
+				} else {
+					$.data( this, fullName, new object( options, this ) );
+				}
+			});
+		}
+
+		return returnValue;
+	};
+};
+
+$.Widget = function( /* options, element */ ) {};
+$.Widget._childConstructors = [];
+
+$.Widget.prototype = {
+	widgetName: "widget",
+	widgetEventPrefix: "",
+	defaultElement: "<div>",
+	options: {
+		disabled: false,
+
+		// callbacks
+		create: null
+	},
+	_createWidget: function( options, element ) {
+		element = $( element || this.defaultElement || this )[ 0 ];
+		this.element = $( element );
+		this.uuid = uuid++;
+		this.eventNamespace = "." + this.widgetName + this.uuid;
+		this.options = $.widget.extend( {},
+			this.options,
+			this._getCreateOptions(),
+			options );
+
+		this.bindings = $();
+		this.hoverable = $();
+		this.focusable = $();
+
+		if ( element !== this ) {
+			// 1.9 BC for #7810
+			// TODO remove dual storage
+			$.data( element, this.widgetName, this );
+			$.data( element, this.widgetFullName, this );
+			this._on( true, this.element, {
+				remove: function( event ) {
+					if ( event.target === element ) {
+						this.destroy();
+					}
+				}
+			});
+			this.document = $( element.style ?
+				// element within the document
+				element.ownerDocument :
+				// element is window or document
+				element.document || element );
+			this.window = $( this.document[0].defaultView || this.document[0].parentWindow );
+		}
+
+		this._create();
+		this._trigger( "create", null, this._getCreateEventData() );
+		this._init();
+	},
+	_getCreateOptions: $.noop,
+	_getCreateEventData: $.noop,
+	_create: $.noop,
+	_init: $.noop,
+
+	destroy: function() {
+		this._destroy();
+		// we can probably remove the unbind calls in 2.0
+		// all event bindings should go through this._on()
+		this.element
+			.unbind( this.eventNamespace )
+			// 1.9 BC for #7810
+			// TODO remove dual storage
+			.removeData( this.widgetName )
+			.removeData( this.widgetFullName )
+			// support: jquery <1.6.3
+			// http://bugs.jquery.com/ticket/9413
+			.removeData( $.camelCase( this.widgetFullName ) );
+		this.widget()
+			.unbind( this.eventNamespace )
+			.removeAttr( "aria-disabled" )
+			.removeClass(
+				this.widgetFullName + "-disabled " +
+				"ui-state-disabled" );
+
+		// clean up events and states
+		this.bindings.unbind( this.eventNamespace );
+		this.hoverable.removeClass( "ui-state-hover" );
+		this.focusable.removeClass( "ui-state-focus" );
+	},
+	_destroy: $.noop,
+
+	widget: function() {
+		return this.element;
+	},
+
+	option: function( key, value ) {
+		var options = key,
+			parts,
+			curOption,
+			i;
+
+		if ( arguments.length === 0 ) {
+			// don't return a reference to the internal hash
+			return $.widget.extend( {}, this.options );
+		}
+
+		if ( typeof key === "string" ) {
+			// handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
+			options = {};
+			parts = key.split( "." );
+			key = parts.shift();
+			if ( parts.length ) {
+				curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
+				for ( i = 0; i < parts.length - 1; i++ ) {
+					curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
+					curOption = curOption[ parts[ i ] ];
+				}
+				key = parts.pop();
+				if ( value === undefined ) {
+					return curOption[ key ] === undefined ? null : curOption[ key ];
+				}
+				curOption[ key ] = value;
+			} else {
+				if ( value === undefined ) {
+					return this.options[ key ] === undefined ? null : this.options[ key ];
+				}
+				options[ key ] = value;
+			}
+		}
+
+		this._setOptions( options );
+
+		return this;
+	},
+	_setOptions: function( options ) {
+		var key;
+
+		for ( key in options ) {
+			this._setOption( key, options[ key ] );
+		}
+
+		return this;
+	},
+	_setOption: function( key, value ) {
+		this.options[ key ] = value;
+
+		if ( key === "disabled" ) {
+			this.widget()
+				.toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value )
+				.attr( "aria-disabled", value );
+			this.hoverable.removeClass( "ui-state-hover" );
+			this.focusable.removeClass( "ui-state-focus" );
+		}
+
+		return this;
+	},
+
+	enable: function() {
+		return this._setOption( "disabled", false );
+	},
+	disable: function() {
+		return this._setOption( "disabled", true );
+	},
+
+	_on: function( suppressDisabledCheck, element, handlers ) {
+		var delegateElement,
+			instance = this;
+
+		// no suppressDisabledCheck flag, shuffle arguments
+		if ( typeof suppressDisabledCheck !== "boolean" ) {
+			handlers = element;
+			element = suppressDisabledCheck;
+			suppressDisabledCheck = false;
+		}
+
+		// no element argument, shuffle and use this.element
+		if ( !handlers ) {
+			handlers = element;
+			element = this.element;
+			delegateElement = this.widget();
+		} else {
+			// accept selectors, DOM elements
+			element = delegateElement = $( element );
+			this.bindings = this.bindings.add( element );
+		}
+
+		$.each( handlers, function( event, handler ) {
+			function handlerProxy() {
+				// allow widgets to customize the disabled handling
+				// - disabled as an array instead of boolean
+				// - disabled class as method for disabling individual parts
+				if ( !suppressDisabledCheck &&
+						( instance.options.disabled === true ||
+							$( this ).hasClass( "ui-state-disabled" ) ) ) {
+					return;
+				}
+				return ( typeof handler === "string" ? instance[ handler ] : handler )
+					.apply( instance, arguments );
+			}
+
+			// copy the guid so direct unbinding works
+			if ( typeof handler !== "string" ) {
+				handlerProxy.guid = handler.guid =
+					handler.guid || handlerProxy.guid || $.guid++;
+			}
+
+			var match = event.match( /^(\w+)\s*(.*)$/ ),
+				eventName = match[1] + instance.eventNamespace,
+				selector = match[2];
+			if ( selector ) {
+				delegateElement.delegate( selector, eventName, handlerProxy );
+			} else {
+				element.bind( eventName, handlerProxy );
+			}
+		});
+	},
+
+	_off: function( element, eventName ) {
+		eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace;
+		element.unbind( eventName ).undelegate( eventName );
+	},
+
+	_delay: function( handler, delay ) {
+		function handlerProxy() {
+			return ( typeof handler === "string" ? instance[ handler ] : handler )
+				.apply( instance, arguments );
+		}
+		var instance = this;
+		return setTimeout( handlerProxy, delay || 0 );
+	},
+
+	_hoverable: function( element ) {
+		this.hoverable = this.hoverable.add( element );
+		this._on( element, {
+			mouseenter: function( event ) {
+				$( event.currentTarget ).addClass( "ui-state-hover" );
+			},
+			mouseleave: function( event ) {
+				$( event.currentTarget ).removeClass( "ui-state-hover" );
+			}
+		});
+	},
+
+	_focusable: function( element ) {
+		this.focusable = this.focusable.add( element );
+		this._on( element, {
+			focusin: function( event ) {
+				$( event.currentTarget ).addClass( "ui-state-focus" );
+			},
+			focusout: function( event ) {
+				$( event.currentTarget ).removeClass( "ui-state-focus" );
+			}
+		});
+	},
+
+	_trigger: function( type, event, data ) {
+		var prop, orig,
+			callback = this.options[ type ];
+
+		data = data || {};
+		event = $.Event( event );
+		event.type = ( type === this.widgetEventPrefix ?
+			type :
+			this.widgetEventPrefix + type ).toLowerCase();
+		// the original event may come from any element
+		// so we need to reset the target on the new event
+		event.target = this.element[ 0 ];
+
+		// copy original event properties over to the new event
+		orig = event.originalEvent;
+		if ( orig ) {
+			for ( prop in orig ) {
+				if ( !( prop in event ) ) {
+					event[ prop ] = orig[ prop ];
+				}
+			}
+		}
+
+		this.element.trigger( event, data );
+		return !( $.isFunction( callback ) &&
+			callback.apply( this.element[0], [ event ].concat( data ) ) === false ||
+			event.isDefaultPrevented() );
+	}
+};
+
+$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
+	$.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
+		if ( typeof options === "string" ) {
+			options = { effect: options };
+		}
+		var hasOptions,
+			effectName = !options ?
+				method :
+				options === true || typeof options === "number" ?
+					defaultEffect :
+					options.effect || defaultEffect;
+		options = options || {};
+		if ( typeof options === "number" ) {
+			options = { duration: options };
+		}
+		hasOptions = !$.isEmptyObject( options );
+		options.complete = callback;
+		if ( options.delay ) {
+			element.delay( options.delay );
+		}
+		if ( hasOptions && $.effects && ( $.effects.effect[ effectName ] || $.uiBackCompat !== false && $.effects[ effectName ] ) ) {
+			element[ method ]( options );
+		} else if ( effectName !== method && element[ effectName ] ) {
+			element[ effectName ]( options.duration, options.easing, callback );
+		} else {
+			element.queue(function( next ) {
+				$( this )[ method ]();
+				if ( callback ) {
+					callback.call( element[ 0 ] );
+				}
+				next();
+			});
+		}
+	};
+});
+
+// DEPRECATED
+if ( $.uiBackCompat !== false ) {
+	$.Widget.prototype._getCreateOptions = function() {
+		return $.metadata && $.metadata.get( this.element[0] )[ this.widgetName ];
+	};
+}
+
+})( jQuery );
+(function( $, undefined ) {
+
+var mouseHandled = false;
+$( document ).mouseup( function( e ) {
+	mouseHandled = false;
+});
+
+$.widget("ui.mouse", {
+	version: "1.9.2",
+	options: {
+		cancel: 'input,textarea,button,select,option',
+		distance: 1,
+		delay: 0
+	},
+	_mouseInit: function() {
+		var that = this;
+
+		this.element
+			.bind('mousedown.'+this.widgetName, function(event) {
+				return that._mouseDown(event);
+			})
+			.bind('click.'+this.widgetName, function(event) {
+				if (true === $.data(event.target, that.widgetName + '.preventClickEvent')) {
+					$.removeData(event.target, that.widgetName + '.preventClickEvent');
+					event.stopImmediatePropagation();
+					return false;
+				}
+			});
+
+		this.started = false;
+	},
+
+	// TODO: make sure destroying one instance of mouse doesn't mess with
+	// other instances of mouse
+	_mouseDestroy: function() {
+		this.element.unbind('.'+this.widgetName);
+		if ( this._mouseMoveDelegate ) {
+			$(document)
+				.unbind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
+				.unbind('mouseup.'+this.widgetName, this._mouseUpDelegate);
+		}
+	},
+
+	_mouseDown: function(event) {
+		// don't let more than one widget handle mouseStart
+		if( mouseHandled ) { return; }
+
+		// we may have missed mouseup (out of window)
+		(this._mouseStarted && this._mouseUp(event));
+
+		this._mouseDownEvent = event;
+
+		var that = this,
+			btnIsLeft = (event.which === 1),
+			// event.target.nodeName works around a bug in IE 8 with
+			// disabled inputs (#7620)
+			elIsCancel = (typeof this.options.cancel === "string" && event.target.nodeName ? $(event.target).closest(this.options.cancel).length : false);
+		if (!btnIsLeft || elIsCancel || !this._mouseCapture(event)) {
+			return true;
+		}
+
+		this.mouseDelayMet = !this.options.delay;
+		if (!this.mouseDelayMet) {
+			this._mouseDelayTimer = setTimeout(function() {
+				that.mouseDelayMet = true;
+			}, this.options.delay);
+		}
+
+		if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
+			this._mouseStarted = (this._mouseStart(event) !== false);
+			if (!this._mouseStarted) {
+				event.preventDefault();
+				return true;
+			}
+		}
+
+		// Click event may never have fired (Gecko & Opera)
+		if (true === $.data(event.target, this.widgetName + '.preventClickEvent')) {
+			$.removeData(event.target, this.widgetName + '.preventClickEvent');
+		}
+
+		// these delegates are required to keep context
+		this._mouseMoveDelegate = function(event) {
+			return that._mouseMove(event);
+		};
+		this._mouseUpDelegate = function(event) {
+			return that._mouseUp(event);
+		};
+		$(document)
+			.bind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
+			.bind('mouseup.'+this.widgetName, this._mouseUpDelegate);
+
+		event.preventDefault();
+
+		mouseHandled = true;
+		return true;
+	},
+
+	_mouseMove: function(event) {
+		// IE mouseup check - mouseup happened when mouse was out of window
+		if ($.ui.ie && !(document.documentMode >= 9) && !event.button) {
+			return this._mouseUp(event);
+		}
+
+		if (this._mouseStarted) {
+			this._mouseDrag(event);
+			return event.preventDefault();
+		}
+
+		if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
+			this._mouseStarted =
+				(this._mouseStart(this._mouseDownEvent, event) !== false);
+			(this._mouseStarted ? this._mouseDrag(event) : this._mouseUp(event));
+		}
+
+		return !this._mouseStarted;
+	},
+
+	_mouseUp: function(event) {
+		$(document)
+			.unbind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
+			.unbind('mouseup.'+this.widgetName, this._mouseUpDelegate);
+
+		if (this._mouseStarted) {
+			this._mouseStarted = false;
+
+			if (event.target === this._mouseDownEvent.target) {
+				$.data(event.target, this.widgetName + '.preventClickEvent', true);
+			}
+
+			this._mouseStop(event);
+		}
+
+		return false;
+	},
+
+	_mouseDistanceMet: function(event) {
+		return (Math.max(
+				Math.abs(this._mouseDownEvent.pageX - event.pageX),
+				Math.abs(this._mouseDownEvent.pageY - event.pageY)
+			) >= this.options.distance
+		);
+	},
+
+	_mouseDelayMet: function(event) {
+		return this.mouseDelayMet;
+	},
+
+	// These are placeholder methods, to be overriden by extending plugin
+	_mouseStart: function(event) {},
+	_mouseDrag: function(event) {},
+	_mouseStop: function(event) {},
+	_mouseCapture: function(event) { return true; }
+});
+
+})(jQuery);
+(function( $, undefined ) {
+
+$.ui = $.ui || {};
+
+var cachedScrollbarWidth,
+	max = Math.max,
+	abs = Math.abs,
+	round = Math.round,
+	rhorizontal = /left|center|right/,
+	rvertical = /top|center|bottom/,
+	roffset = /[\+\-]\d+%?/,
+	rposition = /^\w+/,
+	rpercent = /%$/,
+	_position = $.fn.position;
+
+function getOffsets( offsets, width, height ) {
+	return [
+		parseInt( offsets[ 0 ], 10 ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ),
+		parseInt( offsets[ 1 ], 10 ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 )
+	];
+}
+function parseCss( element, property ) {
+	return parseInt( $.css( element, property ), 10 ) || 0;
+}
+
+$.position = {
+	scrollbarWidth: function() {
+		if ( cachedScrollbarWidth !== undefined ) {
+			return cachedScrollbarWidth;
+		}
+		var w1, w2,
+			div = $( "<div style='display:block;width:50px;height:50px;overflow:hidden;'><div style='height:100px;width:auto;'></div></div>" ),
+			innerDiv = div.children()[0];
+
+		$( "body" ).append( div );
+		w1 = innerDiv.offsetWidth;
+		div.css( "overflow", "scroll" );
+
+		w2 = innerDiv.offsetWidth;
+
+		if ( w1 === w2 ) {
+			w2 = div[0].clientWidth;
+		}
+
+		div.remove();
+
+		return (cachedScrollbarWidth = w1 - w2);
+	},
+	getScrollInfo: function( within ) {
+		var overflowX = within.isWindow ? "" : within.element.css( "overflow-x" ),
+			overflowY = within.isWindow ? "" : within.element.css( "overflow-y" ),
+			hasOverflowX = overflowX === "scroll" ||
+				( overflowX === "auto" && within.width < within.element[0].scrollWidth ),
+			hasOverflowY = overflowY === "scroll" ||
+				( overflowY === "auto" && within.height < within.element[0].scrollHeight );
+		return {
+			width: hasOverflowX ? $.position.scrollbarWidth() : 0,
+			height: hasOverflowY ? $.position.scrollbarWidth() : 0
+		};
+	},
+	getWithinInfo: function( element ) {
+		var withinElement = $( element || window ),
+			isWindow = $.isWindow( withinElement[0] );
+		return {
+			element: withinElement,
+			isWindow: isWindow,
+			offset: withinElement.offset() || { left: 0, top: 0 },
+			scrollLeft: withinElement.scrollLeft(),
+			scrollTop: withinElement.scrollTop(),
+			width: isWindow ? withinElement.width() : withinElement.outerWidth(),
+			height: isWindow ? withinElement.height() : withinElement.outerHeight()
+		};
+	}
+};
+
+$.fn.position = function( options ) {
+	if ( !options || !options.of ) {
+		return _position.apply( this, arguments );
+	}
+
+	// make a copy, we don't want to modify arguments
+	options = $.extend( {}, options );
+
+	var atOffset, targetWidth, targetHeight, targetOffset, basePosition,
+		target = $( options.of ),
+		within = $.position.getWithinInfo( options.within ),
+		scrollInfo = $.position.getScrollInfo( within ),
+		targetElem = target[0],
+		collision = ( options.collision || "flip" ).split( " " ),
+		offsets = {};
+
+	if ( targetElem.nodeType === 9 ) {
+		targetWidth = target.width();
+		targetHeight = target.height();
+		targetOffset = { top: 0, left: 0 };
+	} else if ( $.isWindow( targetElem ) ) {
+		targetWidth = target.width();
+		targetHeight = target.height();
+		targetOffset = { top: target.scrollTop(), left: target.scrollLeft() };
+	} else if ( targetElem.preventDefault ) {
+		// force left top to allow flipping
+		options.at = "left top";
+		targetWidth = targetHeight = 0;
+		targetOffset = { top: targetElem.pageY, left: targetElem.pageX };
+	} else {
+		targetWidth = target.outerWidth();
+		targetHeight = target.outerHeight();
+		targetOffset = target.offset();
+	}
+	// clone to reuse original targetOffset later
+	basePosition = $.extend( {}, targetOffset );
+
+	// force my and at to have valid horizontal and vertical positions
+	// if a value is missing or invalid, it will be converted to center
+	$.each( [ "my", "at" ], function() {
+		var pos = ( options[ this ] || "" ).split( " " ),
+			horizontalOffset,
+			verticalOffset;
+
+		if ( pos.length === 1) {
+			pos = rhorizontal.test( pos[ 0 ] ) ?
+				pos.concat( [ "center" ] ) :
+				rvertical.test( pos[ 0 ] ) ?
+					[ "center" ].concat( pos ) :
+					[ "center", "center" ];
+		}
+		pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center";
+		pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center";
+
+		// calculate offsets
+		horizontalOffset = roffset.exec( pos[ 0 ] );
+		verticalOffset = roffset.exec( pos[ 1 ] );
+		offsets[ this ] = [
+			horizontalOffset ? horizontalOffset[ 0 ] : 0,
+			verticalOffset ? verticalOffset[ 0 ] : 0
+		];
+
+		// reduce to just the positions without the offsets
+		options[ this ] = [
+			rposition.exec( pos[ 0 ] )[ 0 ],
+			rposition.exec( pos[ 1 ] )[ 0 ]
+		];
+	});
+
+	// normalize collision option
+	if ( collision.length === 1 ) {
+		collision[ 1 ] = collision[ 0 ];
+	}
+
+	if ( options.at[ 0 ] === "right" ) {
+		basePosition.left += targetWidth;
+	} else if ( options.at[ 0 ] === "center" ) {
+		basePosition.left += targetWidth / 2;
+	}
+
+	if ( options.at[ 1 ] === "bottom" ) {
+		basePosition.top += targetHeight;
+	} else if ( options.at[ 1 ] === "center" ) {
+		basePosition.top += targetHeight / 2;
+	}
+
+	atOffset = getOffsets( offsets.at, targetWidth, targetHeight );
+	basePosition.left += atOffset[ 0 ];
+	basePosition.top += atOffset[ 1 ];
+
+	return this.each(function() {
+		var collisionPosition, using,
+			elem = $( this ),
+			elemWidth = elem.outerWidth(),
+			elemHeight = elem.outerHeight(),
+			marginLeft = parseCss( this, "marginLeft" ),
+			marginTop = parseCss( this, "marginTop" ),
+			collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) + scrollInfo.width,
+			collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) + scrollInfo.height,
+			position = $.extend( {}, basePosition ),
+			myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() );
+
+		if ( options.my[ 0 ] === "right" ) {
+			position.left -= elemWidth;
+		} else if ( options.my[ 0 ] === "center" ) {
+			position.left -= elemWidth / 2;
+		}
+
+		if ( options.my[ 1 ] === "bottom" ) {
+			position.top -= elemHeight;
+		} else if ( options.my[ 1 ] === "center" ) {
+			position.top -= elemHeight / 2;
+		}
+
+		position.left += myOffset[ 0 ];
+		position.top += myOffset[ 1 ];
+
+		// if the browser doesn't support fractions, then round for consistent results
+		if ( !$.support.offsetFractions ) {
+			position.left = round( position.left );
+			position.top = round( position.top );
+		}
+
+		collisionPosition = {
+			marginLeft: marginLeft,
+			marginTop: marginTop
+		};
+
+		$.each( [ "left", "top" ], function( i, dir ) {
+			if ( $.ui.position[ collision[ i ] ] ) {
+				$.ui.position[ collision[ i ] ][ dir ]( position, {
+					targetWidth: targetWidth,
+					targetHeight: targetHeight,
+					elemWidth: elemWidth,
+					elemHeight: elemHeight,
+					collisionPosition: collisionPosition,
+					collisionWidth: collisionWidth,
+					collisionHeight: collisionHeight,
+					offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ],
+					my: options.my,
+					at: options.at,
+					within: within,
+					elem : elem
+				});
+			}
+		});
+
+		if ( $.fn.bgiframe ) {
+			elem.bgiframe();
+		}
+
+		if ( options.using ) {
+			// adds feedback as second argument to using callback, if present
+			using = function( props ) {
+				var left = targetOffset.left - position.left,
+					right = left + targetWidth - elemWidth,
+					top = targetOffset.top - position.top,
+					bottom = top + targetHeight - elemHeight,
+					feedback = {
+						target: {
+							element: target,
+							left: targetOffset.left,
+							top: targetOffset.top,
+							width: targetWidth,
+							height: targetHeight
+						},
+						element: {
+							element: elem,
+							left: position.left,
+							top: position.top,
+							width: elemWidth,
+							height: elemHeight
+						},
+						horizontal: right < 0 ? "left" : left > 0 ? "right" : "center",
+						vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle"
+					};
+				if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) {
+					feedback.horizontal = "center";
+				}
+				if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) {
+					feedback.vertical = "middle";
+				}
+				if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) {
+					feedback.important = "horizontal";
+				} else {
+					feedback.important = "vertical";
+				}
+				options.using.call( this, props, feedback );
+			};
+		}
+
+		elem.offset( $.extend( position, { using: using } ) );
+	});
+};
+
+$.ui.position = {
+	fit: {
+		left: function( position, data ) {
+			var within = data.within,
+				withinOffset = within.isWindow ? within.scrollLeft : within.offset.left,
+				outerWidth = within.width,
+				collisionPosLeft = position.left - data.collisionPosition.marginLeft,
+				overLeft = withinOffset - collisionPosLeft,
+				overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset,
+				newOverRight;
+
+			// element is wider than within
+			if ( data.collisionWidth > outerWidth ) {
+				// element is initially over the left side of within
+				if ( overLeft > 0 && overRight <= 0 ) {
+					newOverRight = position.left + overLeft + data.collisionWidth - outerWidth - withinOffset;
+					position.left += overLeft - newOverRight;
+				// element is initially over right side of within
+				} else if ( overRight > 0 && overLeft <= 0 ) {
+					position.left = withinOffset;
+				// element is initially over both left and right sides of within
+				} else {
+					if ( overLeft > overRight ) {
+						position.left = withinOffset + outerWidth - data.collisionWidth;
+					} else {
+						position.left = withinOffset;
+					}
+				}
+			// too far left -> align with left edge
+			} else if ( overLeft > 0 ) {
+				position.left += overLeft;
+			// too far right -> align with right edge
+			} else if ( overRight > 0 ) {
+				position.left -= overRight;
+			// adjust based on position and margin
+			} else {
+				position.left = max( position.left - collisionPosLeft, position.left );
+			}
+		},
+		top: function( position, data ) {
+			var within = data.within,
+				withinOffset = within.isWindow ? within.scrollTop : within.offset.top,
+				outerHeight = data.within.height,
+				collisionPosTop = position.top - data.collisionPosition.marginTop,
+				overTop = withinOffset - collisionPosTop,
+				overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset,
+				newOverBottom;
+
+			// element is taller than within
+			if ( data.collisionHeight > outerHeight ) {
+				// element is initially over the top of within
+				if ( overTop > 0 && overBottom <= 0 ) {
+					newOverBottom = position.top + overTop + data.collisionHeight - outerHeight - withinOffset;
+					position.top += overTop - newOverBottom;
+				// element is initially over bottom of within
+				} else if ( overBottom > 0 && overTop <= 0 ) {
+					position.top = withinOffset;
+				// element is initially over both top and bottom of within
+				} else {
+					if ( overTop > overBottom ) {
+						position.top = withinOffset + outerHeight - data.collisionHeight;
+					} else {
+						position.top = withinOffset;
+					}
+				}
+			// too far up -> align with top
+			} else if ( overTop > 0 ) {
+				position.top += overTop;
+			// too far down -> align with bottom edge
+			} else if ( overBottom > 0 ) {
+				position.top -= overBottom;
+			// adjust based on position and margin
+			} else {
+				position.top = max( position.top - collisionPosTop, position.top );
+			}
+		}
+	},
+	flip: {
+		left: function( position, data ) {
+			var within = data.within,
+				withinOffset = within.offset.left + within.scrollLeft,
+				outerWidth = within.width,
+				offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left,
+				collisionPosLeft = position.left - data.collisionPosition.marginLeft,
+				overLeft = collisionPosLeft - offsetLeft,
+				overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft,
+				myOffset = data.my[ 0 ] === "left" ?
+					-data.elemWidth :
+					data.my[ 0 ] === "right" ?
+						data.elemWidth :
+						0,
+				atOffset = data.at[ 0 ] === "left" ?
+					data.targetWidth :
+					data.at[ 0 ] === "right" ?
+						-data.targetWidth :
+						0,
+				offset = -2 * data.offset[ 0 ],
+				newOverRight,
+				newOverLeft;
+
+			if ( overLeft < 0 ) {
+				newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth - outerWidth - withinOffset;
+				if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) {
+					position.left += myOffset + atOffset + offset;
+				}
+			}
+			else if ( overRight > 0 ) {
+				newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset + atOffset + offset - offsetLeft;
+				if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) {
+					position.left += myOffset + atOffset + offset;
+				}
+			}
+		},
+		top: function( position, data ) {
+			var within = data.within,
+				withinOffset = within.offset.top + within.scrollTop,
+				outerHeight = within.height,
+				offsetTop = within.isWindow ? within.scrollTop : within.offset.top,
+				collisionPosTop = position.top - data.collisionPosition.marginTop,
+				overTop = collisionPosTop - offsetTop,
+				overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop,
+				top = data.my[ 1 ] === "top",
+				myOffset = top ?
+					-data.elemHeight :
+					data.my[ 1 ] === "bottom" ?
+						data.elemHeight :
+						0,
+				atOffset = data.at[ 1 ] === "top" ?
+					data.targetHeight :
+					data.at[ 1 ] === "bottom" ?
+						-data.targetHeight :
+						0,
+				offset = -2 * data.offset[ 1 ],
+				newOverTop,
+				newOverBottom;
+			if ( overTop < 0 ) {
+				newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight - outerHeight - withinOffset;
+				if ( ( position.top + myOffset + atOffset + offset) > overTop && ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) ) {
+					position.top += myOffset + atOffset + offset;
+				}
+			}
+			else if ( overBottom > 0 ) {
+				newOverTop = position.top -  data.collisionPosition.marginTop + myOffset + atOffset + offset - offsetTop;
+				if ( ( position.top + myOffset + atOffset + offset) > overBottom && ( newOverTop > 0 || abs( newOverTop ) < overBottom ) ) {
+					position.top += myOffset + atOffset + offset;
+				}
+			}
+		}
+	},
+	flipfit: {
+		left: function() {
+			$.ui.position.flip.left.apply( this, arguments );
+			$.ui.position.fit.left.apply( this, arguments );
+		},
+		top: function() {
+			$.ui.position.flip.top.apply( this, arguments );
+			$.ui.position.fit.top.apply( this, arguments );
+		}
+	}
+};
+
+// fraction support test
+(function () {
+	var testElement, testElementParent, testElementStyle, offsetLeft, i,
+		body = document.getElementsByTagName( "body" )[ 0 ],
+		div = document.createElement( "div" );
+
+	//Create a "fake body" for testing based on method used in jQuery.support
+	testElement = document.createElement( body ? "div" : "body" );
+	testElementStyle = {
+		visibility: "hidden",
+		width: 0,
+		height: 0,
+		border: 0,
+		margin: 0,
+		background: "none"
+	};
+	if ( body ) {
+		$.extend( testElementStyle, {
+			position: "absolute",
+			left: "-1000px",
+			top: "-1000px"
+		});
+	}
+	for ( i in testElementStyle ) {
+		testElement.style[ i ] = testElementStyle[ i ];
+	}
+	testElement.appendChild( div );
+	testElementParent = body || document.documentElement;
+	testElementParent.insertBefore( testElement, testElementParent.firstChild );
+
+	div.style.cssText = "position: absolute; left: 10.7432222px;";
+
+	offsetLeft = $( div ).offset().left;
+	$.support.offsetFractions = offsetLeft > 10 && offsetLeft < 11;
+
+	testElement.innerHTML = "";
+	testElementParent.removeChild( testElement );
+})();
+
+// DEPRECATED
+if ( $.uiBackCompat !== false ) {
+	// offset option
+	(function( $ ) {
+		var _position = $.fn.position;
+		$.fn.position = function( options ) {
+			if ( !options || !options.offset ) {
+				return _position.call( this, options );
+			}
+			var offset = options.offset.split( " " ),
+				at = options.at.split( " " );
+			if ( offset.length === 1 ) {
+				offset[ 1 ] = offset[ 0 ];
+			}
+			if ( /^\d/.test( offset[ 0 ] ) ) {
+				offset[ 0 ] = "+" + offset[ 0 ];
+			}
+			if ( /^\d/.test( offset[ 1 ] ) ) {
+				offset[ 1 ] = "+" + offset[ 1 ];
+			}
+			if ( at.length === 1 ) {
+				if ( /left|center|right/.test( at[ 0 ] ) ) {
+					at[ 1 ] = "center";
+				} else {
+					at[ 1 ] = at[ 0 ];
+					at[ 0 ] = "center";
+				}
+			}
+			return _position.call( this, $.extend( options, {
+				at: at[ 0 ] + offset[ 0 ] + " " + at[ 1 ] + offset[ 1 ],
+				offset: undefined
+			} ) );
+		};
+	}( jQuery ) );
+}
+
+}( jQuery ) );
+(function( $, undefined ) {
+
+// number of pages in a slider
+// (how many times can you page up/down to go through the whole range)
+var numPages = 5;
+
+$.widget( "ui.slider", $.ui.mouse, {
+	version: "1.9.2",
+	widgetEventPrefix: "slide",
+
+	options: {
+		animate: false,
+		distance: 0,
+		max: 100,
+		min: 0,
+		orientation: "horizontal",
+		range: false,
+		step: 1,
+		value: 0,
+		values: null
+	},
+
+	_create: function() {
+		var i, handleCount,
+			o = this.options,
+			existingHandles = this.element.find( ".ui-slider-handle" ).addClass( "ui-state-default ui-corner-all" ),
+			handle = "<a class='ui-slider-handle ui-state-default ui-corner-all' href='#'></a>",
+			handles = [];
+
+		this._keySliding = false;
+		this._mouseSliding = false;
+		this._animateOff = true;
+		this._handleIndex = null;
+		this._detectOrientation();
+		this._mouseInit();
+
+		this.element
+			.addClass( "ui-slider" +
+				" ui-slider-" + this.orientation +
+				" ui-widget" +
+				" ui-widget-content" +
+				" ui-corner-all" +
+				( o.disabled ? " ui-slider-disabled ui-disabled" : "" ) );
+
+		this.range = $([]);
+
+		if ( o.range ) {
+			if ( o.range === true ) {
+				if ( !o.values ) {
+					o.values = [ this._valueMin(), this._valueMin() ];
+				}
+				if ( o.values.length && o.values.length !== 2 ) {
+					o.values = [ o.values[0], o.values[0] ];
+				}
+			}
+
+			this.range = $( "<div></div>" )
+				.appendTo( this.element )
+				.addClass( "ui-slider-range" +
+				// note: this isn't the most fittingly semantic framework class for this element,
+				// but worked best visually with a variety of themes
+				" ui-widget-header" +
+				( ( o.range === "min" || o.range === "max" ) ? " ui-slider-range-" + o.range : "" ) );
+		}
+
+		handleCount = ( o.values && o.values.length ) || 1;
+
+		for ( i = existingHandles.length; i < handleCount; i++ ) {
+			handles.push( handle );
+		}
+
+		this.handles = existingHandles.add( $( handles.join( "" ) ).appendTo( this.element ) );
+
+		this.handle = this.handles.eq( 0 );
+
+		this.handles.add( this.range ).filter( "a" )
+			.click(function( event ) {
+				event.preventDefault();
+			})
+			.mouseenter(function() {
+				if ( !o.disabled ) {
+					$( this ).addClass( "ui-state-hover" );
+				}
+			})
+			.mouseleave(function() {
+				$( this ).removeClass( "ui-state-hover" );
+			})
+			.focus(function() {
+				if ( !o.disabled ) {
+					$( ".ui-slider .ui-state-focus" ).removeClass( "ui-state-focus" );
+					$( this ).addClass( "ui-state-focus" );
+				} else {
+					$( this ).blur();
+				}
+			})
+			.blur(function() {
+				$( this ).removeClass( "ui-state-focus" );
+			});
+
+		this.handles.each(function( i ) {
+			$( this ).data( "ui-slider-handle-index", i );
+		});
+
+		this._on( this.handles, {
+			keydown: function( event ) {
+				var allowed, curVal, newVal, step,
+					index = $( event.target ).data( "ui-slider-handle-index" );
+
+				switch ( event.keyCode ) {
+					case $.ui.keyCode.HOME:
+					case $.ui.keyCode.END:
+					case $.ui.keyCode.PAGE_UP:
+					case $.ui.keyCode.PAGE_DOWN:
+					case $.ui.keyCode.UP:
+					case $.ui.keyCode.RIGHT:
+					case $.ui.keyCode.DOWN:
+					case $.ui.keyCode.LEFT:
+						event.preventDefault();
+						if ( !this._keySliding ) {
+							this._keySliding = true;
+							$( event.target ).addClass( "ui-state-active" );
+							allowed = this._start( event, index );
+							if ( allowed === false ) {
+								return;
+							}
+						}
+						break;
+				}
+
+				step = this.options.step;
+				if ( this.options.values && this.options.values.length ) {
+					curVal = newVal = this.values( index );
+				} else {
+					curVal = newVal = this.value();
+				}
+
+				switch ( event.keyCode ) {
+					case $.ui.keyCode.HOME:
+						newVal = this._valueMin();
+						break;
+					case $.ui.keyCode.END:
+						newVal = this._valueMax();
+						break;
+					case $.ui.keyCode.PAGE_UP:
+						newVal = this._trimAlignValue( curVal + ( (this._valueMax() - this._valueMin()) / numPages ) );
+						break;
+					case $.ui.keyCode.PAGE_DOWN:
+						newVal = this._trimAlignValue( curVal - ( (this._valueMax() - this._valueMin()) / numPages ) );
+						break;
+					case $.ui.keyCode.UP:
+					case $.ui.keyCode.RIGHT:
+						if ( curVal === this._valueMax() ) {
+							return;
+						}
+						newVal = this._trimAlignValue( curVal + step );
+						break;
+					case $.ui.keyCode.DOWN:
+					case $.ui.keyCode.LEFT:
+						if ( curVal === this._valueMin() ) {
+							return;
+						}
+						newVal = this._trimAlignValue( curVal - step );
+						break;
+				}
+
+				this._slide( event, index, newVal );
+			},
+			keyup: function( event ) {
+				var index = $( event.target ).data( "ui-slider-handle-index" );
+
+				if ( this._keySliding ) {
+					this._keySliding = false;
+					this._stop( event, index );
+					this._change( event, index );
+					$( event.target ).removeClass( "ui-state-active" );
+				}
+			}
+		});
+
+		this._refreshValue();
+
+		this._animateOff = false;
+	},
+
+	_destroy: function() {
+		this.handles.remove();
+		this.range.remove();
+
+		this.element
+			.removeClass( "ui-slider" +
+				" ui-slider-horizontal" +
+				" ui-slider-vertical" +
+				" ui-slider-disabled" +
+				" ui-widget" +
+				" ui-widget-content" +
+				" ui-corner-all" );
+
+		this._mouseDestroy();
+	},
+
+	_mouseCapture: function( event ) {
+		var position, normValue, distance, closestHandle, index, allowed, offset, mouseOverHandle,
+			that = this,
+			o = this.options;
+
+		if ( o.disabled ) {
+			return false;
+		}
+
+		this.elementSize = {
+			width: this.element.outerWidth(),
+			height: this.element.outerHeight()
+		};
+		this.elementOffset = this.element.offset();
+
+		position = { x: event.pageX, y: event.pageY };
+		normValue = this._normValueFromMouse( position );
+		distance = this._valueMax() - this._valueMin() + 1;
+		this.handles.each(function( i ) {
+			var thisDistance = Math.abs( normValue - that.values(i) );
+			if ( distance > thisDistance ) {
+				distance = thisDistance;
+				closestHandle = $( this );
+				index = i;
+			}
+		});
+
+		// workaround for bug #3736 (if both handles of a range are at 0,
+		// the first is always used as the one with least distance,
+		// and moving it is obviously prevented by preventing negative ranges)
+		if( o.range === true && this.values(1) === o.min ) {
+			index += 1;
+			closestHandle = $( this.handles[index] );
+		}
+
+		allowed = this._start( event, index );
+		if ( allowed === false ) {
+			return false;
+		}
+		this._mouseSliding = true;
+
+		this._handleIndex = index;
+
+		closestHandle
+			.addClass( "ui-state-active" )
+			.focus();
+
+		offset = closestHandle.offset();
+		mouseOverHandle = !$( event.target ).parents().andSelf().is( ".ui-slider-handle" );
+		this._clickOffset = mouseOverHandle ? { left: 0, top: 0 } : {
+			left: event.pageX - offset.left - ( closestHandle.width() / 2 ),
+			top: event.pageY - offset.top -
+				( closestHandle.height() / 2 ) -
+				( parseInt( closestHandle.css("borderTopWidth"), 10 ) || 0 ) -
+				( parseInt( closestHandle.css("borderBottomWidth"), 10 ) || 0) +
+				( parseInt( closestHandle.css("marginTop"), 10 ) || 0)
+		};
+
+		if ( !this.handles.hasClass( "ui-state-hover" ) ) {
+			this._slide( event, index, normValue );
+		}
+		this._animateOff = true;
+		return true;
+	},
+
+	_mouseStart: function() {
+		return true;
+	},
+
+	_mouseDrag: function( event ) {
+		var position = { x: event.pageX, y: event.pageY },
+			normValue = this._normValueFromMouse( position );
+
+		this._slide( event, this._handleIndex, normValue );
+
+		return false;
+	},
+
+	_mouseStop: function( event ) {
+		this.handles.removeClass( "ui-state-active" );
+		this._mouseSliding = false;
+
+		this._stop( event, this._handleIndex );
+		this._change( event, this._handleIndex );
+
+		this._handleIndex = null;
+		this._clickOffset = null;
+		this._animateOff = false;
+
+		return false;
+	},
+
+	_detectOrientation: function() {
+		this.orientation = ( this.options.orientation === "vertical" ) ? "vertical" : "horizontal";
+	},
+
+	_normValueFromMouse: function( position ) {
+		var pixelTotal,
+			pixelMouse,
+			percentMouse,
+			valueTotal,
+			valueMouse;
+
+		if ( this.orientation === "horizontal" ) {
+			pixelTotal = this.elementSize.width;
+			pixelMouse = position.x - this.elementOffset.left - ( this._clickOffset ? this._clickOffset.left : 0 );
+		} else {
+			pixelTotal = this.elementSize.height;
+			pixelMouse = position.y - this.elementOffset.top - ( this._clickOffset ? this._clickOffset.top : 0 );
+		}
+
+		percentMouse = ( pixelMouse / pixelTotal );
+		if ( percentMouse > 1 ) {
+			percentMouse = 1;
+		}
+		if ( percentMouse < 0 ) {
+			percentMouse = 0;
+		}
+		if ( this.orientation === "vertical" ) {
+			percentMouse = 1 - percentMouse;
+		}
+
+		valueTotal = this._valueMax() - this._valueMin();
+		valueMouse = this._valueMin() + percentMouse * valueTotal;
+
+		return this._trimAlignValue( valueMouse );
+	},
+
+	_start: function( event, index ) {
+		var uiHash = {
+			handle: this.handles[ index ],
+			value: this.value()
+		};
+		if ( this.options.values && this.options.values.length ) {
+			uiHash.value = this.values( index );
+			uiHash.values = this.values();
+		}
+		return this._trigger( "start", event, uiHash );
+	},
+
+	_slide: function( event, index, newVal ) {
+		var otherVal,
+			newValues,
+			allowed;
+
+		if ( this.options.values && this.options.values.length ) {
+			otherVal = this.values( index ? 0 : 1 );
+
+			if ( ( this.options.values.length === 2 && this.options.range === true ) &&
+					( ( index === 0 && newVal > otherVal) || ( index === 1 && newVal < otherVal ) )
+				) {
+				newVal = otherVal;
+			}
+
+			if ( newVal !== this.values( index ) ) {
+				newValues = this.values();
+				newValues[ index ] = newVal;
+				// A slide can be canceled by returning false from the slide callback
+				allowed = this._trigger( "slide", event, {
+					handle: this.handles[ index ],
+					value: newVal,
+					values: newValues
+				} );
+				otherVal = this.values( index ? 0 : 1 );
+				if ( allowed !== false ) {
+					this.values( index, newVal, true );
+				}
+			}
+		} else {
+			if ( newVal !== this.value() ) {
+				// A slide can be canceled by returning false from the slide callback
+				allowed = this._trigger( "slide", event, {
+					handle: this.handles[ index ],
+					value: newVal
+				} );
+				if ( allowed !== false ) {
+					this.value( newVal );
+				}
+			}
+		}
+	},
+
+	_stop: function( event, index ) {
+		var uiHash = {
+			handle: this.handles[ index ],
+			value: this.value()
+		};
+		if ( this.options.values && this.options.values.length ) {
+			uiHash.value = this.values( index );
+			uiHash.values = this.values();
+		}
+
+		this._trigger( "stop", event, uiHash );
+	},
+
+	_change: function( event, index ) {
+		if ( !this._keySliding && !this._mouseSliding ) {
+			var uiHash = {
+				handle: this.handles[ index ],
+				value: this.value()
+			};
+			if ( this.options.values && this.options.values.length ) {
+				uiHash.value = this.values( index );
+				uiHash.values = this.values();
+			}
+
+			this._trigger( "change", event, uiHash );
+		}
+	},
+
+	value: function( newValue ) {
+		if ( arguments.length ) {
+			this.options.value = this._trimAlignValue( newValue );
+			this._refreshValue();
+			this._change( null, 0 );
+			return;
+		}
+
+		return this._value();
+	},
+
+	values: function( index, newValue ) {
+		var vals,
+			newValues,
+			i;
+
+		if ( arguments.length > 1 ) {
+			this.options.values[ index ] = this._trimAlignValue( newValue );
+			this._refreshValue();
+			this._change( null, index );
+			return;
+		}
+
+		if ( arguments.length ) {
+			if ( $.isArray( arguments[ 0 ] ) ) {
+				vals = this.options.values;
+				newValues = arguments[ 0 ];
+				for ( i = 0; i < vals.length; i += 1 ) {
+					vals[ i ] = this._trimAlignValue( newValues[ i ] );
+					this._change( null, i );
+				}
+				this._refreshValue();
+			} else {
+				if ( this.options.values && this.options.values.length ) {
+					return this._values( index );
+				} else {
+					return this.value();
+				}
+			}
+		} else {
+			return this._values();
+		}
+	},
+
+	_setOption: function( key, value ) {
+		var i,
+			valsLength = 0;
+
+		if ( $.isArray( this.options.values ) ) {
+			valsLength = this.options.values.length;
+		}
+
+		$.Widget.prototype._setOption.apply( this, arguments );
+
+		switch ( key ) {
+			case "disabled":
+				if ( value ) {
+					this.handles.filter( ".ui-state-focus" ).blur();
+					this.handles.removeClass( "ui-state-hover" );
+					this.handles.prop( "disabled", true );
+					this.element.addClass( "ui-disabled" );
+				} else {
+					this.handles.prop( "disabled", false );
+					this.element.removeClass( "ui-disabled" );
+				}
+				break;
+			case "orientation":
+				this._detectOrientation();
+				this.element
+					.removeClass( "ui-slider-horizontal ui-slider-vertical" )
+					.addClass( "ui-slider-" + this.orientation );
+				this._refreshValue();
+				break;
+			case "value":
+				this._animateOff = true;
+				this._refreshValue();
+				this._change( null, 0 );
+				this._animateOff = false;
+				break;
+			case "values":
+				this._animateOff = true;
+				this._refreshValue();
+				for ( i = 0; i < valsLength; i += 1 ) {
+					this._change( null, i );
+				}
+				this._animateOff = false;
+				break;
+			case "min":
+			case "max":
+				this._animateOff = true;
+				this._refreshValue();
+				this._animateOff = false;
+				break;
+		}
+	},
+
+	//internal value getter
+	// _value() returns value trimmed by min and max, aligned by step
+	_value: function() {
+		var val = this.options.value;
+		val = this._trimAlignValue( val );
+
+		return val;
+	},
+
+	//internal values getter
+	// _values() returns array of values trimmed by min and max, aligned by step
+	// _values( index ) returns single value trimmed by min and max, aligned by step
+	_values: function( index ) {
+		var val,
+			vals,
+			i;
+
+		if ( arguments.length ) {
+			val = this.options.values[ index ];
+			val = this._trimAlignValue( val );
+
+			return val;
+		} else {
+			// .slice() creates a copy of the array
+			// this copy gets trimmed by min and max and then returned
+			vals = this.options.values.slice();
+			for ( i = 0; i < vals.length; i+= 1) {
+				vals[ i ] = this._trimAlignValue( vals[ i ] );
+			}
+
+			return vals;
+		}
+	},
+
+	// returns the step-aligned value that val is closest to, between (inclusive) min and max
+	_trimAlignValue: function( val ) {
+		if ( val <= this._valueMin() ) {
+			return this._valueMin();
+		}
+		if ( val >= this._valueMax() ) {
+			return this._valueMax();
+		}
+		var step = ( this.options.step > 0 ) ? this.options.step : 1,
+			valModStep = (val - this._valueMin()) % step,
+			alignValue = val - valModStep;
+
+		if ( Math.abs(valModStep) * 2 >= step ) {
+			alignValue += ( valModStep > 0 ) ? step : ( -step );
+		}
+
+		// Since JavaScript has problems with large floats, round
+		// the final value to 5 digits after the decimal point (see #4124)
+		return parseFloat( alignValue.toFixed(5) );
+	},
+
+	_valueMin: function() {
+		return this.options.min;
+	},
+
+	_valueMax: function() {
+		return this.options.max;
+	},
+
+	_refreshValue: function() {
+		var lastValPercent, valPercent, value, valueMin, valueMax,
+			oRange = this.options.range,
+			o = this.options,
+			that = this,
+			animate = ( !this._animateOff ) ? o.animate : false,
+			_set = {};
+
+		if ( this.options.values && this.options.values.length ) {
+			this.handles.each(function( i ) {
+				valPercent = ( that.values(i) - that._valueMin() ) / ( that._valueMax() - that._valueMin() ) * 100;
+				_set[ that.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%";
+				$( this ).stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate );
+				if ( that.options.range === true ) {
+					if ( that.orientation === "horizontal" ) {
+						if ( i === 0 ) {
+							that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { left: valPercent + "%" }, o.animate );
+						}
+						if ( i === 1 ) {
+							that.range[ animate ? "animate" : "css" ]( { width: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } );
+						}
+					} else {
+						if ( i === 0 ) {
+							that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { bottom: ( valPercent ) + "%" }, o.animate );
+						}
+						if ( i === 1 ) {
+							that.range[ animate ? "animate" : "css" ]( { height: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } );
+						}
+					}
+				}
+				lastValPercent = valPercent;
+			});
+		} else {
+			value = this.value();
+			valueMin = this._valueMin();
+			valueMax = this._valueMax();
+			valPercent = ( valueMax !== valueMin ) ?
+					( value - valueMin ) / ( valueMax - valueMin ) * 100 :
+					0;
+			_set[ this.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%";
+			this.handle.stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate );
+
+			if ( oRange === "min" && this.orientation === "horizontal" ) {
+				this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { width: valPercent + "%" }, o.animate );
+			}
+			if ( oRange === "max" && this.orientation === "horizontal" ) {
+				this.range[ animate ? "animate" : "css" ]( { width: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } );
+			}
+			if ( oRange === "min" && this.orientation === "vertical" ) {
+				this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { height: valPercent + "%" }, o.animate );
+			}
+			if ( oRange === "max" && this.orientation === "vertical" ) {
+				this.range[ animate ? "animate" : "css" ]( { height: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } );
+			}
+		}
+	}
+
+});
+
+}(jQuery));
+(function( $ ) {
+
+var increments = 0;
+
+function addDescribedBy( elem, id ) {
+	var describedby = (elem.attr( "aria-describedby" ) || "").split( /\s+/ );
+	describedby.push( id );
+	elem
+		.data( "ui-tooltip-id", id )
+		.attr( "aria-describedby", $.trim( describedby.join( " " ) ) );
+}
+
+function removeDescribedBy( elem ) {
+	var id = elem.data( "ui-tooltip-id" ),
+		describedby = (elem.attr( "aria-describedby" ) || "").split( /\s+/ ),
+		index = $.inArray( id, describedby );
+	if ( index !== -1 ) {
+		describedby.splice( index, 1 );
+	}
+
+	elem.removeData( "ui-tooltip-id" );
+	describedby = $.trim( describedby.join( " " ) );
+	if ( describedby ) {
+		elem.attr( "aria-describedby", describedby );
+	} else {
+		elem.removeAttr( "aria-describedby" );
+	}
+}
+
+$.widget( "ui.tooltip", {
+	version: "1.9.2",
+	options: {
+		content: function() {
+			return $( this ).attr( "title" );
+		},
+		hide: true,
+		// Disabled elements have inconsistent behavior across browsers (#8661)
+		items: "[title]:not([disabled])",
+		position: {
+			my: "left top+15",
+			at: "left bottom",
+			collision: "flipfit flip"
+		},
+		show: true,
+		tooltipClass: null,
+		track: false,
+
+		// callbacks
+		close: null,
+		open: null
+	},
+
+	_create: function() {
+		this._on({
+			mouseover: "open",
+			focusin: "open"
+		});
+
+		// IDs of generated tooltips, needed for destroy
+		this.tooltips = {};
+		// IDs of parent tooltips where we removed the title attribute
+		this.parents = {};
+
+		if ( this.options.disabled ) {
+			this._disable();
+		}
+	},
+
+	_setOption: function( key, value ) {
+		var that = this;
+
+		if ( key === "disabled" ) {
+			this[ value ? "_disable" : "_enable" ]();
+			this.options[ key ] = value;
+			// disable element style changes
+			return;
+		}
+
+		this._super( key, value );
+
+		if ( key === "content" ) {
+			$.each( this.tooltips, function( id, element ) {
+				that._updateContent( element );
+			});
+		}
+	},
+
+	_disable: function() {
+		var that = this;
+
+		// close open tooltips
+		$.each( this.tooltips, function( id, element ) {
+			var event = $.Event( "blur" );
+			event.target = event.currentTarget = element[0];
+			that.close( event, true );
+		});
+
+		// remove title attributes to prevent native tooltips
+		this.element.find( this.options.items ).andSelf().each(function() {
+			var element = $( this );
+			if ( element.is( "[title]" ) ) {
+				element
+					.data( "ui-tooltip-title", element.attr( "title" ) )
+					.attr( "title", "" );
+			}
+		});
+	},
+
+	_enable: function() {
+		// restore title attributes
+		this.element.find( this.options.items ).andSelf().each(function() {
+			var element = $( this );
+			if ( element.data( "ui-tooltip-title" ) ) {
+				element.attr( "title", element.data( "ui-tooltip-title" ) );
+			}
+		});
+	},
+
+	open: function( event ) {
+		var that = this,
+			target = $( event ? event.target : this.element )
+				// we need closest here due to mouseover bubbling,
+				// but always pointing at the same event target
+				.closest( this.options.items );
+
+		// No element to show a tooltip for or the tooltip is already open
+		if ( !target.length || target.data( "ui-tooltip-id" ) ) {
+			return;
+		}
+
+		if ( target.attr( "title" ) ) {
+			target.data( "ui-tooltip-title", target.attr( "title" ) );
+		}
+
+		target.data( "ui-tooltip-open", true );
+
+		// kill parent tooltips, custom or native, for hover
+		if ( event && event.type === "mouseover" ) {
+			target.parents().each(function() {
+				var parent = $( this ),
+					blurEvent;
+				if ( parent.data( "ui-tooltip-open" ) ) {
+					blurEvent = $.Event( "blur" );
+					blurEvent.target = blurEvent.currentTarget = this;
+					that.close( blurEvent, true );
+				}
+				if ( parent.attr( "title" ) ) {
+					parent.uniqueId();
+					that.parents[ this.id ] = {
+						element: this,
+						title: parent.attr( "title" )
+					};
+					parent.attr( "title", "" );
+				}
+			});
+		}
+
+		this._updateContent( target, event );
+	},
+
+	_updateContent: function( target, event ) {
+		var content,
+			contentOption = this.options.content,
+			that = this,
+			eventType = event ? event.type : null;
+
+		if ( typeof contentOption === "string" ) {
+			return this._open( event, target, contentOption );
+		}
+
+		content = contentOption.call( target[0], function( response ) {
+			// ignore async response if tooltip was closed already
+			if ( !target.data( "ui-tooltip-open" ) ) {
+				return;
+			}
+			// IE may instantly serve a cached response for ajax requests
+			// delay this call to _open so the other call to _open runs first
+			that._delay(function() {
+				// jQuery creates a special event for focusin when it doesn't
+				// exist natively. To improve performance, the native event
+				// object is reused and the type is changed. Therefore, we can't
+				// rely on the type being correct after the event finished
+				// bubbling, so we set it back to the previous value. (#8740)
+				if ( event ) {
+					event.type = eventType;
+				}
+				this._open( event, target, response );
+			});
+		});
+		if ( content ) {
+			this._open( event, target, content );
+		}
+	},
+
+	_open: function( event, target, content ) {
+		var tooltip, events, delayedShow,
+			positionOption = $.extend( {}, this.options.position );
+
+		if ( !content ) {
+			return;
+		}
+
+		// Content can be updated multiple times. If the tooltip already
+		// exists, then just update the content and bail.
+		tooltip = this._find( target );
+		if ( tooltip.length ) {
+			tooltip.find( ".ui-tooltip-content" ).html( content );
+			return;
+		}
+
+		// if we have a title, clear it to prevent the native tooltip
+		// we have to check first to avoid defining a title if none exists
+		// (we don't want to cause an element to start matching [title])
+		//
+		// We use removeAttr only for key events, to allow IE to export the correct
+		// accessible attributes. For mouse events, set to empty string to avoid
+		// native tooltip showing up (happens only when removing inside mouseover).
+		if ( target.is( "[title]" ) ) {
+			if ( event && event.type === "mouseover" ) {
+				target.attr( "title", "" );
+			} else {
+				target.removeAttr( "title" );
+			}
+		}
+
+		tooltip = this._tooltip( target );
+		addDescribedBy( target, tooltip.attr( "id" ) );
+		tooltip.find( ".ui-tooltip-content" ).html( content );
+
+		function position( event ) {
+			positionOption.of = event;
+			if ( tooltip.is( ":hidden" ) ) {
+				return;
+			}
+			tooltip.position( positionOption );
+		}
+		if ( this.options.track && event && /^mouse/.test( event.type ) ) {
+			this._on( this.document, {
+				mousemove: position
+			});
+			// trigger once to override element-relative positioning
+			position( event );
+		} else {
+			tooltip.position( $.extend({
+				of: target
+			}, this.options.position ) );
+		}
+
+		tooltip.hide();
+
+		this._show( tooltip, this.options.show );
+		// Handle tracking tooltips that are shown with a delay (#8644). As soon
+		// as the tooltip is visible, position the tooltip using the most recent
+		// event.
+		if ( this.options.show && this.options.show.delay ) {
+			delayedShow = setInterval(function() {
+				if ( tooltip.is( ":visible" ) ) {
+					position( positionOption.of );
+					clearInterval( delayedShow );
+				}
+			}, $.fx.interval );
+		}
+
+		this._trigger( "open", event, { tooltip: tooltip } );
+
+		events = {
+			keyup: function( event ) {
+				if ( event.keyCode === $.ui.keyCode.ESCAPE ) {
+					var fakeEvent = $.Event(event);
+					fakeEvent.currentTarget = target[0];
+					this.close( fakeEvent, true );
+				}
+			},
+			remove: function() {
+				this._removeTooltip( tooltip );
+			}
+		};
+		if ( !event || event.type === "mouseover" ) {
+			events.mouseleave = "close";
+		}
+		if ( !event || event.type === "focusin" ) {
+			events.focusout = "close";
+		}
+		this._on( true, target, events );
+	},
+
+	close: function( event ) {
+		var that = this,
+			target = $( event ? event.currentTarget : this.element ),
+			tooltip = this._find( target );
+
+		// disabling closes the tooltip, so we need to track when we're closing
+		// to avoid an infinite loop in case the tooltip becomes disabled on close
+		if ( this.closing ) {
+			return;
+		}
+
+		// only set title if we had one before (see comment in _open())
+		if ( target.data( "ui-tooltip-title" ) ) {
+			target.attr( "title", target.data( "ui-tooltip-title" ) );
+		}
+
+		removeDescribedBy( target );
+
+		tooltip.stop( true );
+		this._hide( tooltip, this.options.hide, function() {
+			that._removeTooltip( $( this ) );
+		});
+
+		target.removeData( "ui-tooltip-open" );
+		this._off( target, "mouseleave focusout keyup" );
+		// Remove 'remove' binding only on delegated targets
+		if ( target[0] !== this.element[0] ) {
+			this._off( target, "remove" );
+		}
+		this._off( this.document, "mousemove" );
+
+		if ( event && event.type === "mouseleave" ) {
+			$.each( this.parents, function( id, parent ) {
+				$( parent.element ).attr( "title", parent.title );
+				delete that.parents[ id ];
+			});
+		}
+
+		this.closing = true;
+		this._trigger( "close", event, { tooltip: tooltip } );
+		this.closing = false;
+	},
+
+	_tooltip: function( element ) {
+		var id = "ui-tooltip-" + increments++,
+			tooltip = $( "<div>" )
+				.attr({
+					id: id,
+					role: "tooltip"
+				})
+				.addClass( "ui-tooltip ui-widget ui-corner-all ui-widget-content " +
+					( this.options.tooltipClass || "" ) );
+		$( "<div>" )
+			.addClass( "ui-tooltip-content" )
+			.appendTo( tooltip );
+		tooltip.appendTo( this.document[0].body );
+		if ( $.fn.bgiframe ) {
+			tooltip.bgiframe();
+		}
+		this.tooltips[ id ] = element;
+		return tooltip;
+	},
+
+	_find: function( target ) {
+		var id = target.data( "ui-tooltip-id" );
+		return id ? $( "#" + id ) : $();
+	},
+
+	_removeTooltip: function( tooltip ) {
+		tooltip.remove();
+		delete this.tooltips[ tooltip.attr( "id" ) ];
+	},
+
+	_destroy: function() {
+		var that = this;
+
+		// close open tooltips
+		$.each( this.tooltips, function( id, element ) {
+			// Delegate to close method to handle common cleanup
+			var event = $.Event( "blur" );
+			event.target = event.currentTarget = element[0];
+			that.close( event, true );
+
+			// Remove immediately; destroying an open tooltip doesn't use the
+			// hide animation
+			$( "#" + id ).remove();
+
+			// Restore the title
+			if ( element.data( "ui-tooltip-title" ) ) {
+				element.attr( "title", element.data( "ui-tooltip-title" ) );
+				element.removeData( "ui-tooltip-title" );
+			}
+		});
+	}
+});
+
+}( jQuery ) );
diff --git a/content/injections/jquery.js b/content/injections/jquery.js
new file mode 100644
index 0000000..198b3ff
--- /dev/null
+++ b/content/injections/jquery.js
@@ -0,0 +1,4 @@
+/*! jQuery v1.7.1 jquery.com | jquery.org/license */
+(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cv(a){if(!ck[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){cl||(cl=c.createElement("iframe"),cl.frameBorder=cl.width=cl.height=0),b.appendChild(cl);if(!cm||!cl.createElement)cm=(cl.contentWindow||cl.contentDocument).document,cm.write((c.compatMode==="CSS1Compat"?"<!doctype html>":"")+"<html><body>"),cm.close();d=cm.createElement( [...]
+f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k<c.length;k++){l=A.exec(c[k])||[],m=l[1],n=(l[2]||"").split(".").sort(),s=f.event.special[m]| [...]
+{for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replac [...]
\ No newline at end of file
diff --git a/content/injections/surveys.widgets.js b/content/injections/surveys.widgets.js
new file mode 100644
index 0000000..4ccc653
--- /dev/null
+++ b/content/injections/surveys.widgets.js
@@ -0,0 +1,385 @@
+
+var surveys = {
+
+	question: {},
+	target: "",
+	decodedtarget: "",
+	url: "",
+	answer_value: null,
+	current_idx: 0,
+	submit_enabled: false,
+
+	report: function (msg, data) {
+		var _this = surveys;
+		if (typeof(data) === "object") {
+			data.question_id = _this.question.id;
+			data.url = _this.url;
+			data.target = _this.target;
+		}
+		wot.post("surveyswidget", msg, data);
+	},
+
+	extract_data: function(encoded) {
+		var res = null;
+		try {
+			if(!encoded) return null;
+			var decoded = atob(encoded);
+			if(!decoded) return null;
+
+			res = JSON.parse(decoded);
+			if(!(res && res.question)) return null;
+
+		} catch (e) {
+			console.error("Exception when extracting data in surveys.extract_data()", e);
+		}
+		return res;
+	},
+
+	slider: {
+
+		options_map: {},
+		options: [],
+		max_tick_width: 45,
+		$slider: null,
+
+		prepare_values: function (options) {
+			// makes an array of values for the slider and map them to original options
+
+			var _this = surveys.slider,
+				cnt = 1;
+
+			_this.options = [0]; // add default value
+			_this.options_map[0] = {value: null, text: " "};
+
+			for(var i in options) {
+				_this.options.push(cnt);
+				_this.options_map[cnt] = options[i];
+				cnt++;
+			}
+
+			return _this.options;
+		},
+
+		get_label_by_index: function(index) {
+			var _this = surveys.slider,
+				map = _this.options_map;
+
+			return (map[index] && map[index]['text'] !== undefined) ? map[index]['text'] : "";
+		},
+
+		get_value_by_index: function(index) {
+			var _this = surveys.slider,
+				map = _this.options_map;
+
+			return (map[index] && map[index]['value'] !== undefined) ? map[index]['value'] : "";
+		},
+
+
+		set_value_label: function (label, is_permanent) {
+			var $label = $(".surveys-slider-label");
+			$label.toggleClass("selected", is_permanent);
+			$label.toggleClass("isset", (label.trim().length > 0)); // don't show decoration if no text is in the label
+			$label.text(label);
+		},
+
+		update_slider_state: function (idx) {
+			var _this = surveys.slider,
+				label = _this.get_label_by_index(idx),
+				value = _this.get_value_by_index(idx);
+
+			surveys.current_idx = idx;
+			surveys.answer_value = value;
+			_this.set_value_label(label, true);
+
+			// modify appearance of the handle in "zero" position
+			$(".zero-tick").toggleClass("selected", (idx == 0));
+
+			$(".slider-tick").removeClass("selected");
+
+			if (idx > 0) {
+				$(".slider-tick:nth-child(" + (idx+1) + ")").addClass("selected");
+			}
+
+			surveys.submit_enabled = !!idx;
+			surveys.ui.update_submit_status();
+		},
+
+		on_slider_change: function (event, ui) {
+			var _this = surveys.slider;
+			_this.update_slider_state(ui.value);
+		},
+
+		on_slide: function(event, ui) {
+			var _this = surveys.slider;
+			_this.update_slider_state(ui.value);
+		},
+
+		on_slide_stop: function(event, ui) {
+			var _this = surveys;
+			wot.ga.fire_event(wot.ga.categories.FBL, wot.ga.actions.FBL_slidered, _this.target);
+		},
+
+		update_hover_status: function(idx, is_hovered) {
+			var _this = surveys.slider,
+				value_to_show = is_hovered ? idx : surveys.current_idx,
+				label = _this.get_label_by_index(value_to_show);
+
+			_this.set_value_label(label, !is_hovered);
+
+			if (idx > 0) {
+				$(".slider-tick:nth-child(" + (idx+1) + ")").toggleClass("hovered", is_hovered);
+			} else {
+				$(".zero-tick").toggleClass("hovered", is_hovered);
+			}
+		},
+
+		get_idx: function (element) {
+			return $(element).index();
+		},
+
+		on_enter: function (event) {
+			var _this = surveys.slider;
+			_this.update_hover_status(_this.get_idx(event.currentTarget), true);
+		},
+
+		on_leave: function (event) {
+			var _this = surveys.slider;
+			_this.update_hover_status(_this.get_idx(event.currentTarget), false);
+		},
+
+		on_click: function (event) {
+			var _this = surveys.slider;
+			_this.$slider.slider("value", _this.get_idx(event.currentTarget));
+			wot.ga.fire_event(wot.ga.categories.FBL, wot.ga.actions.FBL_directclick, _this.target);
+		},
+
+		build_slider: function() {
+			var _this =             surveys.slider,
+				$slider =           _this.$slider,
+				items_count =       _this.options.length,
+				tick_ws,
+				$tick_container =   $(".ticks-container");
+
+			var tick_width = Math.round(($slider.width() - items_count) / (items_count - 1));   // take into account border width also
+
+			tick_ws = String(tick_width) + "px";
+
+			// create ticks only if there are more than 3 options. 2 options are represented by edge-values of the slider
+			// and one additional option is a default value ("undefined")
+			if (items_count > 1) {
+				for(var i=0; i < items_count - 1; i++) {
+					$tick_container.append("<div class='slider-tick'/>");
+				}
+			}
+
+			var $ticks = $(".slider-tick"),
+				$zerotick = $(".zero-tick");
+
+			// add ticks to the slider's scale, and make other facelift to imitate a slider with "undefined default value"
+			$ticks.css("width", tick_ws);
+			var parent_offset = $(".surveys-slider").offset();
+			var ticks_offset = $slider.offset().left - parent_offset.left,
+				tick_cont_left = ticks_offset - tick_width/2;
+			$tick_container.css("left", tick_cont_left + "px");
+			$zerotick.css("width", tick_width);
+
+			_this.update_slider_state(0);
+
+			// put bounds' labels
+			$(".surveys-slider-bounds").css({
+				left: tick_cont_left + tick_width + "px",
+				width: $tick_container.width() - tick_width
+			});
+			$(".surveys-slider-left-bound").text(_this.get_label_by_index(1));//.css("left", tick_ws);
+			$(".surveys-slider-right-bound").text(_this.get_label_by_index(items_count - 1));
+
+			// handle hovering over ticks
+			$ticks.mouseenter(_this.on_enter).mouseleave(_this.on_leave);
+			$zerotick.mouseenter(_this.on_enter).mouseleave(_this.on_leave);
+
+			// handle clicks to ticks
+			$ticks.click(_this.on_click);
+			$zerotick.click(_this.on_click);
+
+		},
+
+		init_slider: function () {
+			var _this = surveys.slider,
+				items_count = _this.options.length;
+
+			_this.$slider = $("#slider");   // bind Slider control to div
+			_this.$slider.slider({
+				min: 0,
+				max: items_count - 1,
+				step: 1,
+				animate: "fast",
+				change: _this.on_slider_change,
+				slide: _this.on_slide,
+				stop: _this.on_slide_stop,
+				create: surveys.slider.build_slider // build the rest after slider is created
+			});
+		}
+	},
+
+	ui: {
+
+		is_optout_shown: false,
+		is_whatisthis_shown: false,
+
+		show_bottom_section: function () {
+			$(".bottom-section").show();
+		},
+
+		hide_bottom_section: function () {
+			var _this = surveys.ui;
+			$(".bottom-section").hide();
+			_this.is_optout_shown = false;
+			_this.is_whatisthis_shown = false;
+		},
+
+		hide_optout: function () {
+			surveys.ui.is_optout_shown = false;
+			$("#btab-optout").hide();
+		},
+
+		switch_tab: function (tab_name) {
+
+			$(".tabs").hide();
+			$("#tab-"+tab_name).show();
+		},
+
+		on_logo: function (e) {
+
+		},
+
+		on_close: function (e) {
+			var _this = surveys;
+			wot.ga.fire_event(wot.ga.categories.FBL, wot.ga.actions.FBL_closed, _this.target);
+
+			// give small time for GA to send event
+			surveys.report("close", {});
+		},
+
+		on_optout: function (e) {
+			var _this = surveys.ui,
+				$tab = $("#btab-optout");
+
+			$("#btab-whatsthis").hide(); // explicitly hide another bottom tabs
+			_this.is_whatisthis_shown = false;
+
+			if(_this.is_optout_shown) {
+				surveys.ui.hide_bottom_section();
+				_this.hide_optout();
+
+			} else {
+
+				surveys.ui.show_bottom_section();
+				$tab.show();
+
+				$(".button-yes", $tab).click(function () {
+					wot.ga.fire_event(wot.ga.categories.FBL, wot.ga.actions.FBL_optout_yes, _this.target);
+					surveys.report("optout", {});
+				});
+
+				$(".button-no", $tab).click(function () {
+					_this.hide_optout();
+					_this.hide_bottom_section();
+					wot.ga.fire_event(wot.ga.categories.FBL, wot.ga.actions.FBL_optout_no, _this.target);
+				});
+
+				_this.is_optout_shown = true;
+				wot.ga.fire_event(wot.ga.categories.FBL, wot.ga.actions.FBL_optout_shown, _this.target);
+			}
+		},
+
+		on_whatisthis: function (e) {
+
+			var _this = surveys.ui,
+				$btab = $("#btab-whatsthis");
+
+			_this.hide_optout();
+
+			if (_this.is_whatisthis_shown) {
+				surveys.ui.hide_bottom_section();
+				$btab.hide();
+			} else {
+				surveys.ui.show_bottom_section();
+				$btab.show();
+				_this.is_whatisthis_shown = true;
+				wot.ga.fire_event(wot.ga.categories.FBL, wot.ga.actions.FBL_whatisthis, _this.target);
+			}
+		},
+
+		on_submit: function (e) {
+			var _this = surveys;
+			if (_this.answer_value !== null) {
+
+				_this.report("submit", { answer: _this.answer_value });
+
+				wot.ga.fire_event(wot.ga.categories.FBL, wot.ga.actions.FBL_submit, _this.target);
+
+				_this.ui.hide_bottom_section();
+				_this.ui.switch_tab("final");
+			}
+		},
+
+		update_texts: function () {
+			var _this = surveys;
+
+			// sanitize the questions (to avoid XSS with addon) and replace placeholder %site% with target name
+			var text = wot.utils.htmlescape(_this.question.text).replace(/%site%/,
+				"<span class='domainname'>" + _this.decodedtarget + "</span>");
+
+			$(".surveys-question").html(text);  // should be safe since we sanitized the question
+		},
+
+		update_submit_status: function () {
+			var $submit = $(".surveys-submit");
+
+			$submit.toggleClass("enabled", surveys.submit_enabled);
+		}
+
+	},
+
+	init: function () {
+		var _this = surveys;
+		var data = _this.extract_data(window.name); // we use name property to transfer data from addon's background
+		if (data) {
+			_this.decodedtarget = data.decodedtarget || "";
+			_this.target = data.target || "";
+			_this.question = data.question ? data.question : {};
+			_this.url = data.url;
+
+			_this.ui.update_texts();
+			_this.slider.prepare_values(_this.question.choices);
+
+			_this.slider.init_slider();
+			_this.report("shown", data);
+
+			// report after short delay to make sure GA code is inited
+			setTimeout(function () {
+				wot.ga.fire_event(wot.ga.categories.FBL, wot.ga.actions.FBL_shown, _this.target);
+			}, 500);
+
+		} else {
+			surveys.report("close", {});
+		}
+
+		// setup events' handlers
+		$(".surveys-submit").click(_this.ui.on_submit);
+		$(".surveys-optout").click(_this.ui.on_optout);
+		$(".close-button").click(_this.ui.on_close);
+		$(".surveys-whatsthis").click(_this.ui.on_whatisthis);
+		$(".wot-logo").click(_this.ui.on_logo);
+
+		$(".close-button-secondary").click(function (event) {
+			_this.ui.hide_bottom_section();
+			wot.ga.fire_event(wot.ga.categories.FBL, wot.ga.actions.FBL_bottom_close);
+		});
+
+	}
+};
+
+$(document).ready(function () {
+	surveys.init();
+});
diff --git a/content/injections/wot_proxy.js b/content/injections/wot_proxy.js
new file mode 100644
index 0000000..16d6e12
--- /dev/null
+++ b/content/injections/wot_proxy.js
@@ -0,0 +1,356 @@
+/*
+	wot.js
+	Copyright © 2012 -   WOT Services Oy <info at mywot.com>
+
+	This file is part of WOT.
+
+	WOT is free software: you can redistribute it and/or modify it
+	under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	WOT is distributed in the hope that it will be useful, but WITHOUT
+	ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+	or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
+	License for more details.
+
+	You should have received a copy of the GNU General Public License
+	along with WOT. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* -- This is imitation of wot object which is in the Chrome version of the addon.
+*  The reason why we use it like this, is to reuse same code without changes between browsers.
+*  wot_proxy helps with it by providing the same interface of wot object but using config.js values.
+* */
+
+var wot = {
+	version: "20121116",
+	platform: "firefox",
+	debug: false,           // when changing this, don't forget to switch ga_id value also!
+	default_component: 0,
+	enable_surveys: true,   // Feedback loop engine
+
+	ga_id: "UA-35564069-1", // test: UA-35564069-1 , live: UA-2412412-8
+
+	// environment (browser, etc)
+	env: {
+		is_mailru: false,
+		is_yandex: false,
+		is_rambler: false
+	},
+
+	components: [
+		{ name: 0 },
+		{ name: 1 },
+		{ name: 2 },
+		{ name: 4 }
+	],
+
+	reputationlevels: [
+		{ name: "rx", min: -2 },
+		{ name: "r0", min: -1 },
+		{ name: "r1", min:  0 },
+		{ name: "r2", min: 20 },
+		{ name: "r3", min: 40 },
+		{ name: "r4", min: 60 },
+		{ name: "r5", min: 80 }
+	],
+
+	confidencelevels: [
+		{ name: "cx", min: -2 },
+		{ name: "c0", min: -1 },
+		{ name: "c1", min: 6 },
+		{ name: "c2", min: 12 },
+		{ name: "c3", min: 23 },
+		{ name: "c4", min: 34 },
+		{ name: "c5", min: 45 }
+	],
+
+	searchtypes: {
+		optimized: 0,
+		worst: 1,
+		trustworthiness: 2
+	},
+
+	warningtypes: { /* bigger value = more severe warning */
+		none: 0,
+		notification: 1,
+		overlay: 2,
+		block: 3
+	},
+
+	warningreasons: { /* bigger value = more important reason */
+		none: 0,
+		unknown: 1,
+		rating: 2,
+		reputation: 3
+	},
+
+	urls: {
+		base:		"http://www.mywot.com/",
+		scorecard:	"http://www.mywot.com/scorecard/",
+		settings:	"http://www.mywot.com/settings",
+		welcome:	"http://www.mywot.com/settings/welcome",
+		setcookies:	"http://www.mywot.com/setcookies.php",
+		update:		"http://www.mywot.com/update",
+
+		contexts: {
+			rwlogo:     "rw-logo",
+			rwsettings: "rw-settings",
+			rwguide:    "rw-guide",
+			rwviewsc:   "rw-viewsc",
+			rwprofile:  "rw-profile",
+			rwmsg:      "rw-msg",
+			warnviewsc: "warn-viewsc",
+			warnrate:   "warn-rate",
+			popupviewsc: "popup",
+			popupdonuts: "popup-donuts"
+		}
+	},
+
+	firstrunupdate: 1, /* increase to show a page after an update */
+
+	cachestatus: {
+		error:	0,
+		ok:		1,
+		busy:	2,
+		retry:	3,
+		link:	4
+	},
+
+	expire_warned_after: 20000,  // number of milliseconds after which warned flag will be expired
+
+	// Constants for playing with date & time (in seconds)
+	DT: {
+		MINUTE: 60,
+		HOUR: 3600,
+		DAY: 24 * 3600,
+		WEEK: 7 * 24 * 3600,
+		MONTH: 30 * 24 * 3600
+	},
+
+	/* logging */
+
+	log: function (s)
+	{
+		if (wot.debug) {
+			console.log(s);
+		}
+	},
+
+	/* events */
+
+	events: {},
+
+	bind: function (name, func, params)
+	{
+	},
+
+	post: function(name, message, data, port)
+	{
+		try {
+			//Call the function from sandbox and make sure data passing from here is safe
+			var data_obj = {
+				name: name,
+				message: message,
+				data: data
+			};
+
+			wot_post(JSON.stringify(data_obj));
+
+		} catch (e) {
+			console.log("Failed to call wot_post()" + e);
+		}
+	},
+
+	/* i18n */
+
+	/* helpers */
+
+	getuniques: function(list)
+	{
+		var seen = {};
+
+		return list.filter(function(item) {
+					if (seen[item]) {
+						return false;
+					} else {
+						seen[item] = true;
+						return true;
+					}
+				});
+	},
+
+	contextedurl: function(url, context)
+	{
+		var newurl = url;
+		newurl += ( (url.indexOf("?") > 0) ? "&" : "?" );
+		newurl += "utm_source=addon&utm_content=" + context;
+		return newurl;
+	},
+
+	detect_environment: function(readonly)
+	{
+		var readonly = readonly || false;
+		// try to understand in which environment we are run
+		var user_agent = window.navigator.userAgent || "";
+		wot.env.is_mailru = user_agent.indexOf("MRCHROME") >= 0;
+
+		if(wot.env.is_mailru) {
+			// set param to label requests
+			wot.partner = "mailru";
+		}
+
+		if(!readonly) wot.prefs.set("partner", wot.partner);
+	},
+
+	time_sincefirstrun: function()
+	{
+		// gives time (in seconds) spent from very first run of the addon.
+		var starttime_str = wot.prefs.get("firstrun:time");
+		if (starttime_str) {
+			var starttime = new Date(starttime_str);
+			return (new Date() - starttime) / 1000;    // in seconds;
+
+		} else {
+			return undefined;
+		}
+	},
+
+	time_since: function(a, b) {
+
+		if (typeof a === "string") {
+			a = new Date(a);
+		}
+
+		b = b || new Date();
+
+		if (typeof b === "string") {
+			b = new Date(b);
+		}
+
+		return (b - a) / 1000;  // in seconds
+	}
+};
+
+
+wot.utils = {
+
+	get_document: function (frame) {
+		frame = frame || window;
+		var framed_document = frame.document || frame.contentDocument;
+		return framed_document;
+	},
+
+	get_or_create_element: function (id, tag, frame) {
+		tag = tag || "div";
+		var framed_document = wot.utils.get_document(frame);
+
+		var elem = framed_document.getElementById(id);
+
+		if(!elem) {
+			elem = framed_document.createElement(tag);
+			elem.setAttribute("id", id);
+		}
+
+		return elem;
+	},
+
+	attach_element: function (element, frame) {
+		var framed_document = wot.utils.get_document(frame);
+
+		if(framed_document) {
+			var body = framed_document.getElementsByTagName("body");
+
+			if (!element || !body || !body.length) {
+				return false;
+			}
+
+			return body[0].appendChild(element);
+		} else {
+			wot.log("Can't get document of frame");
+			return false;
+		}
+
+	},
+
+	attach_style: function (style_file_or_object, uniq_id, frame) {
+		try {
+			uniq_id = uniq_id || null;
+			var reuse_style = false;
+
+			var framed_document = wot.utils.get_document(frame);
+
+			if(!framed_document) {
+				return false;
+			}
+
+			if(uniq_id) {
+				var el = framed_document.getElementById(uniq_id);
+				if(el) {
+					// if the element exists already - remove it to update styles
+					reuse_style = true;
+				}
+			}
+
+			var head = framed_document.getElementsByTagName("head");
+
+			if (!head || !head.length) {
+				return false;
+			}
+
+			var style = reuse_style ? el : framed_document.createElement("style");
+
+			if (!style) {
+				return false;
+			}
+
+			if(uniq_id) {
+				style.setAttribute("id", uniq_id);
+			}
+
+			style.setAttribute("type", "text/css");
+
+			if (typeof style_file_or_object === "object") {
+				style.innerText = style_file_or_object.style;
+			} else {
+				style.innerText = "@import \"chrome://wot/" + style_file_or_object + "\";";
+			}
+
+			if (!reuse_style) {
+				head[0].appendChild(style);
+			}
+
+			return true;
+		} catch (e) {
+			console.log("wot.utils.attach_style() failed with", e, "Arguments:", arguments);
+			return false;
+		}
+	},
+
+	processhtml: function (html, replaces) {
+		try {
+			replaces.forEach(function(item) {
+				html = html.replace(RegExp("{" + item.from + "}", "g"),
+					item.to);
+			});
+
+			return html;
+		} catch (e) {
+			console.log("warning.processhtml: failed with " + e);
+		}
+
+		return "";
+	},
+
+	htmlescape: function(str) {
+		var tagsToReplace = {
+			'&': '&',
+			'<': '<',
+			'>': '>'
+		};
+		return str.replace(/[&<>]/g, function(symb) {
+			return tagsToReplace[symb] || symb;
+		});
+	}
+};
diff --git a/content/overlay.xul b/content/overlay.xul
index 39c375a..0b147b4 100644
--- a/content/overlay.xul
+++ b/content/overlay.xul
@@ -34,6 +34,7 @@
 	<script type="application/x-javascript" src="chrome://wot/content/prefs.js"/>
 	<script type="application/x-javascript" src="chrome://wot/content/my.js"/>
 	<script type="application/x-javascript" src="chrome://wot/content/warning.js"/>
+    <script type="application/x-javascript" src="chrome://wot/content/surveys.js"/>
 	<script type="application/x-javascript" src="chrome://wot/content/update.js"/>
 	<script type="application/x-javascript" src="chrome://wot/content/popup.js"/>
 	<script type="application/x-javascript" src="chrome://wot/content/search.js"/>
diff --git a/content/search.js b/content/search.js
index c83f9a7..71d2670 100644
--- a/content/search.js
+++ b/content/search.js
@@ -1077,9 +1077,9 @@ var wot_search =
 		}
 	},
 
-	getsandboxfunc: function(sandbox, name)
+	getsandboxfunc: function(sandbox, name, obj)
 	{
-		var obj = this.sandboxapi;
+		obj = obj || wot_search.sandboxapi;
 
 		return function() {
 			var args = [ sandbox ];
@@ -1253,7 +1253,7 @@ var wot_search =
 		} catch (e) {
 			dump("wot_search.onclick: failed with " + e + "\n");
 		}
-	},
+	}
 };
 
 wot_modules.push({ name: "wot_search", obj: wot_search });
diff --git a/content/surveys.js b/content/surveys.js
new file mode 100644
index 0000000..ef98e90
--- /dev/null
+++ b/content/surveys.js
@@ -0,0 +1,548 @@
+/*
+ warning.js
+ Copyright © 2012 -   WOT Services Oy <info at mywot.com>
+
+ This file is part of WOT.
+
+ WOT is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ WOT is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
+ License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with WOT. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+var wot_surveys = {
+
+	fbl_form_schema:    "//",
+	storage_file:       "storage.json",
+	fbl_form_uri:       "fbl.local/feedback/surveys.html",
+	re_fbl_uri:         null,
+	wrapper_id:         "wot_surveys_wrapper",
+	is_shown:           false,
+	wrapper:            null,
+	pheight:            350,
+	pwidth:             392,
+	px:                 10,
+	py:                 10,
+	script_base:        "resource://wot-base-dir/injections/",
+	scripts:            [ "jquery.js", "jquery-ui-1.9.2.custom.js",
+						  "wot_proxy.js", "ga_configure.js",
+						 "surveys.widgets.js", "ga_init.js"],
+
+	asked: {},
+	asked_loaded: false,
+	last_time_asked: null,
+
+	calm_period:        1 * 24 * 3600, // Time in seconds after asking a question before we can ask next question
+	always_ask:         ['api.mywot.com', 'fb.mywot.com'],
+	always_ask_passwd:  "#surveymewot", // this string must be present to show survey by force
+	reset_passwd:       "#wotresetsurveysettings", // this string must be present to reset timers and optout
+
+	FLAGS: {
+		none:       0,  // a user didn't make any input yet
+		submited:   1,  // a user has given the answer
+		closed:     2,  // a user has closed the survey dialog without givin the answer
+		optedout:   3   // a user has clicked "Hide forever"
+	},
+
+	survey_url: function()
+	{
+		return this.fbl_form_schema + this.fbl_form_uri;
+	},
+
+	load_delayed: function ()
+	{
+		dump("WOT feedback Loop is being initialized\n");
+		this.re_fbl_uri = new RegExp("^" + wot_surveys.fbl_form_uri, "i");  // prepare RegExp once to use often
+
+		try {
+			var lasttime = wot_prefs.getChar("feedback_lasttimeasked", null);
+			if(lasttime) {
+				this.last_time_asked = new Date(lasttime);
+			}
+		} catch (e) {
+			this.last_time_asked = null;
+			dump("wot_surveys.load_delayed raised the exeption:" + e + "\n");
+		}
+
+		dump("WOT FBL: last_time_asked = " + this.last_time_asked + "\n");
+
+		// Load the JSON stored data about asked websites
+		wot_file.read_json(wot_surveys.storage_file, function (data, status) {
+			dump("File is loaded? " + JSON.stringify(data) + "\n");
+
+			if (data && data.asked) {
+				wot_surveys.asked = data.asked;
+			}
+
+			wot_surveys.asked_loaded = true;    // set this flag anyway to indicate that loading finished
+		});
+	},
+
+	domcontentloaded: function(event)
+	{
+		try {
+
+			if (!event || !wot_util.isenabled()) {
+				return;
+			}
+
+			var content = event.originalTarget,
+				location = (content && content.location) ? content.location : {};
+
+			var is_framed = (content.defaultView != content.defaultView.top);
+
+			// Process framed documents differently than normal ones
+			if (is_framed) {
+
+				if (location) {
+					// skip all frames except of our own FBL form
+					if (wot_surveys.re_fbl_uri.test(location.host + location.pathname)) {
+						// here we found WOT FBL form loaded into a frame. Next step - to inject JS into it.
+						wot_surveys.inject_javascript(content);
+					}
+				}
+
+			} else {
+
+				// same code as for warning screen
+				if (!content || !location || !location.href ||
+					wot_url.isprivate(location.href) || !(/^https?:$/.test(location.protocol))) {
+					return;
+				}
+
+				var hostname = wot_url.gethostname(location.href);
+				var warning_type = wot_warning.isdangerous(hostname, false);
+
+				// ask only if no big Warning is going to be shown
+				if (warning_type == WOT_WARNING_NONE|| warning_type == WOT_WARNING_NOTIFICATION) {
+					wot_surveys.try_show(content, hostname);
+				}
+			}
+
+		} catch (e) {
+			dump("wot_surveys.domcontentloaded: failed with " + e + "\n");
+		}
+
+	},
+
+	unload: function (event)
+	{
+		// TODO: Implement some cleaning here?
+	},
+
+	get_or_create_sandbox: function(content)
+	{
+		var sandbox = content.wotsandbox;
+
+		if (!sandbox) {
+			var wnd = new XPCNativeWrapper(content.defaultView);
+			sandbox = new Components.utils.Sandbox(wnd);
+
+			sandbox.window = wnd;
+			sandbox.document = sandbox.window.document;
+			sandbox.__proto__ = sandbox.window;
+
+			sandbox.wot_post = wot_search.getsandboxfunc(sandbox, "wot_post", wot_surveys.sandboxapi);
+
+			content.wotsandbox = sandbox;
+		}
+
+		return sandbox;
+	},
+
+	load_file: function(file)
+	{
+		var str = "";
+
+		try {
+			var ioService = Components.classes["@mozilla.org/network/io-service;1"]
+				.getService(Components.interfaces.nsIIOService);
+			var scriptableStream = Components
+				.classes["@mozilla.org/scriptableinputstream;1"]
+				.getService(Components.interfaces.nsIScriptableInputStream);
+
+			var channel = ioService.newChannel(file, null, null);
+			var input = channel.open();
+			scriptableStream.init(input);
+			str = scriptableStream.read(input.available());
+			scriptableStream.close();
+			input.close();
+		} catch (e) {
+			dump("wot_surveys.load_file(): failed with " + e + "\n");
+		}
+
+		return str;
+	},
+
+	inject_javascript: function (content)
+	{
+		dump("Going to inject JS into FBL form\n");
+		var sandbox = wot_surveys.get_or_create_sandbox(content);
+
+		var contents = "",
+			url = "";
+
+		// load all scripts and join to one text
+		for(var i=0; i < wot_surveys.scripts.length; i++) {
+			url = wot_surveys.script_base + wot_surveys.scripts[i];
+			contents = wot_surveys.load_file(url);
+
+			// run scripts in fbl form-page
+			try {
+				Components.utils.evalInSandbox(contents, sandbox);
+			} catch (e) {
+				dump("wot_surveys.load_script(): evalInSandbox " +
+					"failed with " + e + "\n");
+			}
+		}
+
+	},
+
+	inject: function (doc, question)
+	{
+		var ws = wot_surveys;
+		var location = doc.defaultView.location;
+
+		// skip params and hash in the URL
+		question.url = location.protocol + "//" + location.host + location.pathname;
+
+		var wrapper = doc.getElementById(ws.wrapper_id);
+		if(wrapper) {
+			return;
+		}
+		wrapper = doc.createElement("iframe");
+		wrapper.setAttribute("id", ws.wrapper_id);
+
+		if (!wrapper) {
+			dump("can't add element to DOM / wot.surveys.inject_placeholder()");
+			return;
+		}
+
+		ws.wrapper = wrapper;  // keep the link to the element to destroy it
+
+		wrapper.setAttribute("scrolling", "no");
+
+		wrapper.setAttribute("style",
+			"position: fixed; " +
+				"top: " + ws.py + "px; " +
+				"left: "+ ws.px +"px;" +
+				"width: "+ ws.pwidth +"px; " +
+				"height: "+ ws.pheight +"px; " +
+				"z-index: 2147483647; " +
+				"border: none; visibility: hidden;");
+
+		wrapper.setAttribute("src", this.survey_url());
+
+		var encoded_data = btoa(JSON.stringify(question));
+
+		// Probably in FF we should transfer data to the frame by injecting it as JS (json) object instead of
+		// relying to "name" property
+		wrapper.setAttribute("name", encoded_data);  // transfer question's data via "name" property of iframe
+
+		wot_browser.attach_element(wrapper, doc.defaultView); // attach iframe wrapper to DOM
+	},
+
+	try_show: function (doc, hostname)
+	{
+		try {
+			var url = doc.defaultView.location.href;
+
+			// test url for RESET command
+			if (wot_surveys.always_ask.indexOf(hostname) >= 0 && url && url.indexOf(wot_surveys.reset_passwd) >= 0) {
+				wot_surveys.reset_settings(hostname);
+				return;
+			}
+
+			var question = wot_surveys.get_question(hostname);
+
+			if (this.is_tts(hostname, url, question.question)) {
+				this.inject(doc, question);
+			}
+
+		} catch (e) {
+			dump("wot_surveys.try_show() failed with " + e + "\n");
+		}
+	},
+
+	reset_settings: function (hostname)
+	{
+		var ws = wot_surveys;
+		ws.asked_loaded = true;
+		ws.last_time_asked =null;
+		ws.asked = {};      // reset the list of websites asked about
+		ws.opt_out(false);  // reset opt-out
+		wot_prefs.setChar("feedback_lasttimeasked", ""); // reset time
+		ws.remember_asked(hostname, 0, ws.FLAGS.none);
+	},
+
+	remove_form: function (sandbox, timeout)
+	{
+		try {
+
+			timeout = timeout || 100;
+
+			window.setTimeout(function () {
+				var wrapper = wot_surveys.get_wrapper(sandbox);
+				if (wrapper) {
+					wrapper.parentNode.removeChild(wrapper);
+				}
+			}, timeout);
+
+		} catch (e) {
+			dump("wot_surveys.remove_form() failed with " + e + "\n");
+		}
+	},
+
+	get_question: function (hostname)
+	{
+		try {
+			var question_id = wot_cache.get(hostname, "question_id");
+			var question_text = wot_cache.get(hostname, "question_text");
+			var choices_number = wot_cache.get(hostname, "choices_number");
+
+			dump("SHOW: id, text : " + String(question_id) + " , " + String(question_text) + "\n");
+
+			if (choices_number > 0) {
+				var question = {
+					target: hostname,
+					decodedtarget: wot_idn.idntoutf(hostname),
+					question: {
+						id: question_id,
+						text: question_text,
+						choices: []
+					}
+				};
+
+				for(var i= 0, v, t; i < choices_number; i++) {
+					v = wot_cache.get(hostname, "choice_value_" + i);
+					t = wot_cache.get(hostname, "choice_text_" + i);
+					question.question.choices.push({ value: v, text: t });
+				}
+
+				return question;
+
+			} else {
+				return {};
+			}
+
+		} catch (e) {
+			return {};
+		}
+
+	},
+
+	is_tts: function (hostname, url, question)
+	{
+		var ws = wot_surveys;
+
+		dump("IS_TTS? " + JSON.stringify(question) + "\n");
+
+		try {
+			if(!wot_surveys.asked_loaded) return false; // data isn't ready for process
+			dump("if(!wot_surveys.asked_loaded) passed.\n");
+
+			if(!(question && question.id !== undefined && question.text && question.choices)) {
+				// no question was given for the current website - do nothing
+				return false;
+			}
+			dump("is_tts: question test passed.\n");
+
+			// on special domains we should always show the survey if there is a special password given (for testing purposes)
+			// e.g. try this url http://api.mywot.com/test.html#surveymewot
+			if (ws.always_ask.indexOf(hostname) >= 0 && url && url.indexOf(ws.always_ask_passwd) >= 0) {
+				return true;
+			}
+			dump("is_tts: always ask test not passed.\n");
+
+			if (ws.is_optedout() || !wot_prefs.getBool("feedback_enabled", true)) {
+				return false;
+			}
+			dump("is_tts: opt-out and feedback_enabled test passed.\n");
+
+			// check if have asked the user more than X days ago or never before
+			if (ws.last_time_asked && wot_util.time_since(ws.last_time_asked) < ws.calm_period) {
+				return false;
+			}
+			dump("is_tts: last-time test passed.\n");
+
+			// check whether we already have asked the user about current website
+			if (ws.asked[hostname] && ws.asked[hostname][question.id]) {
+				// here we could test also if user just closed the survey last time without providing any info
+				// (in case if we want to be more annoying)
+				return false;
+			}
+			dump("is_tts: already asked test passed -> show it!\n");
+
+			return true;
+		} catch (e) {
+			dump("wot_surveys.is_tts() failed with " + e + "\n");
+			return false;
+		}
+
+	},
+
+	is_optedout: function()
+	{
+		return wot_prefs.getBool("feedback_optedout", false);
+	},
+
+	opt_out: function(value)
+	{
+		value = (value === undefined) ? true : value;
+		wot_prefs.setBool("feedback_optedout", value);
+	},
+
+	remember_asked: function(target, question_id, status) {
+		var ws = wot_surveys;
+
+		try {
+
+			status = status === undefined ? ws.FLAGS.none : status;
+
+			var asked_data = {
+				time: new Date(),   // time of first show the survey
+				status: status
+			};
+
+			if (ws.asked[target]) {
+				if (ws.asked[target][question_id]) {
+					asked_data = ws.asked[target][question_id];
+					asked_data.status = status;    // just update the status
+				} else {
+					ws.asked[target][question_id] = {};
+				}
+
+			} else {
+				ws.asked[target] = {};
+				ws.asked[target][question_id] = {};
+			}
+
+			ws.asked[target][question_id] = asked_data;    // keep in runtime variable
+
+			var storage = {
+				asked: wot_surveys.asked
+			};
+			wot_file.save_json(wot_surveys.storage_file, storage); // and dump to file
+
+		} catch (e) {
+			console.error("remember_asked() failed with", e);
+		}
+	},
+
+	save_asked_status: function (data, status) {
+		var ws = wot_surveys;
+		try {
+			if (data && data.target && data.question_id) {
+				ws.remember_asked(data.target, data.question_id, status);
+
+				// we remember the last time of user's interaction with FBL
+				ws.last_time_asked = new Date();
+				wot_prefs.setChar("feedback_lasttimeasked", ws.last_time_asked);
+			}
+		} catch (e) {
+			console.error(e);
+		}
+	},
+
+	get_top_content: function (sandbox)
+	{
+		var top = null;
+		if(sandbox && sandbox.window && sandbox.window.top) {
+			top = sandbox.window.top;  // look into top content window document
+		}
+		return top;
+	},
+
+	get_wrapper: function (sandbox)
+	{
+		var wrapper = null;
+
+		try {
+			var top = wot_surveys.get_top_content(sandbox);
+			if(top && top.document) {
+				wrapper = top.document.getElementById(wot_surveys.wrapper_id);
+				if(!wrapper) {
+					dump("wot_surveys.get_wrapper(): can't find FBL wrapper in the document\n");
+				}
+			}
+		} catch (e) {
+			dump("wot_surveys.get_wrapper() failed with " + e + "\n");
+		}
+
+		return wrapper;
+	},
+
+	reveal_form: function (sandbox)
+	{
+		var wrapper = wot_surveys.get_wrapper(sandbox);
+
+		if (wrapper) {
+			var style = wrapper.getAttribute("style") || "";
+			if (style) {
+				style = style.replace(/^(.*visibility: )(hidden;)(.*)$/, "$1visible;$3");   // replace hidden -> visible
+				wrapper.setAttribute("style", style);
+			}
+		}
+	},
+
+	dispatch: function (message, data, sandbox)
+	{
+		switch(message) {
+			case "shown": // FBL was shown
+				wot_surveys.reveal_form(sandbox);   // make iframe visible
+				wot_surveys.save_asked_status(data, wot_surveys.FLAGS.none);
+				break;
+			case "close": // FBL is asking to close it
+				// data.target
+				wot_surveys.save_asked_status(data, wot_surveys.FLAGS.closed);
+				wot_surveys.remove_form(sandbox);
+				break;
+			case "optout": // FBL says the user wants to opt-out from the feedback loop.
+				// data.target
+				wot_surveys.opt_out();  // store setting
+				wot_surveys.save_asked_status(data, wot_surveys.FLAGS.optedout);
+				wot_surveys.remove_form(sandbox);
+				break;
+			case "submit":
+				//	data.target, .url, .question_id, .answer
+				wot_api_feedback.send(data.url, data.question_id, data.answer);
+				wot_surveys.save_asked_status(data, wot_surveys.FLAGS.submited);
+				wot_surveys.remove_form(sandbox, 1500); // wait a bit to show "thank you!"
+				break;
+		}
+	},
+
+	// This is a wrapper around functions that might be called from the injected JS
+	sandboxapi: {
+
+		wot_post: function (sandbox, data_json) {
+			// this func is called from /content/injections/wot_proxy.js : wot.post()
+
+			try {
+				// try un-json data (DON'T call any methods of data_json since it is unsafe!)
+				var data = JSON.parse(data_json);
+
+				dump("wot_surveys.sandpoxapi.wot_post(): " + JSON.stringify(data) + "\n");
+
+				if (data && data.message && data.data) {
+					wot_surveys.dispatch(data.message, data.data, sandbox);
+				}
+			} catch (e) {
+				dump("wot_surveys.sandboxapi.wot_post(): failed with " + e + "\n");
+			}
+
+		}
+
+	}
+
+};
+
+wot_modules.push({ name: "wot_surveys", obj: wot_surveys });
diff --git a/content/ui.js b/content/ui.js
index fc91a43..772d2fb 100644
--- a/content/ui.js
+++ b/content/ui.js
@@ -112,7 +112,11 @@ var wot_status = {
 			if (type == WOT_WARNING_NOTIFICATION || type == WOT_WARNING_DOM) {
 				wot_warning.add(wot_core.hostname, content, type);
 			} else {
-				if(type != WOT_WARNING_BLOCK) wot_warning.hide(content);
+				if(type != WOT_WARNING_BLOCK) {
+					wot_warning.hide(content);
+					// TODO: here we should put a call for FBL showing
+//					wot_surveys.inject();
+				}
 			}
 		} catch (e) {
 			dump("wot_status.update: failed with " + e + "\n");
diff --git a/content/util.js b/content/util.js
index 5b80007..d5a2bf3 100644
--- a/content/util.js
+++ b/content/util.js
@@ -20,6 +20,12 @@
 
 var wot_util =
 {
+	reportError: function(params)
+	{
+		Components.utils.reportError(JSON.stringify(arguments));
+
+	},
+
 	isenabled: function()
 	{
 		try {
@@ -50,6 +56,40 @@ var wot_util =
 		}
 
 		return null;
+	},
+
+	time_sincefirstrun: function()
+	{
+		try {
+			// gives time (in seconds) spent from very first run of the addon.
+			var starttime_str = wot_prefs.getChar("firstrun_time");
+			if (starttime_str) {
+				var starttime = new Date(starttime_str);
+				return (new Date() - starttime) / 1000;    // in seconds;
+			} else {
+				return undefined;
+			}
+		} catch (e) {
+			return undefined;
+		}
+	},
+
+	time_since: function(a, b) {
+		try {
+			if (typeof a === "string") {
+				a = new Date(a);
+			}
+
+			b = b || new Date();
+
+			if (typeof b === "string") {
+				b = new Date(b);
+			}
+
+			return (b - a) / 1000;  // in seconds
+		} catch (e) {
+			return null;
+		}
 	}
 };
 
@@ -438,6 +478,62 @@ var wot_browser =
 		} catch (e) {
 			dump("wot_browser.installsearch: failed with " + e + "\n");
 		}
+	},
+
+	get_document: function (frame)
+	{
+		try {
+			frame = frame || getBrowser();
+			var framed_document = frame.document || frame.contentDocument;
+			return framed_document;
+		} catch (e) {
+			dump("wot_browser.get_document failed with " + e + "\n");
+			return null;
+		}
+	},
+
+	get_or_create_element: function (id, tag, frame)
+	{
+		try {
+			tag = tag || "div";
+			var framed_document = this.get_document(frame);
+
+			var elem = framed_document.getElementById(id);
+
+			if(!elem) {
+				elem = framed_document.createElement(tag);
+				elem.setAttribute("id", id);
+			}
+
+			return elem;
+		} catch (e) {
+			dump("wot_browser.get_or_create_element failed with " + e + "\n");
+			return null;
+		}
+	},
+
+	attach_element: function (element, frame)
+	{
+		try {
+			var framed_document = this.get_document(frame);
+
+			if(framed_document) {
+				var body = framed_document.getElementsByTagName("body");
+
+				if (!element || !body || !body.length) {
+					return false;
+				}
+
+				return body[0].appendChild(element);
+			} else {
+				dump("Can't get document of frame");
+				return false;
+			}
+
+		} catch (e) {
+			dump("wot_browser.attach_element failed with " + e + "\n");
+			return null;
+		}
 	}
 };
 
@@ -930,3 +1026,100 @@ var wot_css =
 		}
 	}
 };
+
+wot_file = {
+
+	wot_dir: "WOT",
+
+	import_libs: function()
+	{
+		Components.utils.import("resource://gre/modules/NetUtil.jsm");
+		Components.utils.import("resource://gre/modules/FileUtils.jsm");
+	},
+
+	read_json: function (filename, callback) {
+
+		try {
+
+			wot_file.import_libs();
+
+			var dir = FileUtils.getDir("ProfD", [wot_file.wot_dir], true); // to make sure the Dir exists
+			var file = FileUtils.getFile("ProfD", [wot_file.wot_dir, filename]);
+
+			NetUtil.asyncFetch(file, function(inputStream, status) {
+
+				if (!Components.isSuccessCode(status)) {
+					// Handle error!
+					callback({});
+					return;
+				}
+
+				try {
+					var data = NetUtil.readInputStreamToString(inputStream, inputStream.available());
+
+					if (data) {
+						var res = JSON.parse(data);
+						if (res instanceof Object) {
+							callback(res);
+						}
+					}
+
+				} catch (e) {
+					dump("utils.wot_file.read_json() is failed with " + e + "\n");
+					callback({});   // anyway, provide empty object
+					return;
+				}
+
+			});
+
+		} catch (e) {
+			dump("wot_file.read_json() failed with " + e + "\n");
+			callback({});   // anyway, provide empty object
+		}
+
+	},
+
+	save_json: function (filename, obj, callback) {
+
+		callback = callback || function(status){};
+
+		try {
+			wot_file.import_libs();
+
+			var dir = FileUtils.getDir("ProfD", [wot_file.wot_dir], true); // to make sure the Dir exists
+			var file = FileUtils.getFile("ProfD", [wot_file.wot_dir, filename]);
+
+			// You can also optionally pass a flags parameter here. It defaults to
+			// FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | FileUtils.MODE_TRUNCATE;
+			var ostream = FileUtils.openSafeFileOutputStream(file);
+
+			var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].
+				createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
+			converter.charset = "UTF-8";
+
+			var data = JSON.stringify(obj);
+			var istream = converter.convertToInputStream(data);
+
+			NetUtil.asyncCopy(istream, ostream, function(status) {
+				if (!Components.isSuccessCode(status)) {
+					// Handle error!
+					callback(false);
+					return;
+				}
+
+				// Data has been written to the file.
+				callback(true);
+			});
+
+		} catch (e) {
+			dump("wot_file.save_json() failed with " + e + "\n");
+			callback(false);   // report about failed attempt to save
+		}
+	},
+
+	remove: function (filename, callback)
+	{
+		// TODO: implement a function which will delete the file (for the case of uninstalling the addon)
+	}
+
+};
diff --git a/extra/feedback/assets/close-button-secondary.png b/extra/feedback/assets/close-button-secondary.png
new file mode 100644
index 0000000..f61b7e2
Binary files /dev/null and b/extra/feedback/assets/close-button-secondary.png differ
diff --git a/extra/feedback/assets/close-button.png b/extra/feedback/assets/close-button.png
new file mode 100644
index 0000000..1f869fa
Binary files /dev/null and b/extra/feedback/assets/close-button.png differ
diff --git a/extra/feedback/assets/logo.png b/extra/feedback/assets/logo.png
new file mode 100644
index 0000000..aba32ed
Binary files /dev/null and b/extra/feedback/assets/logo.png differ
diff --git a/extra/feedback/assets/slide-sep.png b/extra/feedback/assets/slide-sep.png
new file mode 100644
index 0000000..d728fa1
Binary files /dev/null and b/extra/feedback/assets/slide-sep.png differ
diff --git a/extra/feedback/assets/slider-handle.png b/extra/feedback/assets/slider-handle.png
new file mode 100644
index 0000000..0d44f45
Binary files /dev/null and b/extra/feedback/assets/slider-handle.png differ
diff --git a/extra/feedback/assets/submit-button.png b/extra/feedback/assets/submit-button.png
new file mode 100644
index 0000000..c32583d
Binary files /dev/null and b/extra/feedback/assets/submit-button.png differ
diff --git a/extra/feedback/surveys.html b/extra/feedback/surveys.html
new file mode 100644
index 0000000..64515b9
--- /dev/null
+++ b/extra/feedback/surveys.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Web Of Trust Feedback Loop</title>
+        <link rel="stylesheet" href="/feedback/surveys.widgets.css" type="text/css"/>
+    </head>
+    <body>
+        <div class="surveys-window">
+            <div class="top-section">
+
+                <div id="tab-question" class="tabs">
+                    <div class="wot-logo"></div>
+                    <div class="close-button"></div>
+                    <div class="surveys-question"></div>
+                    <div class="surveys-answer">
+                        <div class="surveys-slider">
+                            <div class="surveys-slider-bounds">
+                                <div class="surveys-slider-left-bound"></div>
+                                <div class="surveys-slider-right-bound"></div>
+                            </div>
+                            <div class="ticks-container">
+                                <div class="zero-tick">
+                                    <div class="zero-tick-mark"></div>
+                                </div>
+                            </div>
+                            <div id="slider"></div>
+                        </div>
+                        <div class="surveys-slider-label-wrapper">
+                            <div class="surveys-slider-label"></div>
+                        </div>
+                    </div>
+                    <div class="surveys-submit">Submit</div>
+                    <div class="surveys-optout">
+                        <span class="pseudo-link">Hide forever</span>
+                    </div>
+                    <div class="surveys-whatsthis">
+                        <span class="pseudo-link">What's this?</span>
+                    </div>
+                </div>
+
+            <div id="tab-final" class="tabs">
+                <div class="wot-logo"></div>
+                <div class="thank-you-text">Thank you!</div>
+            </div>
+
+        </div>
+
+            <div class="bottom-section">
+                <div class="close-button-secondary"></div>
+
+                <div id="btab-optout">
+                    <div class="text-optout-confirm">
+                        If you prefer not to be asked questions, click "yes".
+                    </div>
+                    <div class="optout-buttons">
+                        <div class="button-yes">yes</div>
+                        <div class="button-no">No</div>
+                    </div>
+                </div>
+
+                <div id="btab-whatsthis">
+                    <a href="http://www.mywot.com?utm_source=addon&utm_content=whatisthis" target="_blank">Web of Trust (WOT)</a> is working to promote safety and quality across the web for millions of users.
+                        You can help by submitting your opinions whenever you see this prompt.
+                </div>
+
+            </div>
+        </div>
+    </body>
+</html>
diff --git a/extra/feedback/surveys.widgets.css b/extra/feedback/surveys.widgets.css
new file mode 100644
index 0000000..0ba7221
--- /dev/null
+++ b/extra/feedback/surveys.widgets.css
@@ -0,0 +1,390 @@
+body {
+    margin: 0;
+    padding: 0;
+    -webkit-user-select: none;
+}
+
+.surveys-window {
+    position: relative;
+    margin: 10px;
+    border: 1px solid silver;
+    font-family: arial, sans-serif;
+    font-size: 11pt;
+    box-shadow: 0px 0px 10px rgba(27, 27, 27, 0.8);
+}
+
+.top-section {
+    width: 340px;
+    padding: 20px 15px;
+    border-bottom: 1px solid silver;
+    background: #ffffff;
+}
+
+.bottom-section {
+    display: none;
+    padding: 1em 1em 1.2em;
+    background-color: #edf0f5;
+    box-shadow: inset 0 1px 1px rgba(152, 157, 169, 0.7);
+    font-size: 9pt; /* diff than Chrome */
+    color: #656565;
+    position: relative;
+}
+
+/* Top Section */
+
+#tab-question {
+    display: block;
+}
+
+#tab-final {
+    display: none;
+    font-size: 110%;
+}
+
+.wot-logo {
+    position: absolute;
+    top: 10px;
+    left: 10px;
+    width: 42px;
+    height: 17px;
+    background-image: url(/feedback/assets/logo.png);
+    background-repeat: no-repeat;
+}
+
+.close-button {
+    position: absolute;
+    top: 10px;
+    right: 10px;
+    width: 19px;
+    height: 19px;
+    background-image: url(/feedback/assets/close-button.png);
+    background-position: 0 0;
+    cursor: pointer;
+}
+
+.close-button:hover {
+    background-position: 0 -19px;
+}
+
+.surveys-question {
+    margin: 1em 0em 1em 0;
+    font-size: 13pt;
+    text-align: center;
+    -webkit-user-select: text;
+}
+
+.surveys-question .domainname {
+    font-weight: bold;
+}
+
+.surveys-answer {
+    margin: 2.5em auto 0em;
+}
+
+.surveys-slider {
+    position: relative;
+}
+
+.slider-tick {
+    width: 5px;
+    height: 13px;
+    position: relative;
+    top: 0;
+    left: 0;
+    border-right: 1px solid silver;
+    float: left;
+    background-image: -moz-linear-gradient(90deg, #d8dde5 0%, #e4e9f1 38%, #bbc0cb 100%);
+    cursor: pointer;
+}
+
+.slider-tick.selected {
+    background-image: -moz-linear-gradient(90deg, #70a8ba 0%, #a3d7ec 38%, #70a8ba 100%);
+    border-color: #70a8ba;
+}
+
+.slider-tick.hovered {
+    background-image: -moz-linear-gradient(90deg, #70a8ba 0%, #a3d7ec 38%, #70a8ba 100%);
+}
+
+.zero-tick {
+    position: relative;
+    float: left;
+}
+
+.zero-tick-mark {
+    width:4px;
+    height: 4px;
+    margin: 4px auto;
+    border: 1px solid #c6cbd6;
+    border-radius: 5px;
+    background-image: -moz-linear-gradient(90deg, #d8dde5 0%, #c6cbd6 100%);
+}
+
+.zero-tick.selected .zero-tick-mark {
+    background-image: -moz-linear-gradient(90deg, #70a8ba 0%, #a3d7ec 38%, #70a8ba 100%);
+}
+
+.zero-tick.hovered .zero-tick-mark {
+    background-image: -moz-linear-gradient(90deg, #70a8ba 0%, #a3d7ec 38%, #70a8ba 100%);
+}
+
+.slider-tick:nth-child(2) {
+    border-top-left-radius: 8px;
+    border-bottom-left-radius: 8px;
+}
+
+.slider-tick:last-child {
+    border-top-right-radius: 8px;
+    border-bottom-right-radius: 8px;
+}
+
+.ticks-container {
+    /*border: 1px solid silver;*/
+    /*border-radius: 2px;*/
+    position: absolute;
+    height: 13px;
+    margin-bottom: 6px;
+}
+
+#slider {
+    height: 2px;
+    max-width: 80%;
+    top: 18px;
+    margin: auto;
+    border: none;
+    background-color: #c5cbd9;
+    box-shadow: 0 0 2px #7f8693;
+    cursor: pointer;
+}
+
+.surveys-slider-label-wrapper {
+    height: 1.5em;
+    margin: 30px auto auto;
+    text-align: center;
+}
+
+.surveys-slider-label {
+    text-align: center;
+    color: #bebebe;
+    display: inline-block;
+    font-size: 90%;
+}
+
+.surveys-slider-label.selected {
+    color: #444444;
+}
+
+.surveys-slider-label:before,
+.surveys-slider-label:after {
+    position: relative;
+    width: 7px;
+    height: 3px;
+    content: "";
+    display: block;
+    top: 0.7em;
+}
+
+.surveys-slider-label.isset:before,
+.surveys-slider-label.isset:after {
+    background-image: -moz-linear-gradient(90deg, #70a8ba 0%, #a3d7ec 38%, #70a8ba 100%);
+}
+
+.surveys-slider-label:before {
+    padding-left: 10px;
+    left: -1.9em;   /* diff than Chrome */
+}
+
+.surveys-slider-label:after {
+    padding-right: 10px;
+    left: 0.6em; /* diff than Chrome */
+    float: right;
+    top: 0.5em; /* diff than Chrome */
+}
+
+.surveys-submit {
+    width: 101px;
+    height: 30px;
+    margin: 0.5em auto 0.1em;
+    padding-top: 0.6em;
+    padding-bottom: 0.1em;  /* :active shift compensator */
+    text-align: center;
+    background-image: url(/feedback/assets/submit-button.png);
+    background-position: 0 -111px;
+    font-size: 92%;
+    color: #888;
+    text-shadow: 1px 1px 2px silver;
+}
+
+.surveys-submit.enabled {
+    background-position: 0 0px;
+    color: #3E4B3E;
+    cursor: pointer;
+}
+
+.surveys-submit.enabled:hover {
+    background-position: 0 -37px;
+}
+
+.surveys-submit.enabled:active {
+    padding-top: 0.7em;
+    padding-bottom: 0;
+    background-position: 0 -74px;
+}
+
+.surveys-whatsthis,
+.surveys-optout {
+    color: #a7a7a7;
+    font-size: 80%;
+}
+
+.surveys-whatsthis {
+    float: right;
+    top: -1em;
+    position: relative;
+}
+
+.pseudo-link {
+    border-bottom: 1px dashed silver;
+    cursor: pointer;
+}
+
+.pseudo-link:hover {
+    border-bottom: 1px dashed silver;
+    color: #5d5d5d;
+}
+
+/* From jquery.ui.slider.css */
+.ui-slider { position: relative; text-align: left; }
+.ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; }
+.ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; background-position: 0 0; }
+
+.ui-slider-horizontal { height: .8em; }
+.ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; }
+.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; }
+.ui-slider-horizontal .ui-slider-range-min { left: 0; }
+.ui-slider-horizontal .ui-slider-range-max { right: 0; }
+
+/* overriding styles of Jquery UI components */
+.ui-widget-content,
+.ui-state-default,
+.ui-widget-content .ui-state-default {
+    background-image: none;
+    border: none;
+    background-color: transparent;
+}
+
+
+#slider .ui-slider-handle {
+    width: 26px;
+    height: 29px;
+    background-image: url(/feedback/assets/slider-handle.png);
+    background-repeat: no-repeat;
+    top: -11px;
+    margin-left: -13px;
+}
+
+#slider .ui-slider-handle.ui-state-focus {
+    border: none;
+    outline: none;
+}
+
+.surveys-slider-bounds {
+    position: relative;
+}
+
+.surveys-slider-right-bound,
+.surveys-slider-left-bound {
+    position: absolute;
+    top: -1.9em;
+    font-size: 80%;
+    color: gray;
+}
+
+.surveys-slider-left-bound {
+    left: 4px;
+}
+
+.surveys-slider-right-bound {
+    right: 4px;
+}
+
+/* Tab Final */
+
+.thank-you-text {
+    text-align: center;
+}
+
+/* Bottom Section -- */
+
+.close-button-secondary {
+    width: 11px;
+    height: 11px;
+    position: absolute;
+    right: 10px;
+    top: 10px;
+    background-image: url(/feedback/assets/close-button-secondary.png);
+    background-position: 0 0;
+}
+
+.close-button-secondary:hover {
+    background-position: 0 -12px;
+}
+
+/* -- tab OptOut */
+
+#btab-optout {
+    display: none;
+}
+
+.text-optout-confirm {
+    display: inline-block;
+    max-width: 210px;
+    float: left;
+    padding-right: 1em; /* diff than Chrome */
+
+}
+
+.optout-buttons {
+    display: inline-block;
+}
+
+.button-yes,
+.button-no {
+    border: 1px solid silver;
+    display: inline-block;
+    padding: 0.3em 1em;
+    text-align: center;
+    border-radius: 2px;
+    cursor: pointer;
+    box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.47);
+}
+
+.button-yes:hover,
+.button-no:hover {
+    background-image: -moz-linear-gradient(top, #e5fad1 0%, #b2e186 39%, #83bc4a 83%, #83BC4A);
+}
+
+.button-yes {
+    margin-right: 1em;
+    background-color: silver;
+    color: #787f8c;
+    border-color: #b5bcca;
+    background-image: -moz-linear-gradient(top, #edf1f8 0%, #e7ebf5 6%, #d9dee9 48%, #b3b9c7);
+    box-shadow: 0px 1px 1px #787f8c;
+}
+
+.button-no {
+    border-color: #84bf42;
+    background-image: -moz-linear-gradient(top, #ade279 0%, #67a62b);
+}
+
+#btab-whatsthis {
+    display: none;
+    padding-top: 0.5em;
+}
+
+#btab-whatsthis a,
+#btab-whatsthis a:visited,
+#btab-whatsthis a:link {
+    color: #4848F1;
+}
diff --git a/extra/feedback/surveys_test.html b/extra/feedback/surveys_test.html
new file mode 100644
index 0000000..f19d037
--- /dev/null
+++ b/extra/feedback/surveys_test.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>WOT Surveys page test</title>
+</head>
+<body style="background-color: #f5f5f5; background-image: url('http://st.gdefon.ru/wallpapers_original/wallpapers/74533_cvety_foto_oboi_2560x1600_(www.GdeFon.ru).jpg')">
+    <script src="surveys_test.js"></script>
+</body>
+</html>
diff --git a/extra/feedback/surveys_test.js b/extra/feedback/surveys_test.js
new file mode 100644
index 0000000..8684079
--- /dev/null
+++ b/extra/feedback/surveys_test.js
@@ -0,0 +1,34 @@
+
+var question = {
+	target: "test.me.mywot.com",
+	decodedtarget: "test.me.name.mywot.com",
+	question: {
+		id: 9999,
+		text: "Overall, how satisfied are you with %site%?",
+		choices: [
+			{"value":0, "text":"Extremely dissatisfied"},
+			{"value":1, "text":"Moderately dissatisfied"},
+			{"value":2, "text":"Slightly dissatisfied"},
+			{"value":3, "text":"Neither satisfied nor dissatisfied"},
+			{"value":4, "text":"Slightly satisfied"},
+			{"value":5, "text":"Moderately satisfied"},
+			{"value":6, "text":"Extremely satisfied"}
+		]
+	}
+};
+
+var encoded = btoa(JSON.stringify(question));
+
+var iframe = document.createElement("iframe");
+
+iframe.setAttribute("id", "wot_surveys_wrapper");
+iframe.setAttribute("scrolling", "no");
+iframe.setAttribute("style", "position: fixed; top: 10px; left: 10px;width: 392px; height: 350px; z-index: 2147483647; border: none;");
+
+iframe.setAttribute("name", encoded);
+
+iframe.setAttribute("src", "./surveys.html");
+
+var body = document.getElementsByTagName("body")[0];
+
+body.appendChild(iframe);

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-mozext/wot.git



More information about the Pkg-mozext-commits mailing list