").append( jQuery.parseHTML( responseText ) ).find( selector ) :
+
+ // Otherwise use the full result
+ responseText );
+
+ }).complete( callback && function( jqXHR, status ) {
+ self.each( callback, response || [ jqXHR.responseText, status, jqXHR ] );
+ });
+ }
+
+ return this;
+};
+
+// Attach a bunch of functions for handling common AJAX events
+jQuery.each( [ "ajaxStart", "ajaxStop", "ajaxComplete", "ajaxError", "ajaxSuccess", "ajaxSend" ], function( i, type ){
+ jQuery.fn[ type ] = function( fn ){
+ return this.on( type, fn );
+ };
+});
+
+jQuery.extend({
+
+ // Counter for holding the number of active queries
+ active: 0,
+
+ // Last-Modified header cache for next request
+ lastModified: {},
+ etag: {},
+
+ ajaxSettings: {
+ url: ajaxLocation,
+ type: "GET",
+ isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),
+ global: true,
+ processData: true,
+ async: true,
+ contentType: "application/x-www-form-urlencoded; charset=UTF-8",
+ /*
+ timeout: 0,
+ data: null,
+ dataType: null,
+ username: null,
+ password: null,
+ cache: null,
+ throws: false,
+ traditional: false,
+ headers: {},
+ */
+
+ accepts: {
+ "*": allTypes,
+ text: "text/plain",
+ html: "text/html",
+ xml: "application/xml, text/xml",
+ json: "application/json, text/javascript"
+ },
+
+ contents: {
+ xml: /xml/,
+ html: /html/,
+ json: /json/
+ },
+
+ responseFields: {
+ xml: "responseXML",
+ text: "responseText",
+ json: "responseJSON"
+ },
+
+ // Data converters
+ // Keys separate source (or catchall "*") and destination types with a single space
+ converters: {
+
+ // Convert anything to text
+ "* text": String,
+
+ // Text to html (true = no transformation)
+ "text html": true,
+
+ // Evaluate text as a json expression
+ "text json": jQuery.parseJSON,
+
+ // Parse text as xml
+ "text xml": jQuery.parseXML
+ },
+
+ // For options that shouldn't be deep extended:
+ // you can add your own custom options here if
+ // and when you create one that shouldn't be
+ // deep extended (see ajaxExtend)
+ flatOptions: {
+ url: true,
+ context: true
+ }
+ },
+
+ // Creates a full fledged settings object into target
+ // with both ajaxSettings and settings fields.
+ // If target is omitted, writes into ajaxSettings.
+ ajaxSetup: function( target, settings ) {
+ return settings ?
+
+ // Building a settings object
+ ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :
+
+ // Extending ajaxSettings
+ ajaxExtend( jQuery.ajaxSettings, target );
+ },
+
+ ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
+ ajaxTransport: addToPrefiltersOrTransports( transports ),
+
+ // Main method
+ ajax: function( url, options ) {
+
+ // If url is an object, simulate pre-1.5 signature
+ if ( typeof url === "object" ) {
+ options = url;
+ url = undefined;
+ }
+
+ // Force options to be an object
+ options = options || {};
+
+ var transport,
+ // URL without anti-cache param
+ cacheURL,
+ // Response headers
+ responseHeadersString,
+ responseHeaders,
+ // timeout handle
+ timeoutTimer,
+ // Cross-domain detection vars
+ parts,
+ // To know if global events are to be dispatched
+ fireGlobals,
+ // Loop variable
+ i,
+ // Create the final options object
+ s = jQuery.ajaxSetup( {}, options ),
+ // Callbacks context
+ callbackContext = s.context || s,
+ // Context for global events is callbackContext if it is a DOM node or jQuery collection
+ globalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ?
+ jQuery( callbackContext ) :
+ jQuery.event,
+ // Deferreds
+ deferred = jQuery.Deferred(),
+ completeDeferred = jQuery.Callbacks("once memory"),
+ // Status-dependent callbacks
+ statusCode = s.statusCode || {},
+ // Headers (they are sent all at once)
+ requestHeaders = {},
+ requestHeadersNames = {},
+ // The jqXHR state
+ state = 0,
+ // Default abort message
+ strAbort = "canceled",
+ // Fake xhr
+ jqXHR = {
+ readyState: 0,
+
+ // Builds headers hashtable if needed
+ getResponseHeader: function( key ) {
+ var match;
+ if ( state === 2 ) {
+ if ( !responseHeaders ) {
+ responseHeaders = {};
+ while ( (match = rheaders.exec( responseHeadersString )) ) {
+ responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
+ }
+ }
+ match = responseHeaders[ key.toLowerCase() ];
+ }
+ return match == null ? null : match;
+ },
+
+ // Raw string
+ getAllResponseHeaders: function() {
+ return state === 2 ? responseHeadersString : null;
+ },
+
+ // Caches the header
+ setRequestHeader: function( name, value ) {
+ var lname = name.toLowerCase();
+ if ( !state ) {
+ name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
+ requestHeaders[ name ] = value;
+ }
+ return this;
+ },
+
+ // Overrides response content-type header
+ overrideMimeType: function( type ) {
+ if ( !state ) {
+ s.mimeType = type;
+ }
+ return this;
+ },
+
+ // Status-dependent callbacks
+ statusCode: function( map ) {
+ var code;
+ if ( map ) {
+ if ( state < 2 ) {
+ for ( code in map ) {
+ // Lazy-add the new callback in a way that preserves old ones
+ statusCode[ code ] = [ statusCode[ code ], map[ code ] ];
+ }
+ } else {
+ // Execute the appropriate callbacks
+ jqXHR.always( map[ jqXHR.status ] );
+ }
+ }
+ return this;
+ },
+
+ // Cancel the request
+ abort: function( statusText ) {
+ var finalText = statusText || strAbort;
+ if ( transport ) {
+ transport.abort( finalText );
+ }
+ done( 0, finalText );
+ return this;
+ }
+ };
+
+ // Attach deferreds
+ deferred.promise( jqXHR ).complete = completeDeferred.add;
+ jqXHR.success = jqXHR.done;
+ jqXHR.error = jqXHR.fail;
+
+ // Remove hash character (#7531: and string promotion)
+ // Add protocol if not provided (prefilters might expect it)
+ // Handle falsy url in the settings object (#10093: consistency with old signature)
+ // We also use the url parameter if available
+ s.url = ( ( url || s.url || ajaxLocation ) + "" ).replace( rhash, "" )
+ .replace( rprotocol, ajaxLocParts[ 1 ] + "//" );
+
+ // Alias method option to type as per ticket #12004
+ s.type = options.method || options.type || s.method || s.type;
+
+ // Extract dataTypes list
+ s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().match( core_rnotwhite ) || [""];
+
+ // A cross-domain request is in order when we have a protocol:host:port mismatch
+ if ( s.crossDomain == null ) {
+ parts = rurl.exec( s.url.toLowerCase() );
+ s.crossDomain = !!( parts &&
+ ( parts[ 1 ] !== ajaxLocParts[ 1 ] || parts[ 2 ] !== ajaxLocParts[ 2 ] ||
+ ( parts[ 3 ] || ( parts[ 1 ] === "http:" ? "80" : "443" ) ) !==
+ ( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? "80" : "443" ) ) )
+ );
+ }
+
+ // Convert data if not already a string
+ if ( s.data && s.processData && typeof s.data !== "string" ) {
+ s.data = jQuery.param( s.data, s.traditional );
+ }
+
+ // Apply prefilters
+ inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
+
+ // If request was aborted inside a prefilter, stop there
+ if ( state === 2 ) {
+ return jqXHR;
+ }
+
+ // We can fire global events as of now if asked to
+ fireGlobals = s.global;
+
+ // Watch for a new set of requests
+ if ( fireGlobals && jQuery.active++ === 0 ) {
+ jQuery.event.trigger("ajaxStart");
+ }
+
+ // Uppercase the type
+ s.type = s.type.toUpperCase();
+
+ // Determine if request has content
+ s.hasContent = !rnoContent.test( s.type );
+
+ // Save the URL in case we're toying with the If-Modified-Since
+ // and/or If-None-Match header later on
+ cacheURL = s.url;
+
+ // More options handling for requests with no content
+ if ( !s.hasContent ) {
+
+ // If data is available, append data to url
+ if ( s.data ) {
+ cacheURL = ( s.url += ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + s.data );
+ // #9682: remove data so that it's not used in an eventual retry
+ delete s.data;
+ }
+
+ // Add anti-cache in url if needed
+ if ( s.cache === false ) {
+ s.url = rts.test( cacheURL ) ?
+
+ // If there is already a '_' parameter, set its value
+ cacheURL.replace( rts, "$1_=" + ajax_nonce++ ) :
+
+ // Otherwise add one to the end
+ cacheURL + ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ajax_nonce++;
+ }
+ }
+
+ // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+ if ( s.ifModified ) {
+ if ( jQuery.lastModified[ cacheURL ] ) {
+ jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] );
+ }
+ if ( jQuery.etag[ cacheURL ] ) {
+ jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] );
+ }
+ }
+
+ // Set the correct header, if data is being sent
+ if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
+ jqXHR.setRequestHeader( "Content-Type", s.contentType );
+ }
+
+ // Set the Accepts header for the server, depending on the dataType
+ jqXHR.setRequestHeader(
+ "Accept",
+ s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
+ s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
+ s.accepts[ "*" ]
+ );
+
+ // Check for headers option
+ for ( i in s.headers ) {
+ jqXHR.setRequestHeader( i, s.headers[ i ] );
+ }
+
+ // Allow custom headers/mimetypes and early abort
+ if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
+ // Abort if not done already and return
+ return jqXHR.abort();
+ }
+
+ // aborting is no longer a cancellation
+ strAbort = "abort";
+
+ // Install callbacks on deferreds
+ for ( i in { success: 1, error: 1, complete: 1 } ) {
+ jqXHR[ i ]( s[ i ] );
+ }
+
+ // Get transport
+ transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
+
+ // If no transport, we auto-abort
+ if ( !transport ) {
+ done( -1, "No Transport" );
+ } else {
+ jqXHR.readyState = 1;
+
+ // Send global event
+ if ( fireGlobals ) {
+ globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
+ }
+ // Timeout
+ if ( s.async && s.timeout > 0 ) {
+ timeoutTimer = setTimeout(function() {
+ jqXHR.abort("timeout");
+ }, s.timeout );
+ }
+
+ try {
+ state = 1;
+ transport.send( requestHeaders, done );
+ } catch ( e ) {
+ // Propagate exception as error if not done
+ if ( state < 2 ) {
+ done( -1, e );
+ // Simply rethrow otherwise
+ } else {
+ throw e;
+ }
+ }
+ }
+
+ // Callback for when everything is done
+ function done( status, nativeStatusText, responses, headers ) {
+ var isSuccess, success, error, response, modified,
+ statusText = nativeStatusText;
+
+ // Called once
+ if ( state === 2 ) {
+ return;
+ }
+
+ // State is "done" now
+ state = 2;
+
+ // Clear timeout if it exists
+ if ( timeoutTimer ) {
+ clearTimeout( timeoutTimer );
+ }
+
+ // Dereference transport for early garbage collection
+ // (no matter how long the jqXHR object will be used)
+ transport = undefined;
+
+ // Cache response headers
+ responseHeadersString = headers || "";
+
+ // Set readyState
+ jqXHR.readyState = status > 0 ? 4 : 0;
+
+ // Determine if successful
+ isSuccess = status >= 200 && status < 300 || status === 304;
+
+ // Get response data
+ if ( responses ) {
+ response = ajaxHandleResponses( s, jqXHR, responses );
+ }
+
+ // Convert no matter what (that way responseXXX fields are always set)
+ response = ajaxConvert( s, response, jqXHR, isSuccess );
+
+ // If successful, handle type chaining
+ if ( isSuccess ) {
+
+ // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+ if ( s.ifModified ) {
+ modified = jqXHR.getResponseHeader("Last-Modified");
+ if ( modified ) {
+ jQuery.lastModified[ cacheURL ] = modified;
+ }
+ modified = jqXHR.getResponseHeader("etag");
+ if ( modified ) {
+ jQuery.etag[ cacheURL ] = modified;
+ }
+ }
+
+ // if no content
+ if ( status === 204 ) {
+ statusText = "nocontent";
+
+ // if not modified
+ } else if ( status === 304 ) {
+ statusText = "notmodified";
+
+ // If we have data, let's convert it
+ } else {
+ statusText = response.state;
+ success = response.data;
+ error = response.error;
+ isSuccess = !error;
+ }
+ } else {
+ // We extract error from statusText
+ // then normalize statusText and status for non-aborts
+ error = statusText;
+ if ( status || !statusText ) {
+ statusText = "error";
+ if ( status < 0 ) {
+ status = 0;
+ }
+ }
+ }
+
+ // Set data for the fake xhr object
+ jqXHR.status = status;
+ jqXHR.statusText = ( nativeStatusText || statusText ) + "";
+
+ // Success/Error
+ if ( isSuccess ) {
+ deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
+ } else {
+ deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
+ }
+
+ // Status-dependent callbacks
+ jqXHR.statusCode( statusCode );
+ statusCode = undefined;
+
+ if ( fireGlobals ) {
+ globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
+ [ jqXHR, s, isSuccess ? success : error ] );
+ }
+
+ // Complete
+ completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
+
+ if ( fireGlobals ) {
+ globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
+ // Handle the global AJAX counter
+ if ( !( --jQuery.active ) ) {
+ jQuery.event.trigger("ajaxStop");
+ }
+ }
+ }
+
+ return jqXHR;
+ },
+
+ getJSON: function( url, data, callback ) {
+ return jQuery.get( url, data, callback, "json" );
+ },
+
+ getScript: function( url, callback ) {
+ return jQuery.get( url, undefined, callback, "script" );
+ }
+});
+
+jQuery.each( [ "get", "post" ], function( i, method ) {
+ jQuery[ method ] = function( url, data, callback, type ) {
+ // shift arguments if data argument was omitted
+ if ( jQuery.isFunction( data ) ) {
+ type = type || callback;
+ callback = data;
+ data = undefined;
+ }
+
+ return jQuery.ajax({
+ url: url,
+ type: method,
+ dataType: type,
+ data: data,
+ success: callback
+ });
+ };
+});
+
+/* Handles responses to an ajax request:
+ * - finds the right dataType (mediates between content-type and expected dataType)
+ * - returns the corresponding response
+ */
+function ajaxHandleResponses( s, jqXHR, responses ) {
+
+ var ct, type, finalDataType, firstDataType,
+ contents = s.contents,
+ dataTypes = s.dataTypes;
+
+ // Remove auto dataType and get content-type in the process
+ while( dataTypes[ 0 ] === "*" ) {
+ dataTypes.shift();
+ if ( ct === undefined ) {
+ ct = s.mimeType || jqXHR.getResponseHeader("Content-Type");
+ }
+ }
+
+ // Check if we're dealing with a known content-type
+ if ( ct ) {
+ for ( type in contents ) {
+ if ( contents[ type ] && contents[ type ].test( ct ) ) {
+ dataTypes.unshift( type );
+ break;
+ }
+ }
+ }
+
+ // Check to see if we have a response for the expected dataType
+ if ( dataTypes[ 0 ] in responses ) {
+ finalDataType = dataTypes[ 0 ];
+ } else {
+ // Try convertible dataTypes
+ for ( type in responses ) {
+ if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) {
+ finalDataType = type;
+ break;
+ }
+ if ( !firstDataType ) {
+ firstDataType = type;
+ }
+ }
+ // Or just use first one
+ finalDataType = finalDataType || firstDataType;
+ }
+
+ // If we found a dataType
+ // We add the dataType to the list if needed
+ // and return the corresponding response
+ if ( finalDataType ) {
+ if ( finalDataType !== dataTypes[ 0 ] ) {
+ dataTypes.unshift( finalDataType );
+ }
+ return responses[ finalDataType ];
+ }
+}
+
+/* Chain conversions given the request and the original response
+ * Also sets the responseXXX fields on the jqXHR instance
+ */
+function ajaxConvert( s, response, jqXHR, isSuccess ) {
+ var conv2, current, conv, tmp, prev,
+ converters = {},
+ // Work with a copy of dataTypes in case we need to modify it for conversion
+ dataTypes = s.dataTypes.slice();
+
+ // Create converters map with lowercased keys
+ if ( dataTypes[ 1 ] ) {
+ for ( conv in s.converters ) {
+ converters[ conv.toLowerCase() ] = s.converters[ conv ];
+ }
+ }
+
+ current = dataTypes.shift();
+
+ // Convert to each sequential dataType
+ while ( current ) {
+
+ if ( s.responseFields[ current ] ) {
+ jqXHR[ s.responseFields[ current ] ] = response;
+ }
+
+ // Apply the dataFilter if provided
+ if ( !prev && isSuccess && s.dataFilter ) {
+ response = s.dataFilter( response, s.dataType );
+ }
+
+ prev = current;
+ current = dataTypes.shift();
+
+ if ( current ) {
+
+ // There's only work to do if current dataType is non-auto
+ if ( current === "*" ) {
+
+ current = prev;
+
+ // Convert response if prev dataType is non-auto and differs from current
+ } else if ( prev !== "*" && prev !== current ) {
+
+ // Seek a direct converter
+ conv = converters[ prev + " " + current ] || converters[ "* " + current ];
+
+ // If none found, seek a pair
+ if ( !conv ) {
+ for ( conv2 in converters ) {
+
+ // If conv2 outputs current
+ tmp = conv2.split( " " );
+ if ( tmp[ 1 ] === current ) {
+
+ // If prev can be converted to accepted input
+ conv = converters[ prev + " " + tmp[ 0 ] ] ||
+ converters[ "* " + tmp[ 0 ] ];
+ if ( conv ) {
+ // Condense equivalence converters
+ if ( conv === true ) {
+ conv = converters[ conv2 ];
+
+ // Otherwise, insert the intermediate dataType
+ } else if ( converters[ conv2 ] !== true ) {
+ current = tmp[ 0 ];
+ dataTypes.unshift( tmp[ 1 ] );
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ // Apply converter (if not an equivalence)
+ if ( conv !== true ) {
+
+ // Unless errors are allowed to bubble, catch and return them
+ if ( conv && s[ "throws" ] ) {
+ response = conv( response );
+ } else {
+ try {
+ response = conv( response );
+ } catch ( e ) {
+ return { state: "parsererror", error: conv ? e : "No conversion from " + prev + " to " + current };
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return { state: "success", data: response };
+}
+// Install script dataType
+jQuery.ajaxSetup({
+ accepts: {
+ script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
+ },
+ contents: {
+ script: /(?:java|ecma)script/
+ },
+ converters: {
+ "text script": function( text ) {
+ jQuery.globalEval( text );
+ return text;
+ }
+ }
+});
+
+// Handle cache's special case and crossDomain
+jQuery.ajaxPrefilter( "script", function( s ) {
+ if ( s.cache === undefined ) {
+ s.cache = false;
+ }
+ if ( s.crossDomain ) {
+ s.type = "GET";
+ }
+});
+
+// Bind script tag hack transport
+jQuery.ajaxTransport( "script", function( s ) {
+ // This transport only deals with cross domain requests
+ if ( s.crossDomain ) {
+ var script, callback;
+ return {
+ send: function( _, complete ) {
+ script = jQuery("
+
+
+
+
+
+
+
+
+
+
diff --git a/ext/src/inject/webcode/scripts/canvas.js b/ext/src/inject/webcode/scripts/canvas.js
new file mode 100644
index 0000000..1262825
--- /dev/null
+++ b/ext/src/inject/webcode/scripts/canvas.js
@@ -0,0 +1,433 @@
+function oval(context, x, y, w, h)
+{
+ context.save();
+ context.beginPath();
+ context.translate(x, y);
+ context.scale(w/2, h/2);
+ context.arc(1, 1, 1, 0, 2*Math.PI, false);
+ context.closePath();
+ context.restore();
+}
+
+function arc(context, x, y, w, h, startAngle, endAngle, isClosed)
+{
+ context.save();
+ context.beginPath();
+ context.translate(x, y);
+ context.scale(w/2, h/2);
+ context.arc(1, 1, 1, Math.PI/180*startAngle, Math.PI/180*endAngle, false);
+ if (isClosed)
+ {
+ context.lineTo(1, 1);
+ context.closePath();
+ }
+ context.restore();
+}
+
+function makeRect(x, y, w, h)
+{
+ return { x: x, y: y, w: w, h: h };
+}
+
+function toRadians (angle) {
+ return angle * (Math.PI / 180);
+}
+
+function dot(context, degrees, radius, w, h){
+ context.save();
+ context.beginPath();
+
+ var x = radius*Math.cos(toRadians(degrees+180));
+ var y = radius*Math.sin(toRadians(degrees+180));
+ //console.log({x:x,y:y, degrees: degrees, radius:radius});
+
+ context.translate(x, y);
+ context.scale(w/2, h/2);
+ context.arc(1, 1, 1, 0, 2*Math.PI, false);
+ context.closePath();
+ context.restore();
+}
+
+function drawCanvas(canvasId, input)
+{
+ var fairprice = input.kbb.data.values.privatepartyfair.price;
+ var goodprice = input.kbb.data.values.privatepartygood.price;
+ var verygoodprice = input.kbb.data.values.privatepartyverygood.price;
+ var excellentprice = input.kbb.data.values.privatepartyexcellent.price;
+ var scaleLow = Math.floor(input.kbb.data.scale.scaleLow * .85);
+ var scaleHigh = Math.floor(input.kbb.data.scale.scaleHigh);
+ var listPrice = input.listPrice;
+
+ var kbbStartAngle = (((fairprice-scaleLow)/(scaleHigh-scaleLow))*(360-180))+180;
+ var kbbEndAngle = (((excellentprice-scaleLow)/(scaleHigh-scaleLow))*(360-180))+180;
+ kbbStartAngle = 225;
+ kbbEndAngle = 315;
+ //// General Declarations
+ var canvas = document.getElementById(canvasId);
+ var context = canvas.getContext('2d');
+
+ var currentX = 129.5;
+ var currentY = 54.5;
+
+ //// Color Declarations
+ var currentPriceColor = 'rgba(29, 52, 255, 1)';
+ var blackColor = 'rgba(0, 0, 0, 1)';
+ var whiteColor = 'rgba(255, 255, 255, 1)';
+ var grey = 'rgba(237, 238, 237, 1)';
+ var good = 'rgba(101, 160, 89, 0.86)';
+ var goodPriceColor = 'rgba(27, 160, 0, 0.86)';
+ var bad = 'rgba(195, 24, 21, 1)';
+ var color = 'rgba(255, 255, 255, 1)';
+
+ //// Shadow Declarations
+ function shadow(context)
+ {
+ context.shadowOffsetX = 0;
+ context.shadowOffsetY = 0;
+ context.shadowBlur = 5;
+ context.shadowColor = blackColor;
+ }
+ function shadow3(context)
+ {
+ context.shadowOffsetX = 0;
+ context.shadowOffsetY = 0;
+ context.shadowBlur = 0.5;
+ context.shadowColor = color;
+ }
+
+ //// Image Declarations
+ var logo240 = new Image();
+ logo240.src = chrome.extension.getURL('/src/inject/webcode/images/logo240.png');
+
+ //// Abstracted Attributes
+ var redSemiCircleStartAngle = 315;
+ var greySemiCircleEndAngle = 315;
+ var greenSemiCircleStartAngle = 225;
+ var greenSemiCircleEndAngle = 315;
+ var privatePartyRangeContent = 'PRIVATE PARTY RANGE';
+ var excellentPriceRect = makeRect(188, 70, 44, 17);
+ var veryGoodPriceRect = makeRect(146, 46, 48, 17);
+ var currentPriceRect = makeRect(111, 37, 48, 17);
+ var goodPriceRect = makeRect(67, 44, 48, 17);
+ var fairPriceRect = makeRect(27, 68, 48, 17);
+
+ var redSemiCircleStartAngle = kbbEndAngle;
+ var greySemiCircleEndAngle = redSemiCircleStartAngle;
+ var greenSemiCircleStartAngle = kbbStartAngle;
+ var greenSemiCircleEndAngle = redSemiCircleStartAngle;
+
+ var minPriceContent = scaleLow.toMoney();
+ var maxPriceContent = scaleHigh.toMoney();
+ var excellentPriceContent = excellentprice.toMoney();
+ var fairPriceContent = fairprice.toMoney();
+ var goodPriceContent = goodprice.toMoney();
+ var veryGoodPriceContent = verygoodprice.toMoney();
+ var currentPriceContent = listPrice.toMoney();
+
+
+ if(listPrice < fairprice)
+ {
+ currentX = 28;
+ currentY = 130;
+ currentPriceColor = goodPriceColor;
+ currentPriceRect = makeRect(6, 110, 48, 17);
+ }
+ else if(listPrice > excellentprice)
+ {
+ currentX = 220;
+ currentY = 130;
+ currentPriceColor = bad;
+ currentPriceRect = makeRect(220, 110, 48, 17);
+ }
+
+
+ //// Red Semi Circle Drawing
+ arc(context, 29, 71, 200, 200, redSemiCircleStartAngle, 0, true);
+ context.save();
+ shadow(context);
+ context.fillStyle = bad;
+ context.fill();
+
+ ////// Red Semi Circle Inner Shadow
+ context.save();
+ context.clip();
+ context.moveTo(-10000, -10000);
+ context.lineTo(-10000, 10000);
+ context.lineTo(10001, 10000);
+ context.lineTo(10000, -10000);
+ context.closePath();
+ shadow3(context);
+ context.fillStyle = 'grey';
+ context.fill();
+ context.restore();
+ context.restore();
+
+ context.strokeStyle = bad;
+ context.lineWidth = 0;
+ context.stroke();
+
+
+ //// Grey Semi Circle Drawing
+ arc(context, 29, 71, 200, 200, 180, greySemiCircleEndAngle, true);
+ context.save();
+ shadow(context);
+ context.fillStyle = grey;
+ context.fill();
+
+ ////// Grey Semi Circle Inner Shadow
+ context.save();
+ context.clip();
+ context.moveTo(-10000, -10000);
+ context.lineTo(-10000, 10000);
+ context.lineTo(10001, 10000);
+ context.lineTo(10000, -10000);
+ context.closePath();
+ shadow3(context);
+ context.fillStyle = 'grey';
+ context.fill();
+ context.restore();
+ context.restore();
+
+ context.strokeStyle = grey;
+ context.lineWidth = 0;
+ context.stroke();
+
+
+ //// Green SemiCircle Drawing
+ arc(context, 19, 61, 220, 220, greenSemiCircleStartAngle, greenSemiCircleEndAngle, true);
+ context.save();
+ shadow(context);
+ context.fillStyle = good;
+ context.fill();
+
+ ////// Green SemiCircle Inner Shadow
+ context.save();
+ context.clip();
+ context.moveTo(-10000, -10000);
+ context.lineTo(-10000, 10000);
+ context.lineTo(10001, 10000);
+ context.lineTo(10000, -10000);
+ context.closePath();
+ shadow3(context);
+ context.fillStyle = 'grey';
+ context.fill();
+ context.restore();
+ context.restore();
+
+ context.strokeStyle = good;
+ context.lineWidth = 0.5;
+ context.stroke();
+
+
+ //// Good Dot Drawing
+ oval(context, 85.5, 62.5, 10, 10);
+ context.save();
+ shadow(context);
+ context.fillStyle = good;
+ context.fill();
+
+ ////// Good Dot Inner Shadow
+ context.save();
+ context.clip();
+ context.moveTo(-10000, -10000);
+ context.lineTo(-10000, 10000);
+ context.lineTo(10001, 10000);
+ context.lineTo(10000, -10000);
+ context.closePath();
+ shadow3(context);
+ context.fillStyle = 'grey';
+ context.fill();
+ context.restore();
+ context.restore();
+
+ context.save();
+ shadow3(context);
+ context.strokeStyle = grey;
+ context.lineWidth = 3;
+ context.stroke();
+ context.restore();
+
+
+ //// Fair Dot Drawing
+ oval(context, 46.5, 87.5, 10, 10);
+ context.save();
+ shadow(context);
+ context.fillStyle = good;
+ context.fill();
+
+ ////// Fair Dot Inner Shadow
+ context.save();
+ context.clip();
+ context.moveTo(-10000, -10000);
+ context.lineTo(-10000, 10000);
+ context.lineTo(10001, 10000);
+ context.lineTo(10000, -10000);
+ context.closePath();
+ shadow3(context);
+ context.fillStyle = 'grey';
+ context.fill();
+ context.restore();
+ context.restore();
+
+ context.save();
+ shadow3(context);
+ context.strokeStyle = grey;
+ context.lineWidth = 3;
+ context.stroke();
+ context.restore();
+
+
+ //// Very Good Dot Drawing
+ oval(context, 164.5, 63.5, 10, 10);
+ context.save();
+ shadow(context);
+ context.fillStyle = good;
+ context.fill();
+
+ ////// Very Good Dot Inner Shadow
+ context.save();
+ context.clip();
+ context.moveTo(-10000, -10000);
+ context.lineTo(-10000, 10000);
+ context.lineTo(10001, 10000);
+ context.lineTo(10000, -10000);
+ context.closePath();
+ shadow3(context);
+ context.fillStyle = 'grey';
+ context.fill();
+ context.restore();
+ context.restore();
+
+ context.save();
+ shadow3(context);
+ context.strokeStyle = grey;
+ context.lineWidth = 3;
+ context.stroke();
+ context.restore();
+
+
+ //// Excellent Dot Drawing
+ oval(context, 204.5, 87.5, 10, 10);
+ context.save();
+ shadow(context);
+ context.fillStyle = good;
+ context.fill();
+
+ ////// Excellent Dot Inner Shadow
+ context.save();
+ context.clip();
+ context.moveTo(-10000, -10000);
+ context.lineTo(-10000, 10000);
+ context.lineTo(10001, 10000);
+ context.lineTo(10000, -10000);
+ context.closePath();
+ shadow3(context);
+ context.fillStyle = 'grey';
+ context.fill();
+ context.restore();
+ context.restore();
+
+ context.save();
+ shadow3(context);
+ context.strokeStyle = grey;
+ context.lineWidth = 3;
+ context.stroke();
+ context.restore();
+
+
+ //// Private Party Range Drawing
+ var privatePartyRangeRect = makeRect(79, 100, 94, 10);
+ context.fillStyle = whiteColor;
+ context.font = '9px Tahoma, Verdana, Segoe, sans-serif';
+ context.textAlign = 'center';
+ context.fillText(privatePartyRangeContent, privatePartyRangeRect.x + privatePartyRangeRect.w/2, privatePartyRangeRect.y + 9);
+
+
+ //// kbblogo Drawing
+ context.beginPath();
+ context.rect(79, 120, 100, 100);
+ context.save();
+ context.drawImage(logo240, 79, 120, logo240.width, logo240.height);
+ context.clip();
+ context.restore();
+
+
+ //// Min Price Drawing
+ var minPriceRect = makeRect(29, 172, 44, 17);
+ context.fillStyle = blackColor;
+ context.font = '12px Tahoma, Verdana, Segoe, sans-serif';
+ context.textAlign = 'left';
+ context.fillText(minPriceContent, minPriceRect.x, minPriceRect.y + 12);
+
+
+ //// Max Price Drawing
+ var maxPriceRect = makeRect(171, 172, 60, 17);
+ context.fillStyle = blackColor;
+ context.font = '12px Tahoma, Verdana, Segoe, sans-serif';
+ context.textAlign = 'right';
+ context.fillText(maxPriceContent, maxPriceRect.x + maxPriceRect.w, maxPriceRect.y + 12);
+
+
+ //// Excellent Price Drawing
+ context.fillStyle = goodPriceColor;
+ context.font = '12px Tahoma, Verdana, Segoe, sans-serif';
+ context.textAlign = 'center';
+ context.fillText(excellentPriceContent, excellentPriceRect.x + excellentPriceRect.w/2, excellentPriceRect.y + 12);
+
+
+ //// Fair Price Drawing
+ context.fillStyle = goodPriceColor;
+ context.font = '12px Tahoma, Verdana, Segoe, sans-serif';
+ context.textAlign = 'center';
+ context.fillText(fairPriceContent, fairPriceRect.x + fairPriceRect.w/2, fairPriceRect.y + 12);
+
+
+ //// Good Price Drawing
+ context.fillStyle = goodPriceColor;
+ context.font = '12px Tahoma, Verdana, Segoe, sans-serif';
+ context.textAlign = 'center';
+ context.fillText(goodPriceContent, goodPriceRect.x + goodPriceRect.w/2, goodPriceRect.y + 12);
+
+
+ //// Very Good Price Drawing
+ context.fillStyle = goodPriceColor;
+ context.font = '12px Tahoma, Verdana, Segoe, sans-serif';
+ context.textAlign = 'center';
+ context.fillText(veryGoodPriceContent, veryGoodPriceRect.x + veryGoodPriceRect.w/2, veryGoodPriceRect.y + 12);
+
+
+ //// Current Dot Drawing
+ oval(context, currentX, currentY, 10, 10);
+ context.save();
+ shadow(context);
+ context.fillStyle = currentPriceColor;
+ context.fill();
+
+ ////// Current Dot Inner Shadow
+ context.save();
+ context.clip();
+ context.moveTo(-10000, -10000);
+ context.lineTo(-10000, 10000);
+ context.lineTo(10001, 10000);
+ context.lineTo(10000, -10000);
+ context.closePath();
+ shadow3(context);
+ context.fillStyle = 'grey';
+ context.fill();
+ context.restore();
+ context.restore();
+
+ context.save();
+ shadow3(context);
+ context.strokeStyle = grey;
+ context.lineWidth = 3;
+ context.stroke();
+ context.restore();
+
+
+ //// Current Price Drawing
+ context.fillStyle = currentPriceColor;
+ context.font = '12px Tahoma, Verdana, Segoe, sans-serif';
+ context.textAlign = 'center';
+ context.fillText(currentPriceContent, currentPriceRect.x + currentPriceRect.w/2, currentPriceRect.y + 12);
+}
diff --git a/ext/src/inject/webcode/scripts/canvas2.js b/ext/src/inject/webcode/scripts/canvas2.js
new file mode 100644
index 0000000..4b8a4d4
--- /dev/null
+++ b/ext/src/inject/webcode/scripts/canvas2.js
@@ -0,0 +1,44 @@
+function toRadians (angle) {
+ return angle * (Math.PI / 180);
+}
+
+function dot(context, degrees, radius, w, h){
+ context.save();
+ context.beginPath();
+
+ var x = radius*Math.cos(toRadians(degrees+180));
+ var y = radius*Math.sin(toRadians(degrees+180));
+ //console.log({x:x,y:y, degrees: degrees, radius:radius});
+
+ context.translate(x, y);
+ context.scale(w/2, h/2);
+ context.arc(1, 1, 1, 0, 2*Math.PI, false);
+ context.closePath();
+ context.restore();
+}
+
+function drawCanvas(canvasId, input)
+{
+
+ var fairprice = input.kbb.data.values.privatepartyfair.price;
+ var goodprice = input.kbb.data.values.privatepartygood.price;
+ var verygoodprice = input.kbb.data.values.privatepartyverygood.price;
+ var excellentprice = input.kbb.data.values.privatepartyexcellent.price;
+ var scaleLow = Math.floor(input.kbb.data.scale.scaleLow * .85);
+ var scaleHigh = Math.floor(input.kbb.data.scale.scaleHigh);
+
+ var kbbStartAngle = (((fairprice-scaleLow)/(scaleHigh-scaleLow))*(360-180))+180;
+ var kbbEndAngle = (((excellentprice-scaleLow)/(scaleHigh-scaleLow))*(360-180))+180;
+
+ var redSemiCircleStartAngle = kbbEndAngle;
+ var greySemiCircleEndAngle = redSemiCircleStartAngle;
+ var greenSemiCircleStartAngle = kbbStartAngle;
+ var greenSemiCircleEndAngle = redSemiCircleStartAngle;
+
+ var minPriceContent = "$" + scaleLow;
+ var maxPriceContent = '$'+ scaleHigh;
+ var excellentPriceContent = '$' + excellentprice;
+ var fairPriceContent = '$'+fairprice;
+ var goodPriceContent = '$'+ goodprice;
+ var veryGoodPriceContent = '$'+verygoodprice;
+}
\ No newline at end of file
diff --git a/ext/src/inject/webcode/svg/images/logo240.png b/ext/src/inject/webcode/svg/images/logo240.png
new file mode 100644
index 0000000..7baf676
Binary files /dev/null and b/ext/src/inject/webcode/svg/images/logo240.png differ
diff --git a/ext/src/inject/webcode/svg/images/logo240_2x.png b/ext/src/inject/webcode/svg/images/logo240_2x.png
new file mode 100644
index 0000000..eca40c5
Binary files /dev/null and b/ext/src/inject/webcode/svg/images/logo240_2x.png differ
diff --git a/ext/src/inject/webcode/svg/kbb.svg b/ext/src/inject/webcode/svg/kbb.svg
new file mode 100644
index 0000000..1a445c0
--- /dev/null
+++ b/ext/src/inject/webcode/svg/kbb.svg
@@ -0,0 +1,110 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PRIVATE PARTY RANGE
+
+
+
+
+ $0
+
+
+
+ $200,000
+
+
+
+ $10,000
+
+
+
+ $1,000
+
+
+
+ $1,000
+
+
+
+ $1,000
+
+
+
+
+
+
+
+ $1,000
+
+
diff --git a/ext/src/options_custom/README.md b/ext/src/options_custom/README.md
new file mode 100755
index 0000000..a3f3d14
--- /dev/null
+++ b/ext/src/options_custom/README.md
@@ -0,0 +1,114 @@
+# [Fancy Settings 1.2](https://github.com/frankkohlhepp/fancy-settings)
+*Create fancy, chrome-look-alike settings for your Chrome or Safari extension in minutes!*
+
+### Howto
+Welcome to Fancy Settings! Are you ready for tabs, groups, search, good style?
+Let's get started, it only takes a few minutes...
+
+Settings can be of different types: text input, checkbox, slider, etc. Some "settings" are not actual settings but provide functionality that is relevant to the options page: description (which is simply a block of text), button.
+
+Settings are defined in the manifest.js file as JavaScript objects. Each setting is defined by specifying a number of parameters. All types of settings are configured with the string parameters tab, group, name and type.
+
+###Basic example:
+```javascript
+{
+ "tab": "Tab 1",
+ "group": "Group 1",
+ "name": "checkbox1",
+ "type": "checkbox"
+}
+```
+
+"name" is used as a part of the key when storing the setting's value in localStorage.
+If it's missing, nothing will be saved.
+
+###Additionally, all types of settings are configured with their own custom parameters:
+
+###Description ("type": "description")
+
+text (string) the block of text, which can include HTML tags. You can continue multiple lines of text by putting a \ at the end of a line, just as with any JavaScript file.
+
+####
+Button ("type": "button")
+```
+ Label (string) text shown in front of the button
+
+ Text (string) text shown on the button
+```
+
+####Text ("type": "text")
+```
+ label (string) text shown in front of the text field
+
+ text (string) text shown in the text field when empty
+
+ masked (boolean) indicates a password field
+```
+
+####Checkbox ("type": "checkbox")
+```
+ label (string) text shown behind the checkbox
+```
+
+####Slider ("type": "slider")
+```
+ label (string) text shown in front of the slider
+
+ max (number) maximal value of the slider
+
+ min (number) minimal value of the slider
+
+ step (number) steps between two values
+
+ display (boolean) indicates whether to show the slider display
+
+ displayModifier (function) a function to modify the value shown in the display
+```
+
+####PopupButton ("type": "popupButton"), ListBox ("type": "listBox") & RadioButtons ("type": "radioButtons")
+```
+label (string) text shown in front of the options
+
+ options (array of options)
+
+ where an option can be one of the following formats:
+```
+
+####"value"
+```
+["value", "displayed text"]
+
+{value: "value", text: "displayed text"}
+```
+The "displayed text" field is optional and is displayed to the user when you don't want to display the internal value directly to the user.
+
+#### You can also group options so that the user can easily choose among them (groups may only be applied to popupButtons):
+
+```javascript
+ "options": {
+ "groups": [
+ "Hot", "Cold",
+ ],
+ "values": [
+ {
+ "value": "hot",
+ "text": "Very hot",
+ "group": "Hot",
+ },
+ {
+ "value": "Medium",
+ "group": 1,
+ },
+ {
+ "value": "Cold",
+ "group": 2,
+ },
+ ["Non-existing"]
+ ],
+ },
+
+```
+
+### License
+Fancy Settings is licensed under the **LGPL 2.1**.
+For details see *LICENSE.txt*
\ No newline at end of file
diff --git a/ext/src/options_custom/css/main.css b/ext/src/options_custom/css/main.css
new file mode 100755
index 0000000..5232eb7
--- /dev/null
+++ b/ext/src/options_custom/css/main.css
@@ -0,0 +1,132 @@
+/*
+// Copyright (c) 2011 Frank Kohlhepp
+// https://github.com/frankkohlhepp/fancy-settings
+// License: LGPL v2.1
+*/
+.fancy {
+ text-shadow: #F5F5F5 0 1px 0;
+}
+
+#sidebar {
+ position: absolute;
+ background-color: #EDEDED;
+ background-image: linear-gradient(top, #EDEDED, #F5F5F5);
+ background-image: -webkit-gradient(
+ linear,
+ left top,
+ left 500,
+ color-stop(0, #EDEDED),
+ color-stop(1, #F5F5F5)
+ );
+ background-image: -moz-linear-gradient(
+ center top,
+ #EDEDED 0%,
+ #F5F5F5 100%
+ );
+ background-image: -o-linear-gradient(top, #EDEDED, #F5F5F5);
+ width: 219px;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ border-right: 1px solid #C2C2C2;
+ box-shadow: inset -8px 0 30px -30px black;
+}
+
+#icon {
+ position: absolute;
+ width: 30px;
+ height: 30px;
+ top: 12px;
+ left: 12px;
+}
+
+#sidebar h1 {
+ position: absolute;
+ top: 13px;
+ right: 25px;
+ font-size: 26px;
+ color: #707070;
+}
+
+#tab-container {
+ position: absolute;
+ top: 50px;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ overflow-y: auto;
+ overflow-x: hidden;
+ text-align: right;
+}
+
+#tab-container .tab {
+ height: 28px;
+ padding-right: 25px;
+ border-top: 1px solid transparent;
+ border-bottom: 1px solid transparent;
+ font-size: 12px;
+ line-height: 28px;
+ color: #808080;
+ cursor: pointer;
+}
+
+#search-container {
+ margin-top: 5px;
+ margin-bottom: 5px;
+ padding-right: 23px !important;
+ cursor: default !important;
+}
+
+#search-container input {
+ width: 130px;
+}
+
+#tab-container .tab.active, body.searching #search-container {
+ background-color: #D4D4D4;
+ border-color: #BFBFBF;
+ color: black;
+ text-shadow: #DBDBDB 0 1px 0;
+ box-shadow: inset -12px 0 30px -30px black;
+}
+
+body.searching #tab-container .tab.active {
+ background-color: transparent;
+ border-color: transparent;
+ color: #808080;
+ text-shadow: inherit;
+ box-shadow: none;
+}
+
+#content {
+ position: absolute;
+ top: 0;
+ left: 220px;
+ right: 0;
+ bottom: 0;
+ overflow: auto;
+}
+
+.tab-content {
+ display: none;
+ position: absolute;
+ width: 840px;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ padding: 20px;
+ padding-top: 15px;
+}
+
+body.searching .tab-content {
+ display: none !important;
+}
+
+body.searching #search-result-container {
+ display: block !important;
+}
+
+body.measuring .tab-content, body.measuring #search-result-container {
+ display: block !important;
+ opacity: 0;
+ overflow: hidden;
+}
diff --git a/ext/src/options_custom/css/setting.css b/ext/src/options_custom/css/setting.css
new file mode 100755
index 0000000..58a5388
--- /dev/null
+++ b/ext/src/options_custom/css/setting.css
@@ -0,0 +1,81 @@
+/*
+// Copyright (c) 2011 Frank Kohlhepp
+// https://github.com/frankkohlhepp/fancy-settings
+// License: LGPL v2.1
+*/
+.tab-content h2 {
+ margin: 0;
+ padding-bottom: 5px;
+ font-size: 26px;
+ color: #707070;
+ line-height: 1;
+}
+
+.setting.group {
+ border-top: 1px solid #EEEEEE;
+ margin-top: 10px;
+ padding-top: 5px;
+ padding-left: 2px;
+}
+
+.setting.group-name {
+ width: 140px;
+ padding: 0;
+ font-size: 14px;
+ font-weight: bold;
+ vertical-align: top;
+}
+
+.setting.bundle {
+ max-width: 600px;
+ margin-bottom: 5px;
+}
+
+.setting.bundle.list-box {
+ margin-bottom: 10px;
+}
+
+.setting.label.radio-buttons + .setting.container.radio-buttons {
+ margin-top: 3px;
+}
+
+.setting.label, .setting.element-label {
+ margin-right: 15px;
+ font-size: 13px;
+ font-weight: normal;
+}
+
+.setting.label.checkbox, .setting.element-label {
+ margin-left: 5px;
+ margin-right: 0;
+}
+
+.setting.label.checkbox {
+ position: relative;
+ top: 1px;
+}
+
+.setting.element.slider {
+ position: relative;
+ width: 150px;
+ top: 4px;
+}
+
+.setting.element.list-box {
+ display: block;
+ height: 100px;
+ width: 100%;
+}
+
+.setting.display.slider {
+ margin-left: 5px;
+ color: #666666;
+}
+
+#nothing-found {
+ display: none;
+ margin-top: 10px;
+ font-size: 18px;
+ font-weight: lighter;
+ color: #999999;
+}
diff --git a/ext/src/options_custom/custom.css b/ext/src/options_custom/custom.css
new file mode 100755
index 0000000..f6504ab
--- /dev/null
+++ b/ext/src/options_custom/custom.css
@@ -0,0 +1,4 @@
+/*
+// Add your own style rules here, not in css/main.css
+// or css/setting.css for easy updating reasons.
+*/
diff --git a/ext/src/options_custom/i18n.js b/ext/src/options_custom/i18n.js
new file mode 100755
index 0000000..13d7ebb
--- /dev/null
+++ b/ext/src/options_custom/i18n.js
@@ -0,0 +1,71 @@
+// SAMPLE
+this.i18n = {
+ "settings": {
+ "en": "Settings",
+ "de": "Optionen"
+ },
+ "search": {
+ "en": "Search",
+ "de": "Suche"
+ },
+ "nothing-found": {
+ "en": "No matches were found.",
+ "de": "Keine Übereinstimmungen gefunden."
+ },
+
+
+
+ "information": {
+ "en": "Information",
+ "de": "Information"
+ },
+ "login": {
+ "en": "Login",
+ "de": "Anmeldung"
+ },
+ "username": {
+ "en": "Username:",
+ "de": "Benutzername:"
+ },
+ "password": {
+ "en": "Password:",
+ "de": "Passwort:"
+ },
+ "x-characters": {
+ "en": "6 - 12 characters",
+ "de": "6 - 12 Zeichen"
+ },
+ "x-characters-pw": {
+ "en": "10 - 18 characters",
+ "de": "10 - 18 Zeichen"
+ },
+ "description": {
+ "en": "This is a description. You can write any text inside of this.
\
+ Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut\
+ labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores\
+ et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem\
+ ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et\
+ dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.\
+ Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.",
+
+ "de": "Das ist eine Beschreibung. Du kannst hier beliebigen Text einfügen.
\
+ Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut\
+ labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores\
+ et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem\
+ ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et\
+ dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.\
+ Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet."
+ },
+ "logout": {
+ "en": "Logout",
+ "de": "Abmeldung"
+ },
+ "enable": {
+ "en": "Enable",
+ "de": "Aktivieren"
+ },
+ "disconnect": {
+ "en": "Disconnect:",
+ "de": "Trennen:"
+ }
+};
diff --git a/ext/src/options_custom/icon.png b/ext/src/options_custom/icon.png
new file mode 100755
index 0000000..9ffd4eb
Binary files /dev/null and b/ext/src/options_custom/icon.png differ
diff --git a/ext/src/options_custom/index.html b/ext/src/options_custom/index.html
new file mode 100755
index 0000000..ec72db6
--- /dev/null
+++ b/ext/src/options_custom/index.html
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ext/src/options_custom/js/classes/fancy-settings.js b/ext/src/options_custom/js/classes/fancy-settings.js
new file mode 100755
index 0000000..5c0223f
--- /dev/null
+++ b/ext/src/options_custom/js/classes/fancy-settings.js
@@ -0,0 +1,152 @@
+//
+// Copyright (c) 2011 Frank Kohlhepp
+// https://github.com/frankkohlhepp/fancy-settings
+// License: LGPL v2.1
+//
+(function () {
+ var FancySettings = this.FancySettings = new Class({
+ "tabs": {},
+
+ "initialize": function (name, icon) {
+ // Set title and icon
+ $("title").set("text", name);
+ $("favicon").set("href", icon);
+ $("icon").set("src", icon);
+ $("settings-label").set("text", (i18n.get("settings") || "Settings"));
+ $("search-label").set("text", (i18n.get("search") || "Search"));
+ $("search").set("placeholder", (i18n.get("search") || "Search") + "...");
+
+ this.tab = new Tab($("tab-container"), $("content"));
+ this.search = new Search($("search"), $("search-result-container"));
+ },
+
+ "create": function (params) {
+ var tab,
+ group,
+ row,
+ content,
+ bundle;
+
+ // Create tab if it doesn't exist already
+ if (this.tabs[params.tab] === undefined) {
+ this.tabs[params.tab] = {"groups":{}};
+ tab = this.tabs[params.tab];
+
+ tab.content = this.tab.create();
+ tab.content.tab.set("text", params.tab);
+ this.search.bind(tab.content.tab);
+
+ tab.content = tab.content.content;
+ (new Element("h2", {
+ "text": params.tab
+ })).inject(tab.content);
+ } else {
+ tab = this.tabs[params.tab];
+ }
+
+ // Create group if it doesn't exist already
+ if (tab.groups[params.group] === undefined) {
+ tab.groups[params.group] = {};
+ group = tab.groups[params.group];
+
+ group.content = (new Element("table", {
+ "class": "setting group"
+ })).inject(tab.content);
+
+ row = (new Element("tr")).inject(group.content);
+
+ (new Element("td", {
+ "class": "setting group-name",
+ "text": params.group
+ })).inject(row);
+
+ content = (new Element("td", {
+ "class": "setting group-content"
+ })).inject(row);
+
+ group.setting = new Setting(content);
+ } else {
+ group = tab.groups[params.group];
+ }
+
+ // Create and index the setting
+ bundle = group.setting.create(params);
+ this.search.add(bundle);
+
+ return bundle;
+ },
+
+ "align": function (settings) {
+ var types,
+ type,
+ maxWidth;
+
+ types = [
+ "text",
+ "button",
+ "slider",
+ "popupButton"
+ ];
+ type = settings[0].params.type;
+ maxWidth = 0;
+
+ if (!types.contains(type)) {
+ throw "invalidType";
+ }
+
+ settings.each(function (setting) {
+ if (setting.params.type !== type) {
+ throw "multipleTypes";
+ }
+
+ var width = setting.label.offsetWidth;
+ if (width > maxWidth) {
+ maxWidth = width;
+ }
+ });
+
+ settings.each(function (setting) {
+ var width = setting.label.offsetWidth;
+ if (width < maxWidth) {
+ if (type === "button" || type === "slider") {
+ setting.element.setStyle("margin-left", (maxWidth - width + 2) + "px");
+ setting.search.element.setStyle("margin-left", (maxWidth - width + 2) + "px");
+ } else {
+ setting.element.setStyle("margin-left", (maxWidth - width) + "px");
+ setting.search.element.setStyle("margin-left", (maxWidth - width) + "px");
+ }
+ }
+ });
+ }
+ });
+
+ FancySettings.__proto__.initWithManifest = function (callback) {
+ var settings,
+ output;
+
+ settings = new FancySettings(manifest.name, manifest.icon);
+ settings.manifest = {};
+
+ manifest.settings.each(function (params) {
+ output = settings.create(params);
+ if (params.name !== undefined) {
+ settings.manifest[params.name] = output;
+ }
+ });
+
+ if (manifest.alignment !== undefined) {
+ document.body.addClass("measuring");
+ manifest.alignment.each(function (group) {
+ group = group.map(function (name) {
+ return settings.manifest[name];
+ });
+ settings.align(group);
+ });
+ document.body.removeClass("measuring");
+ }
+
+ if (callback !== undefined) {
+ callback(settings);
+ }
+ };
+}());
diff --git a/ext/src/options_custom/js/classes/search.js b/ext/src/options_custom/js/classes/search.js
new file mode 100755
index 0000000..7278219
--- /dev/null
+++ b/ext/src/options_custom/js/classes/search.js
@@ -0,0 +1,126 @@
+//
+// Copyright (c) 2011 Frank Kohlhepp
+// https://github.com/frankkohlhepp/fancy-settings
+// License: LGPL v2.1
+//
+(function () {
+ this.Search = new Class({
+ "index": [],
+ "groups": {},
+
+ "initialize": function (search, searchResultContainer) {
+ var setting,
+ find;
+
+ this.search = search;
+ this.searchResultContainer = searchResultContainer;
+ this.setting = new Setting(new Element("div"));
+
+ // Create setting for message "nothing found"
+ setting = new Setting(this.searchResultContainer);
+ this.nothingFound = setting.create({
+ "type": "description",
+ "text": (i18n.get("nothing-found") || "No matches were found.")
+ });
+ this.nothingFound.bundle.set("id", "nothing-found");
+
+ // Create event handlers
+ find = (function (event) {
+ this.find(event.target.get("value"));
+ }).bind(this);
+
+ this.search.addEvent("keyup", (function (event) {
+ if (event.key === "esc") {
+ this.reset();
+ } else {
+ find(event);
+ }
+ }).bind(this));
+ this.search.addEventListener("search", find, false);
+ },
+
+ "bind": function (tab) {
+ tab.addEvent("click", this.reset.bind(this));
+ },
+
+ "add": function (setting) {
+ var searchSetting = this.setting.create(setting.params);
+ setting.search = searchSetting;
+ searchSetting.original = setting;
+ this.index.push(searchSetting);
+
+ setting.addEvent("action", function (value, stopPropagation) {
+ if (searchSetting.set !== undefined && stopPropagation !== true) {
+ searchSetting.set(value, true);
+ }
+ });
+ searchSetting.addEvent("action", function (value) {
+ if (setting.set !== undefined) {
+ setting.set(value, true);
+ }
+ setting.fireEvent("action", [value, true]);
+ });
+ },
+
+ "find": function (searchString) {
+ // Exit search mode
+ if (searchString.trim() === "") {
+ document.body.removeClass("searching");
+ return;
+ }
+
+ // Or enter search mode
+ this.index.each(function (setting) { setting.bundle.dispose(); });
+ Object.each(this.groups, function (group) { group.dispose(); });
+ document.body.addClass("searching");
+
+ // Filter settings
+ var result = this.index.filter(function (setting) {
+ if (setting.params.searchString.contains(searchString.trim().toLowerCase())) {
+ return true;
+ }
+ });
+
+ // Display settings
+ result.each((function (setting) {
+ var group,
+ row;
+
+ // Create group if it doesn't exist already
+ if (this.groups[setting.params.group] === undefined) {
+ this.groups[setting.params.group] = (new Element("table", {
+ "class": "setting group"
+ })).inject(this.searchResultContainer);
+
+ group = this.groups[setting.params.group];
+ row = (new Element("tr")).inject(group);
+
+ (new Element("td", {
+ "class": "setting group-name",
+ "text": setting.params.group
+ })).inject(row);
+
+ group.content = (new Element("td", {
+ "class": "setting group-content"
+ })).inject(row);
+ } else {
+ group = this.groups[setting.params.group].inject(this.searchResultContainer);
+ }
+
+ setting.bundle.inject(group.content);
+ }).bind(this));
+
+ if (result.length === 0) {
+ this.nothingFound.bundle.addClass("show");
+ } else {
+ this.nothingFound.bundle.removeClass("show");
+ }
+ },
+
+ "reset": function () {
+ this.search.set("value", "");
+ this.search.blur();
+ this.find("");
+ }
+ });
+}());
diff --git a/ext/src/options_custom/js/classes/setting.js b/ext/src/options_custom/js/classes/setting.js
new file mode 100755
index 0000000..a1cfea0
--- /dev/null
+++ b/ext/src/options_custom/js/classes/setting.js
@@ -0,0 +1,711 @@
+//
+// Copyright (c) 2011 Frank Kohlhepp
+// https://github.com/frankkohlhepp/fancy-settings
+// License: LGPL v2.1
+//
+(function () {
+ var settings,
+ Bundle;
+
+ settings = new Store("settings");
+ Bundle = new Class({
+ // Attributes:
+ // - tab
+ // - group
+ // - name
+ // - type
+ //
+ // Methods:
+ // - initialize
+ // - createDOM
+ // - setupDOM
+ // - addEvents
+ // - get
+ // - set
+ "Implements": Events,
+
+ "initialize": function (params) {
+ this.params = params;
+ this.params.searchString = "•" + this.params.tab + "•" + this.params.group + "•";
+
+ this.createDOM();
+ this.setupDOM();
+ this.addEvents();
+
+ if (this.params.id !== undefined) {
+ this.element.set("id", this.params.id);
+ }
+
+ if (this.params.name !== undefined) {
+ this.set(settings.get(this.params.name), true);
+ }
+
+ this.params.searchString = this.params.searchString.toLowerCase();
+ },
+
+ "addEvents": function () {
+ this.element.addEvent("change", (function (event) {
+ if (this.params.name !== undefined) {
+ settings.set(this.params.name, this.get());
+ }
+
+ this.fireEvent("action", this.get());
+ }).bind(this));
+ },
+
+ "get": function () {
+ return this.element.get("value");
+ },
+
+ "set": function (value, noChangeEvent) {
+ this.element.set("value", value);
+
+ if (noChangeEvent !== true) {
+ this.element.fireEvent("change");
+ }
+
+ return this;
+ }
+ });
+
+ Bundle.Description = new Class({
+ // text
+ "Extends": Bundle,
+ "addEvents": undefined,
+ "get": undefined,
+ "set": undefined,
+
+ "initialize": function (params) {
+ this.params = params;
+ this.params.searchString = "";
+
+ this.createDOM();
+ this.setupDOM();
+ },
+
+ "createDOM": function () {
+ this.bundle = new Element("div", {
+ "class": "setting bundle description"
+ });
+
+ this.container = new Element("div", {
+ "class": "setting container description"
+ });
+
+ this.element = new Element("p", {
+ "class": "setting element description"
+ });
+ },
+
+ "setupDOM": function () {
+ if (this.params.text !== undefined) {
+ this.element.set("html", this.params.text);
+ }
+
+ this.element.inject(this.container);
+ this.container.inject(this.bundle);
+ }
+ });
+
+ Bundle.Button = new Class({
+ // label, text
+ // action -> click
+ "Extends": Bundle,
+ "get": undefined,
+ "set": undefined,
+
+ "initialize": function (params) {
+ this.params = params;
+ this.params.searchString = "•" + this.params.tab + "•" + this.params.group + "•";
+
+ this.createDOM();
+ this.setupDOM();
+ this.addEvents();
+
+ if (this.params.id !== undefined) {
+ this.element.set("id", this.params.id);
+ }
+
+ this.params.searchString = this.params.searchString.toLowerCase();
+ },
+
+ "createDOM": function () {
+ this.bundle = new Element("div", {
+ "class": "setting bundle button"
+ });
+
+ this.container = new Element("div", {
+ "class": "setting container button"
+ });
+
+ this.element = new Element("input", {
+ "class": "setting element button",
+ "type": "button"
+ });
+
+ this.label = new Element("label", {
+ "class": "setting label button"
+ });
+ },
+
+ "setupDOM": function () {
+ if (this.params.label !== undefined) {
+ this.label.set("html", this.params.label);
+ this.label.inject(this.container);
+ this.params.searchString += this.params.label + "•";
+ }
+
+ if (this.params.text !== undefined) {
+ this.element.set("value", this.params.text);
+ this.params.searchString += this.params.text + "•";
+ }
+
+ this.element.inject(this.container);
+ this.container.inject(this.bundle);
+ },
+
+ "addEvents": function () {
+ this.element.addEvent("click", (function () {
+ this.fireEvent("action");
+ }).bind(this));
+ }
+ });
+
+ Bundle.Text = new Class({
+ // label, text, masked
+ // action -> change & keyup
+ "Extends": Bundle,
+
+ "createDOM": function () {
+ this.bundle = new Element("div", {
+ "class": "setting bundle text"
+ });
+
+ this.container = new Element("div", {
+ "class": "setting container text"
+ });
+
+ this.element = new Element("input", {
+ "class": "setting element text",
+ "type": "text"
+ });
+
+ this.label = new Element("label", {
+ "class": "setting label text"
+ });
+ },
+
+ "setupDOM": function () {
+ if (this.params.label !== undefined) {
+ this.label.set("html", this.params.label);
+ this.label.inject(this.container);
+ this.params.searchString += this.params.label + "•";
+ }
+
+ if (this.params.text !== undefined) {
+ this.element.set("placeholder", this.params.text);
+ this.params.searchString += this.params.text + "•";
+ }
+
+ if (this.params.masked === true) {
+ this.element.set("type", "password");
+ this.params.searchString += "password" + "•";
+ }
+
+ this.element.inject(this.container);
+ this.container.inject(this.bundle);
+ },
+
+ "addEvents": function () {
+ var change = (function (event) {
+ if (this.params.name !== undefined) {
+ settings.set(this.params.name, this.get());
+ }
+
+ this.fireEvent("action", this.get());
+ }).bind(this);
+
+ this.element.addEvent("change", change);
+ this.element.addEvent("keyup", change);
+ }
+ });
+
+ Bundle.Checkbox = new Class({
+ // label
+ // action -> change
+ "Extends": Bundle,
+
+ "createDOM": function () {
+ this.bundle = new Element("div", {
+ "class": "setting bundle checkbox"
+ });
+
+ this.container = new Element("div", {
+ "class": "setting container checkbox"
+ });
+
+ this.element = new Element("input", {
+ "id": String.uniqueID(),
+ "class": "setting element checkbox",
+ "type": "checkbox",
+ "value": "true"
+ });
+
+ this.label = new Element("label", {
+ "class": "setting label checkbox",
+ "for": this.element.get("id")
+ });
+ },
+
+ "setupDOM": function () {
+ this.element.inject(this.container);
+ this.container.inject(this.bundle);
+
+ if (this.params.label !== undefined) {
+ this.label.set("html", this.params.label);
+ this.label.inject(this.container);
+ this.params.searchString += this.params.label + "•";
+ }
+ },
+
+ "get": function () {
+ return this.element.get("checked");
+ },
+
+ "set": function (value, noChangeEvent) {
+ this.element.set("checked", value);
+
+ if (noChangeEvent !== true) {
+ this.element.fireEvent("change");
+ }
+
+ return this;
+ }
+ });
+
+ Bundle.Slider = new Class({
+ // label, max, min, step, display, displayModifier
+ // action -> change
+ "Extends": Bundle,
+
+ "initialize": function (params) {
+ this.params = params;
+ this.params.searchString = "•" + this.params.tab + "•" + this.params.group + "•";
+
+ this.createDOM();
+ this.setupDOM();
+ this.addEvents();
+
+ if (this.params.name !== undefined) {
+ this.set((settings.get(this.params.name) || 0), true);
+ } else {
+ this.set(0, true);
+ }
+
+ this.params.searchString = this.params.searchString.toLowerCase();
+ },
+
+ "createDOM": function () {
+ this.bundle = new Element("div", {
+ "class": "setting bundle slider"
+ });
+
+ this.container = new Element("div", {
+ "class": "setting container slider"
+ });
+
+ this.element = new Element("input", {
+ "class": "setting element slider",
+ "type": "range"
+ });
+
+ this.label = new Element("label", {
+ "class": "setting label slider"
+ });
+
+ this.display = new Element("span", {
+ "class": "setting display slider"
+ });
+ },
+
+ "setupDOM": function () {
+ if (this.params.label !== undefined) {
+ this.label.set("html", this.params.label);
+ this.label.inject(this.container);
+ this.params.searchString += this.params.label + "•";
+ }
+
+ if (this.params.max !== undefined) {
+ this.element.set("max", this.params.max);
+ }
+
+ if (this.params.min !== undefined) {
+ this.element.set("min", this.params.min);
+ }
+
+ if (this.params.step !== undefined) {
+ this.element.set("step", this.params.step);
+ }
+
+ this.element.inject(this.container);
+ if (this.params.display !== false) {
+ if (this.params.displayModifier !== undefined) {
+ this.display.set("text", this.params.displayModifier(0));
+ } else {
+ this.display.set("text", 0);
+ }
+ this.display.inject(this.container);
+ }
+ this.container.inject(this.bundle);
+ },
+
+ "addEvents": function () {
+ this.element.addEvent("change", (function (event) {
+ if (this.params.name !== undefined) {
+ settings.set(this.params.name, this.get());
+ }
+
+ if (this.params.displayModifier !== undefined) {
+ this.display.set("text", this.params.displayModifier(this.get()));
+ } else {
+ this.display.set("text", this.get());
+ }
+ this.fireEvent("action", this.get());
+ }).bind(this));
+ },
+
+ "get": function () {
+ return Number.from(this.element.get("value"));
+ },
+
+ "set": function (value, noChangeEvent) {
+ this.element.set("value", value);
+
+ if (noChangeEvent !== true) {
+ this.element.fireEvent("change");
+ } else {
+ if (this.params.displayModifier !== undefined) {
+ this.display.set("text", this.params.displayModifier(Number.from(value)));
+ } else {
+ this.display.set("text", Number.from(value));
+ }
+ }
+
+ return this;
+ }
+ });
+
+ Bundle.PopupButton = new Class({
+ // label, options[{value, text}]
+ // action -> change
+ "Extends": Bundle,
+
+ "createDOM": function () {
+ this.bundle = new Element("div", {
+ "class": "setting bundle popup-button"
+ });
+
+ this.container = new Element("div", {
+ "class": "setting container popup-button"
+ });
+
+ this.element = new Element("select", {
+ "class": "setting element popup-button"
+ });
+
+ this.label = new Element("label", {
+ "class": "setting label popup-button"
+ });
+
+ if (this.params.options === undefined) { return; }
+
+ // convert array syntax into object syntax for options
+ function arrayToObject(option) {
+ if (typeOf(option) == "array") {
+ option = {
+ "value": option[0],
+ "text": option[1] || option[0],
+ };
+ }
+ return option;
+ }
+
+ // convert arrays
+ if (typeOf(this.params.options) == "array") {
+ var values = [];
+ this.params.options.each((function(values, option) {
+ values.push(arrayToObject(option));
+ }).bind(this, values));
+ this.params.options = { "values": values };
+ }
+
+ var groups;
+ if (this.params.options.groups !== undefined) {
+ groups = {};
+ this.params.options.groups.each((function (groups, group) {
+ this.params.searchString += (group) + "•";
+ groups[group] = (new Element("optgroup", {
+ "label": group,
+ }).inject(this.element));
+ }).bind(this, groups));
+ }
+
+ if (this.params.options.values !== undefined) {
+ this.params.options.values.each((function(groups, option) {
+ option = arrayToObject(option);
+ this.params.searchString += (option.text || option.value) + "•";
+
+ // find the parent of this option - either a group or the main element
+ var parent;
+ if (option.group && this.params.options.groups) {
+ if ((option.group - 1) in this.params.options.groups) {
+ option.group = this.params.options.groups[option.group-1];
+ }
+ if (option.group in groups) {
+ parent = groups[option.group];
+ }
+ else {
+ parent = this.element;
+ }
+ }
+ else {
+ parent = this.element;
+ }
+
+ (new Element("option", {
+ "value": option.value,
+ "text": option.text || option.value,
+ })).inject(parent);
+ }).bind(this, groups));
+ }
+ },
+
+ "setupDOM": function () {
+ if (this.params.label !== undefined) {
+ this.label.set("html", this.params.label);
+ this.label.inject(this.container);
+ this.params.searchString += this.params.label + "•";
+ }
+
+ this.element.inject(this.container);
+ this.container.inject(this.bundle);
+ }
+ });
+
+ Bundle.ListBox = new Class({
+ // label, options[{value, text}]
+ // action -> change
+ "Extends": Bundle.PopupButton,
+
+ "createDOM": function () {
+ this.bundle = new Element("div", {
+ "class": "setting bundle list-box"
+ });
+
+ this.container = new Element("div", {
+ "class": "setting container list-box"
+ });
+
+ this.element = new Element("select", {
+ "class": "setting element list-box",
+ "size": "2"
+ });
+
+ this.label = new Element("label", {
+ "class": "setting label list-box"
+ });
+
+ if (this.params.options === undefined) { return; }
+ this.params.options.each((function (option) {
+ this.params.searchString += (option.text || option.value) + "•";
+
+ (new Element("option", {
+ "value": option.value,
+ "text": option.text || option.value
+ })).inject(this.element);
+ }).bind(this));
+ },
+
+ "get": function () {
+ return (this.element.get("value") || undefined);
+ }
+ });
+
+ Bundle.Textarea = new Class({
+ // label, text, value
+ // action -> change & keyup
+ "Extends": Bundle,
+
+ "createDOM": function () {
+ this.bundle = new Element("div", {
+ "class": "setting bundle textarea"
+ });
+
+ this.container = new Element("div", {
+ "class": "setting container textarea"
+ });
+
+ this.element = new Element("textarea", {
+ "class": "setting element textarea"
+ });
+
+ this.label = new Element("label", {
+ "class": "setting label textarea"
+ });
+ },
+
+ "setupDOM": function () {
+ if (this.params.label !== undefined) {
+ this.label.set("html", this.params.label);
+ this.label.inject(this.container);
+ this.params.searchString += this.params.label + "•";
+ }
+
+ if (this.params.text !== undefined) {
+ this.element.set("placeholder", this.params.text);
+ this.params.searchString += this.params.text + "•";
+ }
+
+ if (this.params.value !== undefined) {
+ this.element.appendText(this.params.text);
+ }
+
+ this.element.inject(this.container);
+ this.container.inject(this.bundle);
+ },
+
+ "addEvents": function () {
+ var change = (function (event) {
+ if (this.params.name !== undefined) {
+ settings.set(this.params.name, this.get());
+ }
+
+ this.fireEvent("action", this.get());
+ }).bind(this);
+
+ this.element.addEvent("change", change);
+ this.element.addEvent("keyup", change);
+ }
+ });
+
+ Bundle.RadioButtons = new Class({
+ // label, options[{value, text}]
+ // action -> change
+ "Extends": Bundle,
+
+ "createDOM": function () {
+ var settingID = String.uniqueID();
+
+ this.bundle = new Element("div", {
+ "class": "setting bundle radio-buttons"
+ });
+
+ this.label = new Element("label", {
+ "class": "setting label radio-buttons"
+ });
+
+ this.containers = [];
+ this.elements = [];
+ this.labels = [];
+
+ if (this.params.options === undefined) { return; }
+ this.params.options.each((function (option) {
+ var optionID,
+ container;
+
+ this.params.searchString += (option.text || option.value) + "•";
+
+ optionID = String.uniqueID();
+ container = (new Element("div", {
+ "class": "setting container radio-buttons"
+ })).inject(this.bundle);
+ this.containers.push(container);
+
+ this.elements.push((new Element("input", {
+ "id": optionID,
+ "name": settingID,
+ "class": "setting element radio-buttons",
+ "type": "radio",
+ "value": option.value
+ })).inject(container));
+
+ this.labels.push((new Element("label", {
+ "class": "setting element-label radio-buttons",
+ "for": optionID,
+ "text": option.text || option.value
+ })).inject(container));
+ }).bind(this));
+ },
+
+ "setupDOM": function () {
+ if (this.params.label !== undefined) {
+ this.label.set("html", this.params.label);
+ this.label.inject(this.bundle, "top");
+ this.params.searchString += this.params.label + "•";
+ }
+ },
+
+ "addEvents": function () {
+ this.bundle.addEvent("change", (function (event) {
+ if (this.params.name !== undefined) {
+ settings.set(this.params.name, this.get());
+ }
+
+ this.fireEvent("action", this.get());
+ }).bind(this));
+ },
+
+ "get": function () {
+ var checkedEl = this.elements.filter((function (el) {
+ return el.get("checked");
+ }).bind(this));
+ return (checkedEl[0] && checkedEl[0].get("value"));
+ },
+
+ "set": function (value, noChangeEvent) {
+ var desiredEl = this.elements.filter((function (el) {
+ return (el.get("value") === value);
+ }).bind(this));
+ desiredEl[0] && desiredEl[0].set("checked", true);
+
+ if (noChangeEvent !== true) {
+ this.bundle.fireEvent("change");
+ }
+
+ return this;
+ }
+ });
+
+ this.Setting = new Class({
+ "initialize": function (container) {
+ this.container = container;
+ },
+
+ "create": function (params) {
+ var types,
+ bundle;
+
+ // Available types
+ types = {
+ "description": "Description",
+ "button": "Button",
+ "text": "Text",
+ "textarea": "Textarea",
+ "checkbox": "Checkbox",
+ "slider": "Slider",
+ "popupButton": "PopupButton",
+ "listBox": "ListBox",
+ "radioButtons": "RadioButtons"
+ };
+
+ if (types.hasOwnProperty(params.type)) {
+ bundle = new Bundle[types[params.type]](params);
+ bundle.bundleContainer = this.container;
+ bundle.bundle.inject(this.container);
+ return bundle;
+ } else {
+ throw "invalidType";
+ }
+ }
+ });
+}());
diff --git a/ext/src/options_custom/js/classes/tab.js b/ext/src/options_custom/js/classes/tab.js
new file mode 100755
index 0000000..aafd3ef
--- /dev/null
+++ b/ext/src/options_custom/js/classes/tab.js
@@ -0,0 +1,51 @@
+//
+// Copyright (c) 2011 Frank Kohlhepp
+// https://github.com/frankkohlhepp/fancy-settings
+// License: LGPL v2.1
+//
+(function () {
+ var Bundle = new Class({
+ "initialize": function (creator) {
+ this.creator = creator;
+
+ // Create DOM elements
+ this.tab = new Element("div", {"class": "tab"});
+ this.content = new Element("div", {"class": "tab-content"});
+
+ // Create event handlers
+ this.tab.addEvent("click", this.activate.bind(this));
+ },
+
+ "activate": function () {
+ if (this.creator.activeBundle && this.creator.activeBundle !== this) {
+ this.creator.activeBundle.deactivate();
+ }
+ this.tab.addClass("active");
+ this.content.addClass("show");
+ this.creator.activeBundle = this;
+ },
+
+ "deactivate": function () {
+ this.tab.removeClass("active");
+ this.content.removeClass("show");
+ this.creator.activeBundle = null;
+ }
+ });
+
+ this.Tab = new Class({
+ "activeBundle": null,
+
+ "initialize": function (tabContainer, tabContentContainer) {
+ this.tabContainer = tabContainer;
+ this.tabContentContainer = tabContentContainer;
+ },
+
+ "create": function () {
+ var bundle = new Bundle(this);
+ bundle.tab.inject(this.tabContainer);
+ bundle.content.inject(this.tabContentContainer);
+ if (!this.activeBundle) { bundle.activate(); }
+ return bundle;
+ }
+ });
+}());
diff --git a/ext/src/options_custom/js/i18n.js b/ext/src/options_custom/js/i18n.js
new file mode 100755
index 0000000..20ac623
--- /dev/null
+++ b/ext/src/options_custom/js/i18n.js
@@ -0,0 +1,27 @@
+//
+// Copyright (c) 2011 Frank Kohlhepp
+// https://github.com/frankkohlhepp/fancy-settings
+// License: LGPL v2.1
+//
+(function () {
+ var lang = navigator.language;
+ if (this.i18n === undefined) { this.i18n = {}; }
+ this.i18n.get = function (value) {
+ if (value === "lang") {
+ return lang;
+ }
+
+ if (this.hasOwnProperty(value)) {
+ value = this[value];
+ if (value.hasOwnProperty(lang)) {
+ return value[lang];
+ } else if (value.hasOwnProperty("en")) {
+ return value["en"];
+ } else {
+ return Object.values(value)[0];
+ }
+ } else {
+ return value;
+ }
+ };
+}());
diff --git a/ext/src/options_custom/lib/default.css b/ext/src/options_custom/lib/default.css
new file mode 100755
index 0000000..22e83b3
--- /dev/null
+++ b/ext/src/options_custom/lib/default.css
@@ -0,0 +1,467 @@
+/*
+// Copyright (c) 2007 - 2010 blueprintcss.org
+// Modified and extended by Frank Kohlhepp in 2011
+// https://github.com/frankkohlhepp/default-css
+// License: MIT-license
+*/
+
+/*
+// Reset the default browser CSS
+*/
+html {
+ margin: 0;
+ padding: 0;
+ border: 0;
+}
+
+body, div, span, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, code,
+del, dfn, em, img, q, dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, dialog, figure, footer, header,
+hgroup, nav, section {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ font-family: inherit;
+ font-size: 100%;
+ font-weight: inherit;
+ font-style: inherit;
+ vertical-align: baseline;
+}
+
+article, aside, dialog, figure, footer, header,
+hgroup, nav, section {
+ display: block;
+}
+
+body {
+ background-color: white;
+ line-height: 1.5;
+}
+
+table {
+ border-collapse: separate;
+ border-spacing: 0;
+}
+
+caption, th, td {
+ text-align: left;
+ font-weight: normal;
+}
+
+table, th, td {
+ vertical-align: middle;
+}
+
+blockquote:before, blockquote:after, q:before, q:after {
+ content: "";
+}
+
+blockquote, q {
+ quotes: "" "";
+}
+
+a img {
+ border: none;
+}
+
+/*
+// Default typography
+*/
+html {
+ font-size: 100.01%;
+}
+
+body {
+ background-color: white;
+ font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
+ font-size: 75%;
+ color: #222222;
+}
+
+/* Headings */
+h1, h2, h3, h4, h5, h6 {
+ font-weight: normal;
+ color: #111111;
+}
+
+h1 {
+ margin-bottom: 0.5em;
+ font-size: 3em;
+ line-height: 1;
+}
+
+h2 {
+ margin-bottom: 0.75em;
+ font-size: 2em;
+}
+
+h3 {
+ margin-bottom: 1em;
+ font-size: 1.5em;
+ line-height: 1;
+}
+
+h4 {
+ margin-bottom: 1.25em;
+ font-size: 1.2em;
+ line-height: 1.25;
+}
+
+h5 {
+ margin-bottom: 1.5em;
+ font-size: 1em;
+ font-weight: bold;
+}
+
+h6 {
+ font-size: 1em;
+ font-weight: bold;
+}
+
+h1 img, h2 img, h3 img,
+h4 img, h5 img, h6 img {
+ margin: 0;
+}
+
+/* Text elements */
+p {
+ margin: 0 0 1.5em;
+}
+
+.left {
+ float: left !important;
+}
+
+p .left {
+ margin: 1.5em 1.5em 1.5em 0;
+ padding: 0;
+}
+
+.right {
+ float: right !important;
+}
+
+p .right {
+ margin: 1.5em 0 1.5em 1.5em;
+ padding: 0;
+}
+
+a:focus, a:hover {
+ color: #0099FF;
+}
+
+a {
+ color: #0066CC;
+ text-decoration: underline;
+}
+
+blockquote {
+ margin: 1.5em;
+ font-style: italic;
+ color: #666666;
+}
+
+strong, dfn {
+ font-weight: bold;
+}
+
+em, dfn {
+ font-style: italic;
+}
+
+sup, sub {
+ line-height: 0;
+}
+
+abbr, acronym {
+ border-bottom: 1px dotted #666666;
+}
+
+address {
+ margin: 0 0 1.5em;
+ font-style: italic;
+}
+
+del {
+ color: #666666;
+}
+
+pre {
+ margin: 1.5em 0;
+ white-space: pre;
+}
+
+pre, code, tt {
+ font: 1em "andale mono", "lucida console", monospace;
+ line-height: 1.5;
+}
+
+/* Lists */
+li ul, li ol {
+ margin: 0;
+}
+
+ul, ol {
+ margin: 0 1.5em 1.5em 0;
+ padding-left: 1.5em;
+}
+
+ul {
+ list-style-type: disc;
+}
+
+ol {
+ list-style-type: decimal;
+}
+
+dl {
+ margin: 0 0 1.5em 0;
+}
+
+dl dt {
+ font-weight: bold;
+}
+
+dd {
+ margin-left: 1.5em;
+}
+
+/* Tables */
+table {
+ width: 100%;
+ margin-bottom: 1.4em;
+}
+
+th {
+ font-weight: bold;
+}
+
+table.zebra thead th, table.zebra tfoot th {
+ background-color: #BFBFBF;
+}
+
+th, td, caption {
+ padding: 4px 10px 4px 5px;
+}
+
+table.zebra tbody tr:nth-child(even) td, table.zebra tbody tr.even td {
+ background-color: #E6E6E6;
+}
+
+caption {
+ background-color: #EEEEEE;
+}
+
+/* Misc classes */
+.fancy {
+ text-shadow: white 0 1px 0;
+}
+
+.bfancy {
+ text-shadow: black 0 1px 0;
+}
+
+.fancyt {
+ text-shadow: white 0 -1px 0;
+}
+
+.bfancyt {
+ text-shadow: black 0 -1px 0;
+}
+
+.no-fancy {
+ text-shadow: none;
+}
+
+.select {
+ cursor: auto;
+ user-select: auto;
+ -webkit-user-select: auto;
+ -moz-user-select: auto;
+ -o-user-select: auto;
+}
+
+img.select, .select img {
+ user-drag: auto;
+ -webkit-user-drag: auto;
+ -moz-user-drag: auto;
+ -o-user-drag: auto;
+}
+
+.no-select {
+ cursor: default;
+ user-select: none;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -o-user-select: none;
+}
+
+img.no-select, .no-select img {
+ user-drag: none;
+ -webkit-user-drag: none;
+ -moz-user-drag: none;
+ -o-user-drag: none;
+}
+
+.focus:focus, .focus :focus {
+ outline: auto;
+}
+
+.no-focus:focus, .no-focus :focus {
+ outline: 0;
+}
+
+.small {
+ margin-bottom: 1.875em;
+ font-size: .8em;
+ line-height: 1.875em;
+}
+
+.large {
+ margin-bottom: 1.25em;
+ font-size: 1.2em;
+ line-height: 2.5em;
+}
+
+.show {
+ display: block !important;
+}
+
+.show-inline {
+ display: inline-block !important;
+}
+
+.hide {
+ display: none;
+}
+
+.quiet {
+ color: #666666;
+}
+
+.loud {
+ color: black;
+}
+
+.highlight {
+ background-color: yellow;
+}
+
+.added {
+ background-color: #006600;
+ color: white;
+}
+
+.removed {
+ background-color: #990000;
+ color: white;
+}
+
+.first {
+ margin-left: 0;
+ padding-left: 0;
+}
+
+.last {
+ margin-right: 0;
+ padding-right: 0;
+}
+
+.top {
+ margin-top: 0;
+ padding-top: 0;
+}
+
+.bottom {
+ margin-bottom: 0;
+ padding-bottom: 0;
+}
+
+/*
+// Default styling for forms
+*/
+fieldset {
+ margin: 0 0 1.5em 0;
+ padding: 0 1.4em 1.4em 1.4em;
+ border: 1px solid #CCCCCC;
+}
+
+legend {
+ margin-top: -0.2em;
+ margin-bottom: 1em;
+ font-weight: bold;
+ font-size: 1.2em;
+}
+
+/* Form fields */
+input[type=text], input[type=password], textarea {
+ background-color: white;
+ border: 1px solid #BBBBBB;
+}
+
+input[type=text], input[type=password],
+textarea, select {
+ margin: 0.5em 0;
+}
+
+input[type=text], input[type=password] {
+ width: 300px;
+ padding: 4px;
+}
+
+textarea {
+ width: 450px;
+ height: 170px;
+ padding: 5px;
+}
+
+/* success, info, notice and error boxes */
+.success, .info, .notice, .error {
+ margin-bottom: 1em;
+ padding: 0.8em;
+ border: 2px solid #DDDDDD;
+}
+
+.success {
+ background-color: #E6EFC2;
+ border-color: #C6D880;
+ color: #264409;
+}
+
+.info {
+ background-color: #D5EDF8;
+ border-color: #92CAE4;
+ color: #205791;
+}
+
+.notice {
+ background-color: #FFF6BF;
+ border-color: #FFD324;
+ color: #514721;
+}
+
+.error {
+ background-color: #FBE3E4;
+ border-color: #FBC2C4;
+ color: #8A1F11;
+}
+
+.success a {
+ color: #264409;
+}
+
+.info a {
+ color: #205791;
+}
+
+.notice a {
+ color: #514721;
+}
+
+.error a {
+ color: #8A1F11;
+}
diff --git a/ext/src/options_custom/lib/mootools-core.js b/ext/src/options_custom/lib/mootools-core.js
new file mode 100755
index 0000000..cba97ef
--- /dev/null
+++ b/ext/src/options_custom/lib/mootools-core.js
@@ -0,0 +1,5515 @@
+/*
+---
+MooTools: the javascript framework
+
+web build:
+ - http://mootools.net/core/7c56cfef9dddcf170a5d68e3fb61cfd7
+
+packager build:
+ - packager build Core/Core Core/Array Core/String Core/Number Core/Function Core/Object Core/Event Core/Browser Core/Class Core/Class.Extras Core/Slick.Parser Core/Slick.Finder Core/Element Core/Element.Style Core/Element.Event Core/Element.Dimensions Core/Fx Core/Fx.CSS Core/Fx.Tween Core/Fx.Morph Core/Fx.Transitions Core/Request Core/Request.HTML Core/Request.JSON Core/Cookie Core/JSON Core/DOMReady Core/Swiff
+
+/*
+---
+
+name: Core
+
+description: The heart of MooTools.
+
+license: MIT-style license.
+
+copyright: Copyright (c) 2006-2010 [Valerio Proietti](http://mad4milk.net/).
+
+authors: The MooTools production team (http://mootools.net/developers/)
+
+inspiration:
+ - Class implementation inspired by [Base.js](http://dean.edwards.name/weblog/2006/03/base/) Copyright (c) 2006 Dean Edwards, [GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php)
+ - Some functionality inspired by [Prototype.js](http://prototypejs.org) Copyright (c) 2005-2007 Sam Stephenson, [MIT License](http://opensource.org/licenses/mit-license.php)
+
+provides: [Core, MooTools, Type, typeOf, instanceOf, Native]
+
+...
+*/
+
+(function(){
+
+this.MooTools = {
+ version: '1.3.2',
+ build: 'c9f1ff10e9e7facb65e9481049ed1b450959d587'
+};
+
+// typeOf, instanceOf
+
+var typeOf = this.typeOf = function(item){
+ if (item == null) return 'null';
+ if (item.$family) return item.$family();
+
+ if (item.nodeName){
+ if (item.nodeType == 1) return 'element';
+ if (item.nodeType == 3) return (/\S/).test(item.nodeValue) ? 'textnode' : 'whitespace';
+ } else if (typeof item.length == 'number'){
+ if (item.callee) return 'arguments';
+ if ('item' in item) return 'collection';
+ }
+
+ return typeof item;
+};
+
+var instanceOf = this.instanceOf = function(item, object){
+ if (item == null) return false;
+ var constructor = item.$constructor || item.constructor;
+ while (constructor){
+ if (constructor === object) return true;
+ constructor = constructor.parent;
+ }
+ return item instanceof object;
+};
+
+// Function overloading
+
+var Function = this.Function;
+
+var enumerables = true;
+for (var i in {toString: 1}) enumerables = null;
+if (enumerables) enumerables = ['hasOwnProperty', 'valueOf', 'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString', 'toString', 'constructor'];
+
+Function.prototype.overloadSetter = function(usePlural){
+ var self = this;
+ return function(a, b){
+ if (a == null) return this;
+ if (usePlural || typeof a != 'string'){
+ for (var k in a) self.call(this, k, a[k]);
+ if (enumerables) for (var i = enumerables.length; i--;){
+ k = enumerables[i];
+ if (a.hasOwnProperty(k)) self.call(this, k, a[k]);
+ }
+ } else {
+ self.call(this, a, b);
+ }
+ return this;
+ };
+};
+
+Function.prototype.overloadGetter = function(usePlural){
+ var self = this;
+ return function(a){
+ var args, result;
+ if (usePlural || typeof a != 'string') args = a;
+ else if (arguments.length > 1) args = arguments;
+ if (args){
+ result = {};
+ for (var i = 0; i < args.length; i++) result[args[i]] = self.call(this, args[i]);
+ } else {
+ result = self.call(this, a);
+ }
+ return result;
+ };
+};
+
+Function.prototype.extend = function(key, value){
+ this[key] = value;
+}.overloadSetter();
+
+Function.prototype.implement = function(key, value){
+ this.prototype[key] = value;
+}.overloadSetter();
+
+// From
+
+var slice = Array.prototype.slice;
+
+Function.from = function(item){
+ return (typeOf(item) == 'function') ? item : function(){
+ return item;
+ };
+};
+
+Array.from = function(item){
+ if (item == null) return [];
+ return (Type.isEnumerable(item) && typeof item != 'string') ? (typeOf(item) == 'array') ? item : slice.call(item) : [item];
+};
+
+Number.from = function(item){
+ var number = parseFloat(item);
+ return isFinite(number) ? number : null;
+};
+
+String.from = function(item){
+ return item + '';
+};
+
+// hide, protect
+
+Function.implement({
+
+ hide: function(){
+ this.$hidden = true;
+ return this;
+ },
+
+ protect: function(){
+ this.$protected = true;
+ return this;
+ }
+
+});
+
+// Type
+
+var Type = this.Type = function(name, object){
+ if (name){
+ var lower = name.toLowerCase();
+ var typeCheck = function(item){
+ return (typeOf(item) == lower);
+ };
+
+ Type['is' + name] = typeCheck;
+ if (object != null){
+ object.prototype.$family = (function(){
+ return lower;
+ }).hide();
+
+ }
+ }
+
+ if (object == null) return null;
+
+ object.extend(this);
+ object.$constructor = Type;
+ object.prototype.$constructor = object;
+
+ return object;
+};
+
+var toString = Object.prototype.toString;
+
+Type.isEnumerable = function(item){
+ return (item != null && typeof item.length == 'number' && toString.call(item) != '[object Function]' );
+};
+
+var hooks = {};
+
+var hooksOf = function(object){
+ var type = typeOf(object.prototype);
+ return hooks[type] || (hooks[type] = []);
+};
+
+var implement = function(name, method){
+ if (method && method.$hidden) return;
+
+ var hooks = hooksOf(this);
+
+ for (var i = 0; i < hooks.length; i++){
+ var hook = hooks[i];
+ if (typeOf(hook) == 'type') implement.call(hook, name, method);
+ else hook.call(this, name, method);
+ }
+
+ var previous = this.prototype[name];
+ if (previous == null || !previous.$protected) this.prototype[name] = method;
+
+ if (this[name] == null && typeOf(method) == 'function') extend.call(this, name, function(item){
+ return method.apply(item, slice.call(arguments, 1));
+ });
+};
+
+var extend = function(name, method){
+ if (method && method.$hidden) return;
+ var previous = this[name];
+ if (previous == null || !previous.$protected) this[name] = method;
+};
+
+Type.implement({
+
+ implement: implement.overloadSetter(),
+
+ extend: extend.overloadSetter(),
+
+ alias: function(name, existing){
+ implement.call(this, name, this.prototype[existing]);
+ }.overloadSetter(),
+
+ mirror: function(hook){
+ hooksOf(this).push(hook);
+ return this;
+ }
+
+});
+
+new Type('Type', Type);
+
+// Default Types
+
+var force = function(name, object, methods){
+ var isType = (object != Object),
+ prototype = object.prototype;
+
+ if (isType) object = new Type(name, object);
+
+ for (var i = 0, l = methods.length; i < l; i++){
+ var key = methods[i],
+ generic = object[key],
+ proto = prototype[key];
+
+ if (generic) generic.protect();
+
+ if (isType && proto){
+ delete prototype[key];
+ prototype[key] = proto.protect();
+ }
+ }
+
+ if (isType) object.implement(prototype);
+
+ return force;
+};
+
+force('String', String, [
+ 'charAt', 'charCodeAt', 'concat', 'indexOf', 'lastIndexOf', 'match', 'quote', 'replace', 'search',
+ 'slice', 'split', 'substr', 'substring', 'toLowerCase', 'toUpperCase'
+])('Array', Array, [
+ 'pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift', 'concat', 'join', 'slice',
+ 'indexOf', 'lastIndexOf', 'filter', 'forEach', 'every', 'map', 'some', 'reduce', 'reduceRight'
+])('Number', Number, [
+ 'toExponential', 'toFixed', 'toLocaleString', 'toPrecision'
+])('Function', Function, [
+ 'apply', 'call', 'bind'
+])('RegExp', RegExp, [
+ 'exec', 'test'
+])('Object', Object, [
+ 'create', 'defineProperty', 'defineProperties', 'keys',
+ 'getPrototypeOf', 'getOwnPropertyDescriptor', 'getOwnPropertyNames',
+ 'preventExtensions', 'isExtensible', 'seal', 'isSealed', 'freeze', 'isFrozen'
+])('Date', Date, ['now']);
+
+Object.extend = extend.overloadSetter();
+
+Date.extend('now', function(){
+ return +(new Date);
+});
+
+new Type('Boolean', Boolean);
+
+// fixes NaN returning as Number
+
+Number.prototype.$family = function(){
+ return isFinite(this) ? 'number' : 'null';
+}.hide();
+
+// Number.random
+
+Number.extend('random', function(min, max){
+ return Math.floor(Math.random() * (max - min + 1) + min);
+});
+
+// forEach, each
+
+var hasOwnProperty = Object.prototype.hasOwnProperty;
+Object.extend('forEach', function(object, fn, bind){
+ for (var key in object){
+ if (hasOwnProperty.call(object, key)) fn.call(bind, object[key], key, object);
+ }
+});
+
+Object.each = Object.forEach;
+
+Array.implement({
+
+ forEach: function(fn, bind){
+ for (var i = 0, l = this.length; i < l; i++){
+ if (i in this) fn.call(bind, this[i], i, this);
+ }
+ },
+
+ each: function(fn, bind){
+ Array.forEach(this, fn, bind);
+ return this;
+ }
+
+});
+
+// Array & Object cloning, Object merging and appending
+
+var cloneOf = function(item){
+ switch (typeOf(item)){
+ case 'array': return item.clone();
+ case 'object': return Object.clone(item);
+ default: return item;
+ }
+};
+
+Array.implement('clone', function(){
+ var i = this.length, clone = new Array(i);
+ while (i--) clone[i] = cloneOf(this[i]);
+ return clone;
+});
+
+var mergeOne = function(source, key, current){
+ switch (typeOf(current)){
+ case 'object':
+ if (typeOf(source[key]) == 'object') Object.merge(source[key], current);
+ else source[key] = Object.clone(current);
+ break;
+ case 'array': source[key] = current.clone(); break;
+ default: source[key] = current;
+ }
+ return source;
+};
+
+Object.extend({
+
+ merge: function(source, k, v){
+ if (typeOf(k) == 'string') return mergeOne(source, k, v);
+ for (var i = 1, l = arguments.length; i < l; i++){
+ var object = arguments[i];
+ for (var key in object) mergeOne(source, key, object[key]);
+ }
+ return source;
+ },
+
+ clone: function(object){
+ var clone = {};
+ for (var key in object) clone[key] = cloneOf(object[key]);
+ return clone;
+ },
+
+ append: function(original){
+ for (var i = 1, l = arguments.length; i < l; i++){
+ var extended = arguments[i] || {};
+ for (var key in extended) original[key] = extended[key];
+ }
+ return original;
+ }
+
+});
+
+// Object-less types
+
+['Object', 'WhiteSpace', 'TextNode', 'Collection', 'Arguments'].each(function(name){
+ new Type(name);
+});
+
+// Unique ID
+
+var UID = Date.now();
+
+String.extend('uniqueID', function(){
+ return (UID++).toString(36);
+});
+
+
+
+})();
+
+
+/*
+---
+
+name: Array
+
+description: Contains Array Prototypes like each, contains, and erase.
+
+license: MIT-style license.
+
+requires: Type
+
+provides: Array
+
+...
+*/
+
+Array.implement({
+
+ /**/
+ every: function(fn, bind){
+ for (var i = 0, l = this.length; i < l; i++){
+ if ((i in this) && !fn.call(bind, this[i], i, this)) return false;
+ }
+ return true;
+ },
+
+ filter: function(fn, bind){
+ var results = [];
+ for (var i = 0, l = this.length; i < l; i++){
+ if ((i in this) && fn.call(bind, this[i], i, this)) results.push(this[i]);
+ }
+ return results;
+ },
+
+ indexOf: function(item, from){
+ var len = this.length;
+ for (var i = (from < 0) ? Math.max(0, len + from) : from || 0; i < len; i++){
+ if (this[i] === item) return i;
+ }
+ return -1;
+ },
+
+ map: function(fn, bind){
+ var results = [];
+ for (var i = 0, l = this.length; i < l; i++){
+ if (i in this) results[i] = fn.call(bind, this[i], i, this);
+ }
+ return results;
+ },
+
+ some: function(fn, bind){
+ for (var i = 0, l = this.length; i < l; i++){
+ if ((i in this) && fn.call(bind, this[i], i, this)) return true;
+ }
+ return false;
+ },
+ /*!ES5>*/
+
+ clean: function(){
+ return this.filter(function(item){
+ return item != null;
+ });
+ },
+
+ invoke: function(methodName){
+ var args = Array.slice(arguments, 1);
+ return this.map(function(item){
+ return item[methodName].apply(item, args);
+ });
+ },
+
+ associate: function(keys){
+ var obj = {}, length = Math.min(this.length, keys.length);
+ for (var i = 0; i < length; i++) obj[keys[i]] = this[i];
+ return obj;
+ },
+
+ link: function(object){
+ var result = {};
+ for (var i = 0, l = this.length; i < l; i++){
+ for (var key in object){
+ if (object[key](this[i])){
+ result[key] = this[i];
+ delete object[key];
+ break;
+ }
+ }
+ }
+ return result;
+ },
+
+ contains: function(item, from){
+ return this.indexOf(item, from) != -1;
+ },
+
+ append: function(array){
+ this.push.apply(this, array);
+ return this;
+ },
+
+ getLast: function(){
+ return (this.length) ? this[this.length - 1] : null;
+ },
+
+ getRandom: function(){
+ return (this.length) ? this[Number.random(0, this.length - 1)] : null;
+ },
+
+ include: function(item){
+ if (!this.contains(item)) this.push(item);
+ return this;
+ },
+
+ combine: function(array){
+ for (var i = 0, l = array.length; i < l; i++) this.include(array[i]);
+ return this;
+ },
+
+ erase: function(item){
+ for (var i = this.length; i--;){
+ if (this[i] === item) this.splice(i, 1);
+ }
+ return this;
+ },
+
+ empty: function(){
+ this.length = 0;
+ return this;
+ },
+
+ flatten: function(){
+ var array = [];
+ for (var i = 0, l = this.length; i < l; i++){
+ var type = typeOf(this[i]);
+ if (type == 'null') continue;
+ array = array.concat((type == 'array' || type == 'collection' || type == 'arguments' || instanceOf(this[i], Array)) ? Array.flatten(this[i]) : this[i]);
+ }
+ return array;
+ },
+
+ pick: function(){
+ for (var i = 0, l = this.length; i < l; i++){
+ if (this[i] != null) return this[i];
+ }
+ return null;
+ },
+
+ hexToRgb: function(array){
+ if (this.length != 3) return null;
+ var rgb = this.map(function(value){
+ if (value.length == 1) value += value;
+ return value.toInt(16);
+ });
+ return (array) ? rgb : 'rgb(' + rgb + ')';
+ },
+
+ rgbToHex: function(array){
+ if (this.length < 3) return null;
+ if (this.length == 4 && this[3] == 0 && !array) return 'transparent';
+ var hex = [];
+ for (var i = 0; i < 3; i++){
+ var bit = (this[i] - 0).toString(16);
+ hex.push((bit.length == 1) ? '0' + bit : bit);
+ }
+ return (array) ? hex : '#' + hex.join('');
+ }
+
+});
+
+
+
+
+/*
+---
+
+name: String
+
+description: Contains String Prototypes like camelCase, capitalize, test, and toInt.
+
+license: MIT-style license.
+
+requires: Type
+
+provides: String
+
+...
+*/
+
+String.implement({
+
+ test: function(regex, params){
+ return ((typeOf(regex) == 'regexp') ? regex : new RegExp('' + regex, params)).test(this);
+ },
+
+ contains: function(string, separator){
+ return (separator) ? (separator + this + separator).indexOf(separator + string + separator) > -1 : this.indexOf(string) > -1;
+ },
+
+ trim: function(){
+ return this.replace(/^\s+|\s+$/g, '');
+ },
+
+ clean: function(){
+ return this.replace(/\s+/g, ' ').trim();
+ },
+
+ camelCase: function(){
+ return this.replace(/-\D/g, function(match){
+ return match.charAt(1).toUpperCase();
+ });
+ },
+
+ hyphenate: function(){
+ return this.replace(/[A-Z]/g, function(match){
+ return ('-' + match.charAt(0).toLowerCase());
+ });
+ },
+
+ capitalize: function(){
+ return this.replace(/\b[a-z]/g, function(match){
+ return match.toUpperCase();
+ });
+ },
+
+ escapeRegExp: function(){
+ return this.replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1');
+ },
+
+ toInt: function(base){
+ return parseInt(this, base || 10);
+ },
+
+ toFloat: function(){
+ return parseFloat(this);
+ },
+
+ hexToRgb: function(array){
+ var hex = this.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
+ return (hex) ? hex.slice(1).hexToRgb(array) : null;
+ },
+
+ rgbToHex: function(array){
+ var rgb = this.match(/\d{1,3}/g);
+ return (rgb) ? rgb.rgbToHex(array) : null;
+ },
+
+ substitute: function(object, regexp){
+ return this.replace(regexp || (/\\?\{([^{}]+)\}/g), function(match, name){
+ if (match.charAt(0) == '\\') return match.slice(1);
+ return (object[name] != null) ? object[name] : '';
+ });
+ }
+
+});
+
+
+/*
+---
+
+name: Number
+
+description: Contains Number Prototypes like limit, round, times, and ceil.
+
+license: MIT-style license.
+
+requires: Type
+
+provides: Number
+
+...
+*/
+
+Number.implement({
+
+ limit: function(min, max){
+ return Math.min(max, Math.max(min, this));
+ },
+
+ round: function(precision){
+ precision = Math.pow(10, precision || 0).toFixed(precision < 0 ? -precision : 0);
+ return Math.round(this * precision) / precision;
+ },
+
+ times: function(fn, bind){
+ for (var i = 0; i < this; i++) fn.call(bind, i, this);
+ },
+
+ toFloat: function(){
+ return parseFloat(this);
+ },
+
+ toInt: function(base){
+ return parseInt(this, base || 10);
+ }
+
+});
+
+Number.alias('each', 'times');
+
+(function(math){
+ var methods = {};
+ math.each(function(name){
+ if (!Number[name]) methods[name] = function(){
+ return Math[name].apply(null, [this].concat(Array.from(arguments)));
+ };
+ });
+ Number.implement(methods);
+})(['abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor', 'log', 'max', 'min', 'pow', 'sin', 'sqrt', 'tan']);
+
+
+/*
+---
+
+name: Function
+
+description: Contains Function Prototypes like create, bind, pass, and delay.
+
+license: MIT-style license.
+
+requires: Type
+
+provides: Function
+
+...
+*/
+
+Function.extend({
+
+ attempt: function(){
+ for (var i = 0, l = arguments.length; i < l; i++){
+ try {
+ return arguments[i]();
+ } catch (e){}
+ }
+ return null;
+ }
+
+});
+
+Function.implement({
+
+ attempt: function(args, bind){
+ try {
+ return this.apply(bind, Array.from(args));
+ } catch (e){}
+
+ return null;
+ },
+
+ /**/
+ bind: function(bind){
+ var self = this,
+ args = (arguments.length > 1) ? Array.slice(arguments, 1) : null;
+
+ return function(){
+ if (!args && !arguments.length) return self.call(bind);
+ if (args && arguments.length) return self.apply(bind, args.concat(Array.from(arguments)));
+ return self.apply(bind, args || arguments);
+ };
+ },
+ /*!ES5>*/
+
+ pass: function(args, bind){
+ var self = this;
+ if (args != null) args = Array.from(args);
+ return function(){
+ return self.apply(bind, args || arguments);
+ };
+ },
+
+ delay: function(delay, bind, args){
+ return setTimeout(this.pass((args == null ? [] : args), bind), delay);
+ },
+
+ periodical: function(periodical, bind, args){
+ return setInterval(this.pass((args == null ? [] : args), bind), periodical);
+ }
+
+});
+
+
+
+
+/*
+---
+
+name: Object
+
+description: Object generic methods
+
+license: MIT-style license.
+
+requires: Type
+
+provides: [Object, Hash]
+
+...
+*/
+
+(function(){
+
+var hasOwnProperty = Object.prototype.hasOwnProperty;
+
+Object.extend({
+
+ subset: function(object, keys){
+ var results = {};
+ for (var i = 0, l = keys.length; i < l; i++){
+ var k = keys[i];
+ if (k in object) results[k] = object[k];
+ }
+ return results;
+ },
+
+ map: function(object, fn, bind){
+ var results = {};
+ for (var key in object){
+ if (hasOwnProperty.call(object, key)) results[key] = fn.call(bind, object[key], key, object);
+ }
+ return results;
+ },
+
+ filter: function(object, fn, bind){
+ var results = {};
+ for (var key in object){
+ var value = object[key];
+ if (hasOwnProperty.call(object, key) && fn.call(bind, value, key, object)) results[key] = value;
+ }
+ return results;
+ },
+
+ every: function(object, fn, bind){
+ for (var key in object){
+ if (hasOwnProperty.call(object, key) && !fn.call(bind, object[key], key)) return false;
+ }
+ return true;
+ },
+
+ some: function(object, fn, bind){
+ for (var key in object){
+ if (hasOwnProperty.call(object, key) && fn.call(bind, object[key], key)) return true;
+ }
+ return false;
+ },
+
+ keys: function(object){
+ var keys = [];
+ for (var key in object){
+ if (hasOwnProperty.call(object, key)) keys.push(key);
+ }
+ return keys;
+ },
+
+ values: function(object){
+ var values = [];
+ for (var key in object){
+ if (hasOwnProperty.call(object, key)) values.push(object[key]);
+ }
+ return values;
+ },
+
+ getLength: function(object){
+ return Object.keys(object).length;
+ },
+
+ keyOf: function(object, value){
+ for (var key in object){
+ if (hasOwnProperty.call(object, key) && object[key] === value) return key;
+ }
+ return null;
+ },
+
+ contains: function(object, value){
+ return Object.keyOf(object, value) != null;
+ },
+
+ toQueryString: function(object, base){
+ var queryString = [];
+
+ Object.each(object, function(value, key){
+ if (base) key = base + '[' + key + ']';
+ var result;
+ switch (typeOf(value)){
+ case 'object': result = Object.toQueryString(value, key); break;
+ case 'array':
+ var qs = {};
+ value.each(function(val, i){
+ qs[i] = val;
+ });
+ result = Object.toQueryString(qs, key);
+ break;
+ default: result = key + '=' + encodeURIComponent(value);
+ }
+ if (value != null) queryString.push(result);
+ });
+
+ return queryString.join('&');
+ }
+
+});
+
+})();
+
+
+
+
+/*
+---
+
+name: Browser
+
+description: The Browser Object. Contains Browser initialization, Window and Document, and the Browser Hash.
+
+license: MIT-style license.
+
+requires: [Array, Function, Number, String]
+
+provides: [Browser, Window, Document]
+
+...
+*/
+
+(function(){
+
+var document = this.document;
+var window = document.window = this;
+
+var UID = 1;
+
+this.$uid = (window.ActiveXObject) ? function(item){
+ return (item.uid || (item.uid = [UID++]))[0];
+} : function(item){
+ return item.uid || (item.uid = UID++);
+};
+
+$uid(window);
+$uid(document);
+
+var ua = navigator.userAgent.toLowerCase(),
+ platform = navigator.platform.toLowerCase(),
+ UA = ua.match(/(opera|ie|firefox|chrome|version)[\s\/:]([\w\d\.]+)?.*?(safari|version[\s\/:]([\w\d\.]+)|$)/) || [null, 'unknown', 0],
+ mode = UA[1] == 'ie' && document.documentMode;
+
+var Browser = this.Browser = {
+
+ extend: Function.prototype.extend,
+
+ name: (UA[1] == 'version') ? UA[3] : UA[1],
+
+ version: mode || parseFloat((UA[1] == 'opera' && UA[4]) ? UA[4] : UA[2]),
+
+ Platform: {
+ name: ua.match(/ip(?:ad|od|hone)/) ? 'ios' : (ua.match(/(?:webos|android)/) || platform.match(/mac|win|linux/) || ['other'])[0]
+ },
+
+ Features: {
+ xpath: !!(document.evaluate),
+ air: !!(window.runtime),
+ query: !!(document.querySelector),
+ json: !!(window.JSON)
+ },
+
+ Plugins: {}
+
+};
+
+Browser[Browser.name] = true;
+Browser[Browser.name + parseInt(Browser.version, 10)] = true;
+Browser.Platform[Browser.Platform.name] = true;
+
+// Request
+
+Browser.Request = (function(){
+
+ var XMLHTTP = function(){
+ return new XMLHttpRequest();
+ };
+
+ var MSXML2 = function(){
+ return new ActiveXObject('MSXML2.XMLHTTP');
+ };
+
+ var MSXML = function(){
+ return new ActiveXObject('Microsoft.XMLHTTP');
+ };
+
+ return Function.attempt(function(){
+ XMLHTTP();
+ return XMLHTTP;
+ }, function(){
+ MSXML2();
+ return MSXML2;
+ }, function(){
+ MSXML();
+ return MSXML;
+ });
+
+})();
+
+Browser.Features.xhr = !!(Browser.Request);
+
+// Flash detection
+
+var version = (Function.attempt(function(){
+ return navigator.plugins['Shockwave Flash'].description;
+}, function(){
+ return new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
+}) || '0 r0').match(/\d+/g);
+
+Browser.Plugins.Flash = {
+ version: Number(version[0] || '0.' + version[1]) || 0,
+ build: Number(version[2]) || 0
+};
+
+// String scripts
+
+Browser.exec = function(text){
+ if (!text) return text;
+ if (window.execScript){
+ window.execScript(text);
+ } else {
+ var script = document.createElement('script');
+ script.setAttribute('type', 'text/javascript');
+ script.text = text;
+ document.head.appendChild(script);
+ document.head.removeChild(script);
+ }
+ return text;
+};
+
+String.implement('stripScripts', function(exec){
+ var scripts = '';
+ var text = this.replace(/
+
+
+
+
+
+
+
+
+