").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.expr.filters.animated = function( elem ) {
+ return jQuery.grep(jQuery.timers, function( fn ) {
+ return elem === fn.elem;
+ }).length;
+};
+
+
+
+
+
+var docElem = window.document.documentElement;
+
+/**
+ * Gets a window from an element
+ */
+function getWindow( elem ) {
+ return jQuery.isWindow( elem ) ?
+ elem :
+ elem.nodeType === 9 ?
+ elem.defaultView || elem.parentWindow :
+ false;
+}
+
+jQuery.offset = {
+ setOffset: function( elem, options, i ) {
+ var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition,
+ position = jQuery.css( elem, "position" ),
+ curElem = jQuery( elem ),
+ props = {};
+
+ // set position first, in-case top/left are set even on static elem
+ if ( position === "static" ) {
+ elem.style.position = "relative";
+ }
+
+ curOffset = curElem.offset();
+ curCSSTop = jQuery.css( elem, "top" );
+ curCSSLeft = jQuery.css( elem, "left" );
+ calculatePosition = ( position === "absolute" || position === "fixed" ) &&
+ jQuery.inArray("auto", [ curCSSTop, curCSSLeft ] ) > -1;
+
+ // need to be able to calculate position if either top or left is auto and position is either absolute or fixed
+ if ( calculatePosition ) {
+ curPosition = curElem.position();
+ curTop = curPosition.top;
+ curLeft = curPosition.left;
+ } else {
+ curTop = parseFloat( curCSSTop ) || 0;
+ curLeft = parseFloat( curCSSLeft ) || 0;
+ }
+
+ if ( jQuery.isFunction( options ) ) {
+ options = options.call( elem, i, curOffset );
+ }
+
+ if ( options.top != null ) {
+ props.top = ( options.top - curOffset.top ) + curTop;
+ }
+ if ( options.left != null ) {
+ props.left = ( options.left - curOffset.left ) + curLeft;
+ }
+
+ if ( "using" in options ) {
+ options.using.call( elem, props );
+ } else {
+ curElem.css( props );
+ }
+ }
+};
+
+jQuery.fn.extend({
+ offset: function( options ) {
+ if ( arguments.length ) {
+ return options === undefined ?
+ this :
+ this.each(function( i ) {
+ jQuery.offset.setOffset( this, options, i );
+ });
+ }
+
+ var docElem, win,
+ box = { top: 0, left: 0 },
+ elem = this[ 0 ],
+ doc = elem && elem.ownerDocument;
+
+ if ( !doc ) {
+ return;
+ }
+
+ docElem = doc.documentElement;
+
+ // Make sure it's not a disconnected DOM node
+ if ( !jQuery.contains( docElem, elem ) ) {
+ return box;
+ }
+
+ // If we don't have gBCR, just use 0,0 rather than error
+ // BlackBerry 5, iOS 3 (original iPhone)
+ if ( typeof elem.getBoundingClientRect !== strundefined ) {
+ box = elem.getBoundingClientRect();
+ }
+ win = getWindow( doc );
+ return {
+ top: box.top + ( win.pageYOffset || docElem.scrollTop ) - ( docElem.clientTop || 0 ),
+ left: box.left + ( win.pageXOffset || docElem.scrollLeft ) - ( docElem.clientLeft || 0 )
+ };
+ },
+
+ position: function() {
+ if ( !this[ 0 ] ) {
+ return;
+ }
+
+ var offsetParent, offset,
+ parentOffset = { top: 0, left: 0 },
+ elem = this[ 0 ];
+
+ // fixed elements are offset from window (parentOffset = {top:0, left: 0}, because it is its only offset parent
+ if ( jQuery.css( elem, "position" ) === "fixed" ) {
+ // we assume that getBoundingClientRect is available when computed position is fixed
+ offset = elem.getBoundingClientRect();
+ } else {
+ // Get *real* offsetParent
+ offsetParent = this.offsetParent();
+
+ // Get correct offsets
+ offset = this.offset();
+ if ( !jQuery.nodeName( offsetParent[ 0 ], "html" ) ) {
+ parentOffset = offsetParent.offset();
+ }
+
+ // Add offsetParent borders
+ parentOffset.top += jQuery.css( offsetParent[ 0 ], "borderTopWidth", true );
+ parentOffset.left += jQuery.css( offsetParent[ 0 ], "borderLeftWidth", true );
+ }
+
+ // Subtract parent offsets and element margins
+ // note: when an element has margin: auto the offsetLeft and marginLeft
+ // are the same in Safari causing offset.left to incorrectly be 0
+ return {
+ top: offset.top - parentOffset.top - jQuery.css( elem, "marginTop", true ),
+ left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true)
+ };
+ },
+
+ offsetParent: function() {
+ return this.map(function() {
+ var offsetParent = this.offsetParent || docElem;
+
+ while ( offsetParent && ( !jQuery.nodeName( offsetParent, "html" ) && jQuery.css( offsetParent, "position" ) === "static" ) ) {
+ offsetParent = offsetParent.offsetParent;
+ }
+ return offsetParent || docElem;
+ });
+ }
+});
+
+// Create scrollLeft and scrollTop methods
+jQuery.each( { scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function( method, prop ) {
+ var top = /Y/.test( prop );
+
+ jQuery.fn[ method ] = function( val ) {
+ return access( this, function( elem, method, val ) {
+ var win = getWindow( elem );
+
+ if ( val === undefined ) {
+ return win ? (prop in win) ? win[ prop ] :
+ win.document.documentElement[ method ] :
+ elem[ method ];
+ }
+
+ if ( win ) {
+ win.scrollTo(
+ !top ? val : jQuery( win ).scrollLeft(),
+ top ? val : jQuery( win ).scrollTop()
+ );
+
+ } else {
+ elem[ method ] = val;
+ }
+ }, method, val, arguments.length, null );
+ };
+});
+
+// Add the top/left cssHooks using jQuery.fn.position
+// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
+// getComputedStyle returns percent when specified for top/left/bottom/right
+// rather than make the css module depend on the offset module, we just check for it here
+jQuery.each( [ "top", "left" ], function( i, prop ) {
+ jQuery.cssHooks[ prop ] = addGetHookIf( support.pixelPosition,
+ function( elem, computed ) {
+ if ( computed ) {
+ computed = curCSS( elem, prop );
+ // if curCSS returns percentage, fallback to offset
+ return rnumnonpx.test( computed ) ?
+ jQuery( elem ).position()[ prop ] + "px" :
+ computed;
+ }
+ }
+ );
+});
+
+
+// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
+jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
+ jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name }, function( defaultExtra, funcName ) {
+ // margin is only for outerHeight, outerWidth
+ jQuery.fn[ funcName ] = function( margin, value ) {
+ var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
+ extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );
+
+ return access( this, function( elem, type, value ) {
+ var doc;
+
+ if ( jQuery.isWindow( elem ) ) {
+ // As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there
+ // isn't a whole lot we can do. See pull request at this URL for discussion:
+ // https://github.com/jquery/jquery/pull/764
+ return elem.document.documentElement[ "client" + name ];
+ }
+
+ // Get document width or height
+ if ( elem.nodeType === 9 ) {
+ doc = elem.documentElement;
+
+ // Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height], whichever is greatest
+ // unfortunately, this causes bug #3838 in IE6/8 only, but there is currently no good, small way to fix it.
+ return Math.max(
+ elem.body[ "scroll" + name ], doc[ "scroll" + name ],
+ elem.body[ "offset" + name ], doc[ "offset" + name ],
+ doc[ "client" + name ]
+ );
+ }
+
+ return value === undefined ?
+ // Get width or height on the element, requesting but not forcing parseFloat
+ jQuery.css( elem, type, extra ) :
+
+ // Set width or height on the element
+ jQuery.style( elem, type, value, extra );
+ }, type, chainable ? margin : undefined, chainable, null );
+ };
+ });
+});
+
+
+// The number of elements contained in the matched element set
+jQuery.fn.size = function() {
+ return this.length;
+};
+
+jQuery.fn.andSelf = jQuery.fn.addBack;
+
+
+
+
+// Register as a named AMD module, since jQuery can be concatenated with other
+// files that may use define, but not via a proper concatenation script that
+// understands anonymous AMD modules. A named AMD is safest and most robust
+// way to register. Lowercase jquery is used because AMD module names are
+// derived from file names, and jQuery is normally delivered in a lowercase
+// file name. Do this after creating the global so that if an AMD module wants
+// to call noConflict to hide this version of jQuery, it will work.
+
+// Note that for maximum portability, libraries that are not jQuery should
+// declare themselves as anonymous modules, and avoid setting a global if an
+// AMD loader is present. jQuery is a special case. For more information, see
+// https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon
+
+if ( typeof define === "function" && define.amd ) {
+ define( "jquery", [], function() {
+ return jQuery;
+ });
+}
+
+
+
+
+var
+ // Map over jQuery in case of overwrite
+ _jQuery = window.jQuery,
+
+ // Map over the $ in case of overwrite
+ _$ = window.$;
+
+jQuery.noConflict = function( deep ) {
+ if ( window.$ === jQuery ) {
+ window.$ = _$;
+ }
+
+ if ( deep && window.jQuery === jQuery ) {
+ window.jQuery = _jQuery;
+ }
+
+ return jQuery;
+};
+
+// Expose jQuery and $ identifiers, even in
+// AMD (#7102#comment:10, https://github.com/jquery/jquery/pull/557)
+// and CommonJS for browser emulators (#13566)
+if ( typeof noGlobal === strundefined ) {
+ window.jQuery = window.$ = jQuery;
+}
+
+
+
+
+return jQuery;
+
+}));
\ No newline at end of file
diff --git a/nano/scripts/libraries/jquery.timers.js b/nano/scripts/libraries/jquery.timers.js
new file mode 100644
index 0000000000..ce66eaf476
--- /dev/null
+++ b/nano/scripts/libraries/jquery.timers.js
@@ -0,0 +1,138 @@
+/**
+ * jQuery.timers - Timer abstractions for jQuery
+ * Written by Blair Mitchelmore (blair DOT mitchelmore AT gmail DOT com)
+ * Licensed under the WTFPL (http://sam.zoy.org/wtfpl/).
+ * Date: 2009/10/16
+ *
+ * @author Blair Mitchelmore
+ * @version 1.2
+ *
+ **/
+
+jQuery.fn.extend({
+ everyTime: function(interval, label, fn, times) {
+ return this.each(function() {
+ jQuery.timer.add(this, interval, label, fn, times);
+ });
+ },
+ oneTime: function(interval, label, fn) {
+ return this.each(function() {
+ jQuery.timer.add(this, interval, label, fn, 1);
+ });
+ },
+ stopTime: function(label, fn) {
+ return this.each(function() {
+ jQuery.timer.remove(this, label, fn);
+ });
+ }
+});
+
+jQuery.extend({
+ timer: {
+ global: [],
+ guid: 1,
+ dataKey: "jQuery.timer",
+ regex: /^([0-9]+(?:\.[0-9]*)?)\s*(.*s)?$/,
+ powers: {
+ // Yeah this is major overkill...
+ 'ms': 1,
+ 'cs': 10,
+ 'ds': 100,
+ 's': 1000,
+ 'das': 10000,
+ 'hs': 100000,
+ 'ks': 1000000
+ },
+ timeParse: function(value) {
+ if (value == undefined || value == null)
+ return null;
+ var result = this.regex.exec(jQuery.trim(value.toString()));
+ if (result[2]) {
+ var num = parseFloat(result[1]);
+ var mult = this.powers[result[2]] || 1;
+ return num * mult;
+ } else {
+ return value;
+ }
+ },
+ add: function(element, interval, label, fn, times) {
+ var counter = 0;
+
+ if (jQuery.isFunction(label)) {
+ if (!times)
+ times = fn;
+ fn = label;
+ label = interval;
+ }
+
+ interval = jQuery.timer.timeParse(interval);
+
+ if (typeof interval != 'number' || isNaN(interval) || interval < 0)
+ return;
+
+ if (typeof times != 'number' || isNaN(times) || times < 0)
+ times = 0;
+
+ times = times || 0;
+
+ var timers = jQuery.data(element, this.dataKey) || jQuery.data(element, this.dataKey, {});
+
+ if (!timers[label])
+ timers[label] = {};
+
+ fn.timerID = fn.timerID || this.guid++;
+
+ var handler = function() {
+ if ((++counter > times && times !== 0) || fn.call(element, counter) === false)
+ jQuery.timer.remove(element, label, fn);
+ };
+
+ handler.timerID = fn.timerID;
+
+ if (!timers[label][fn.timerID])
+ timers[label][fn.timerID] = window.setInterval(handler,interval);
+
+ this.global.push( element );
+
+ },
+ remove: function(element, label, fn) {
+ var timers = jQuery.data(element, this.dataKey), ret;
+
+ if ( timers ) {
+
+ if (!label) {
+ for ( label in timers )
+ this.remove(element, label, fn);
+ } else if ( timers[label] ) {
+ if ( fn ) {
+ if ( fn.timerID ) {
+ window.clearInterval(timers[label][fn.timerID]);
+ delete timers[label][fn.timerID];
+ }
+ } else {
+ for ( var fn in timers[label] ) {
+ window.clearInterval(timers[label][fn]);
+ delete timers[label][fn];
+ }
+ }
+
+ for ( ret in timers[label] ) break;
+ if ( !ret ) {
+ ret = null;
+ delete timers[label];
+ }
+ }
+
+ for ( ret in timers ) break;
+ if ( !ret )
+ jQuery.removeData(element, this.dataKey);
+ }
+ }
+ }
+});
+
+jQuery(window).bind("unload", function() {
+ jQuery.each(jQuery.timer.global, function(index, item) {
+ jQuery.timer.remove(item);
+ });
+});
\ No newline at end of file
diff --git a/nano/scripts/nano/nano_base_callbacks.js b/nano/scripts/nano/nano_base_callbacks.js
new file mode 100644
index 0000000000..42a901c478
--- /dev/null
+++ b/nano/scripts/nano/nano_base_callbacks.js
@@ -0,0 +1,112 @@
+// NanoBaseCallbacks is where the base callbacks (common to all templates) are stored
+NanoBaseCallbacks = function ()
+{
+ // _canClick is used to disable clicks for a short period after each click (to avoid mis-clicks)
+ var _canClick = true;
+ var _baseBeforeUpdateCallbacks = {};
+ var _baseAfterUpdateCallbacks = {
+ // this callback is triggered after new data is processed
+ // it updates the status/visibility icon and adds click event handling to buttons/links
+ status: function (updateData) {
+ var uiStatusClass;
+ if (updateData['config']['status'] == 2)
+ {
+ uiStatusClass = 'good';
+ $('.linkActive').removeClass('inactive');
+ }
+ else if (updateData['config']['status'] == 1)
+ {
+ uiStatusClass = 'average';
+ $('.linkActive').addClass('inactive');
+ }
+ else
+ {
+ uiStatusClass = 'bad';
+ $('.linkActive').addClass('inactive');
+ }
+ $('.statusicon').removeClass("good bad average").addClass(uiStatusClass);
+ $('.linkActive').stopTime('linkPending');
+ $('.linkActive').removeClass('linkPending');
+
+ $('.linkActive')
+ .off('click')
+ .on('click', function (event) {
+ event.preventDefault();
+ var href = $(this).data('href');
+ if(href != null && _canClick)
+ {
+ _canClick = false;
+ $('body').oneTime(300, 'enableClick', function () {
+ _canClick = true;
+ });
+ if (updateData['config']['status'] == 2)
+ {
+ $(this).oneTime(300, 'linkPending', function () {
+ $(this).addClass('linkPending');
+ });
+ }
+ window.location.href = href;
+ }
+ });
+
+ return updateData;
+ },
+ nanomap: function (updateData) {
+ $('.mapIcon')
+ .off('mouseenter mouseleave')
+ .on('mouseenter',
+ function (event) {
+ var self = this;
+ $('#uiMapTooltip')
+ .html($(this).children('.tooltip').html())
+ .show()
+ .stopTime()
+ .oneTime(5000, 'hideTooltip', function () {
+ $(this).fadeOut(500);
+ });
+ }
+ );
+
+ $('.zoomLink')
+ .off('click')
+ .on('click', function (event) {
+ event.preventDefault();
+ var zoomLevel = $(this).data('zoomLevel');
+ var uiMapObject = $('#uiMap');
+ var uiMapWidth = uiMapObject.width() * zoomLevel;
+ var uiMapHeight = uiMapObject.height() * zoomLevel;
+
+ uiMapObject.css({
+ zoom: zoomLevel,
+ left: '50%',
+ top: '50%',
+ marginLeft: '-' + Math.floor(uiMapWidth / 2) + 'px',
+ marginTop: '-' + Math.floor(uiMapHeight / 2) + 'px'
+ });
+ });
+
+ $('#uiMapImage').attr('src', 'nanomap_z' + updateData['config']['mapZLevel'] + '.png');
+
+ return updateData;
+ }
+ };
+
+ return {
+ addCallbacks: function () {
+ NanoStateManager.addBeforeUpdateCallbacks(_baseBeforeUpdateCallbacks);
+ NanoStateManager.addAfterUpdateCallbacks(_baseAfterUpdateCallbacks);
+ },
+ removeCallbacks: function () {
+ for (var callbackKey in _baseBeforeUpdateCallbacks) {
+ if (_baseBeforeUpdateCallbacks.hasOwnProperty(callbackKey)) {
+ NanoStateManager.removeBeforeUpdateCallback(callbackKey);
+ }
+ }
+ for (var callbackKey in _baseAfterUpdateCallbacks) {
+ if (_baseAfterUpdateCallbacks.hasOwnProperty(callbackKey)) {
+ NanoStateManager.removeAfterUpdateCallback(callbackKey);
+ }
+ }
+ }
+ };
+}();
diff --git a/nano/scripts/nano/nano_base_helpers.js b/nano/scripts/nano/nano_base_helpers.js
new file mode 100644
index 0000000000..1b7fe43032
--- /dev/null
+++ b/nano/scripts/nano/nano_base_helpers.js
@@ -0,0 +1,283 @@
+// NanoBaseHelpers is where the base template helpers (common to all templates) are stored
+NanoBaseHelpers = function ()
+{
+ var _baseHelpers = {
+ // change ui styling to "syndicate mode"
+ syndicateMode: function() {
+ $('body').css("background-color","#8f1414");
+ $('body').css("background-image","url('uiBackground-Syndicate.png')");
+ $('body').css("background-position","50% 0");
+ $('body').css("background-repeat","repeat-x");
+
+ $('#uiTitleFluff').css("background-image","url('uiTitleFluff-Syndicate.png')");
+ $('#uiTitleFluff').css("background-position","50% 50%");
+ $('#uiTitleFluff').css("background-repeat", "no-repeat");
+
+ return '';
+ },
+
+ combine: function( arr1, arr2 ) {
+ return arr1 && arr2 ? arr1.concat(arr2) : arr1 || arr2;
+ },
+ dump: function( arr1 ) {
+ return JSON.stringify(arr1);
+ },
+
+ // Generate a Byond link
+ link: function( text, icon, parameters, status, elementClass, elementId ) {
+ var iconHtml = '';
+ var iconClass = 'noIcon';
+ if (typeof icon != 'undefined' && icon)
+ {
+ iconHtml = '
';
+ iconClass = 'hasIcon';
+ }
+
+ if (typeof elementClass == 'undefined' || !elementClass)
+ {
+ elementClass = 'link';
+ }
+
+ var elementIdHtml = '';
+ if (typeof elementId != 'undefined' && elementId)
+ {
+ elementIdHtml = 'id="' + elementId + '"';
+ }
+
+ var tid = NanoTransition.allocID(elementIdHtml + "_" + text.toString().replace(/[^a-z0-9_]/gi, "_") + "_" + icon);
+
+ if (typeof status != 'undefined' && status)
+ {
+ return '
' + iconHtml + text + '
';
+ }
+
+ return '
' + iconHtml + text + '
';
+ },
+
+ xor: function (number,bit) {
+ return number ^ bit;
+ },
+
+ precisionRound: function (value, places) {
+ if(places == 0){
+ return Math.round(number);
+ }
+ var multiplier = Math.pow(10, places);
+ return (Math.round(value * multiplier) / multiplier);
+ },
+
+ // Round a number to the nearest integer
+ round: function(number) {
+ return Math.round(number);
+ },
+ fixed: function(number) {
+ return Math.round(number * 10) / 10;
+ },
+ // Round a number down to integer
+ floor: function(number) {
+ return Math.floor(number);
+ },
+ // Round a number up to integer
+ ceil: function(number) {
+ return Math.ceil(number);
+ },
+
+ // Format a string (~string("Hello {0}, how are {1}?", 'Martin', 'you') becomes "Hello Martin, how are you?")
+ string: function() {
+ if (arguments.length == 0)
+ {
+ return '';
+ }
+ else if (arguments.length == 1)
+ {
+ return arguments[0];
+ }
+ else if (arguments.length > 1)
+ {
+ stringArgs = [];
+ for (var i = 1; i < arguments.length; i++)
+ {
+ stringArgs.push(arguments[i]);
+ }
+ return arguments[0].format(stringArgs);
+ }
+ return '';
+ },
+ formatNumber: function(x) {
+ // From http://stackoverflow.com/questions/2901102/how-to-print-a-number-with-commas-as-thousands-separators-in-javascript
+ var parts = x.toString().split(".");
+ parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+ return parts.join(".");
+ },
+ // Capitalize the first letter of a string. From http://stackoverflow.com/questions/1026069/capitalize-the-first-letter-of-string-in-javascript
+ capitalizeFirstLetter: function( string ) {
+ return string.charAt(0).toUpperCase() + string.slice(1);
+ },
+ // Display a bar. Used to show health, capacity, etc.
+ displayBar: function( value, rangeMin, rangeMax, styleClass, showText ) {
+
+ if (rangeMin < rangeMax)
+ {
+ if (value < rangeMin)
+ {
+ value = rangeMin;
+ }
+ else if (value > rangeMax)
+ {
+ value = rangeMax;
+ }
+ }
+ else
+ {
+ if (value > rangeMin)
+ {
+ value = rangeMin;
+ }
+ else if (value < rangeMax)
+ {
+ value = rangeMax;
+ }
+ }
+
+ if (typeof styleClass == 'undefined' || !styleClass)
+ {
+ styleClass = '';
+ }
+
+ if (typeof showText == 'undefined' || !showText)
+ {
+ showText = '';
+ }
+
+ var percentage = Math.round((value - rangeMin) / (rangeMax - rangeMin) * 100);
+ var tid = NanoTransition.allocID(rangeMin + "_" + rangeMax);
+
+ return '
';
+ },
+ // Convert danger level to class (for the air alarm)
+ dangerToClass: function(level) {
+ if(level == 0) return 'good';
+ if(level == 1) return 'average';
+ return 'bad';
+ },
+ dangerToSpan: function(level) {
+ if(level == 0) return '"
Good"';
+ if(level == 1) return '"
Minor Alert"';
+ return '"
Major Alert"';
+ },
+ generateHref: function (parameters) {
+ var body = $('body'); // For some fucking reason, data is stored in the body tag.
+ _urlParameters = body.data('urlParameters');
+ var queryString = '?';
+
+ for (var key in _urlParameters)
+ {
+ if (_urlParameters.hasOwnProperty(key))
+ {
+ if (queryString !== '?')
+ {
+ queryString += ';';
+ }
+ queryString += key + '=' + _urlParameters[key];
+ }
+ }
+
+ for (var key in parameters)
+ {
+ if (parameters.hasOwnProperty(key))
+ {
+ if (queryString !== '?')
+ {
+ queryString += ';';
+ }
+ queryString += key + '=' + parameters[key];
+ }
+ }
+ return queryString;
+ },
+ // Display DNA Blocks (for the DNA Modifier UI)
+ displayDNABlocks: function(dnaString, selectedBlock, selectedSubblock, blockSize, paramKey) {
+ if (!dnaString)
+ {
+ return '
Please place a valid subject into the DNA modifier.
';
+ }
+
+ var characters = dnaString.split('');
+
+ var html = '
1
';
+ var block = 1;
+ var subblock = 1;
+ for (var index in characters) {
+ if (!characters.hasOwnProperty(index) || typeof characters[index] === 'object')
+ {
+ continue;
+ }
+
+ var parameters;
+ if (paramKey.toUpperCase() == 'UI'){
+ parameters = { 'selectUIBlock' : block, 'selectUISubblock' : subblock };
+ } else {
+ parameters = { 'selectSEBlock' : block, 'selectSESubblock' : subblock };
+ }
+
+ var status = 'linkActive';
+ if (block == selectedBlock && subblock == selectedSubblock) {
+ status = 'selected';
+ }
+
+ html += '
' + characters[index] + '
';
+
+ index++;
+ if (index % blockSize == 0 && index < characters.length) {
+ block++;
+ subblock = 1;
+ html += '
' + block + '
';
+ } else {
+ subblock++;
+ }
+ }
+
+ html += '
';
+
+ return html;
+ },
+ cMirror: function(textbox) {
+ var editor = CodeMirror.fromTextArea(document.getElementById(textbox), {
+ lineNumbers: true,
+ indentUnit: 4,
+ indentWithTabs: true,
+ theme: "lesser-dark"
+ });
+ },
+ smoothNumber: function(number) {
+ var tid = NanoTransition.allocID("n");
+
+ return '
';
+ },
+ smoothRound: function(number, places) {
+ var tid = NanoTransition.allocID(places);
+ if(places === undefined)
+ placed = 0;
+
+ return '
';
+ }
+ };
+
+ return {
+ addHelpers: function ()
+ {
+ NanoTemplate.addHelpers(_baseHelpers);
+ },
+
+ removeHelpers: function ()
+ {
+ for (var helperKey in _baseHelpers)
+ {
+ if (_baseHelpers.hasOwnProperty(helperKey))
+ {
+ NanoTemplate.removeHelper(helperKey);
+ }
+ }
+ }
+ };
+}();
diff --git a/nano/scripts/nano/nano_state.js b/nano/scripts/nano/nano_state.js
new file mode 100644
index 0000000000..db7fcf92a1
--- /dev/null
+++ b/nano/scripts/nano/nano_state.js
@@ -0,0 +1,107 @@
+// This is the base state class, it is not to be used directly
+
+function NanoStateClass() {}
+
+NanoStateClass.prototype.key = null;
+NanoStateClass.prototype.layoutRendered = false;
+NanoStateClass.prototype.contentRendered = false;
+NanoStateClass.prototype.mapInitialised = false;
+
+NanoStateClass.prototype.isCurrent = function () {
+ return NanoStateManager.getCurrentState() == this;
+};
+
+NanoStateClass.prototype.onAdd = function (previousState) {
+ // Do not add code here, add it to the 'default' state (nano_state_defaut.js) or create a new state and override this function
+
+ NanoBaseCallbacks.addCallbacks();
+ NanoBaseHelpers.addHelpers();
+};
+
+NanoStateClass.prototype.onRemove = function (nextState) {
+ // Do not add code here, add it to the 'default' state (nano_state_defaut.js) or create a new state and override this function
+
+ NanoBaseCallbacks.removeCallbacks();
+ NanoBaseHelpers.removeHelpers();
+};
+
+NanoStateClass.prototype.onBeforeUpdate = function (data) {
+ // Do not add code here, add it to the 'default' state (nano_state_defaut.js) or create a new state and override this function
+
+ data = NanoStateManager.executeBeforeUpdateCallbacks(data);
+
+ return data; // Return data to continue, return false to prevent onUpdate and onAfterUpdate
+};
+
+NanoStateClass.prototype.onUpdate = function (data) {
+ // Do not add code here, add it to the 'default' state (nano_state_defaut.js) or create a new state and override this function
+
+ try
+ {
+ if (!this.layoutRendered || (data['config'].hasOwnProperty('autoUpdateLayout') && data['config']['autoUpdateLayout']))
+ {
+ $("#uiLayout").html(NanoTemplate.parse('layout', data)); // render the 'mail' template to the #mainTemplate div
+ this.layoutRendered = true;
+ }
+ if (!this.contentRendered || (data['config'].hasOwnProperty('autoUpdateContent') && data['config']['autoUpdateContent']))
+ {
+ $("#uiContent").html(NanoTemplate.parse('main', data)); // render the 'mail' template to the #mainTemplate div
+ this.contentRendered = true;
+ }
+ if (NanoTemplate.templateExists('mapContent'))
+ {
+ if (!this.mapInitialised)
+ {
+ // Add drag functionality to the map ui
+ $('#uiMap').draggable();
+
+ $('#uiMapTooltip')
+ .off('click')
+ .on('click', function (event) {
+ event.preventDefault();
+ $(this).fadeOut(400);
+ });
+
+ this.mapInitialised = true;
+ }
+
+ $("#uiMapContent").html(NanoTemplate.parse('mapContent', data)); // render the 'mapContent' template to the #uiMapContent div
+
+ if (data['config'].hasOwnProperty('showMap') && data['config']['showMap'])
+ {
+ $('#uiContent').addClass('hidden');
+ $('#uiMapWrapper').removeClass('hidden');
+ }
+ else
+ {
+ $('#uiMapWrapper').addClass('hidden');
+ $('#uiContent').removeClass('hidden');
+ }
+ }
+ if (NanoTemplate.templateExists('mapHeader'))
+ {
+ $("#uiMapHeader").html(NanoTemplate.parse('mapHeader', data)); // render the 'mapHeader' template to the #uiMapHeader div
+ }
+ if (NanoTemplate.templateExists('mapFooter'))
+ {
+ $("#uiMapFooter").html(NanoTemplate.parse('mapFooter', data)); // render the 'mapFooter' template to the #uiMapFooter div
+ }
+ }
+ catch(error)
+ {
+ reportError('ERROR: An error occurred while rendering the UI: ' + error.message);
+ return;
+ }
+};
+
+NanoStateClass.prototype.onAfterUpdate = function (data) {
+ // Do not add code here, add it to the 'default' state (nano_state_defaut.js) or create a new state and override this function
+
+ NanoStateManager.executeAfterUpdateCallbacks(data);
+};
+
+NanoStateClass.prototype.alertText = function (text) {
+ // Do not add code here, add it to the 'default' state (nano_state_defaut.js) or create a new state and override this function
+
+ alert(text);
+};
diff --git a/nano/scripts/nano/nano_state_default.js b/nano/scripts/nano/nano_state_default.js
new file mode 100644
index 0000000000..65493b8c87
--- /dev/null
+++ b/nano/scripts/nano/nano_state_default.js
@@ -0,0 +1,14 @@
+
+NanoStateDefaultClass.inheritsFrom(NanoStateClass);
+var NanoStateDefault = new NanoStateDefaultClass();
+
+function NanoStateDefaultClass() {
+
+ this.key = 'default';
+
+ //this.parent.constructor.call(this);
+
+ this.key = this.key.toLowerCase();
+
+ NanoStateManager.addState(this);
+}
\ No newline at end of file
diff --git a/nano/scripts/nano/nano_state_manager.js b/nano/scripts/nano/nano_state_manager.js
new file mode 100644
index 0000000000..3cd988e5a9
--- /dev/null
+++ b/nano/scripts/nano/nano_state_manager.js
@@ -0,0 +1,226 @@
+// NanoStateManager handles data from the server and uses it to render templates
+NanoStateManager = function ()
+{
+ // _isInitialised is set to true when all of this ui's templates have been processed/rendered
+ var _isInitialised = false;
+ // the data for this ui
+ var _data = null;
+
+ // this is an array of callbacks which are called when new data arrives, before it is processed
+ var _beforeUpdateCallbacks = {};
+ // this is an array of callbacks which are called when new data arrives, before it is processed
+ var _afterUpdateCallbacks = {};
+
+ // this is an array of state objects, these can be used to provide custom javascript logic
+ var _states = {};
+ var _currentState = null;
+
+ // the init function is called when the ui has loaded
+ // this function sets up the templates and base functionality
+ var init = function ()
+ {
+ // We store initialData and templateData in the body tag, it's as good a place as any
+ _data = $('body').data('initialData');
+
+ if (_data == null || !_data.hasOwnProperty('config') || !_data.hasOwnProperty('data'))
+ {
+ reportError('Error: Initial data did not load correctly.');
+ }
+
+ var stateKey = 'default';
+ if (_data['config'].hasOwnProperty('stateKey') && _data['config']['stateKey'])
+ {
+ stateKey = _data['config']['stateKey'].toLowerCase();
+ }
+
+ NanoStateManager.setCurrentState(stateKey);
+
+ $(document).on('templatesLoaded', function () {
+ doUpdate(_data);
+
+ _isInitialised = true;
+ });
+ };
+
+ // Receive update data from the server
+ var receiveUpdateData = function (jsonString)
+ {
+ var updateData;
+
+ // reportError("recieveUpdateData called." + "
Type: " + typeof jsonString); //debug hook
+ try
+ {
+ // parse the JSON string from the server into a JSON object
+ updateData = jQuery.parseJSON(jsonString);
+ }
+ catch (error)
+ {
+ reportError("recieveUpdateData failed. " + "
Error name: " + error.name + "
Error Message: " + error.message);
+ return;
+ }
+
+ // reportError("recieveUpdateData passed trycatch block."); //debug hook
+
+ if (!updateData.hasOwnProperty('data'))
+ {
+ if (_data && _data.hasOwnProperty('data'))
+ {
+ updateData['data'] = _data['data'];
+ }
+ else
+ {
+ updateData['data'] = {};
+ }
+ }
+
+ if (_isInitialised) // all templates have been registered, so render them
+ {
+ doUpdate(updateData);
+ }
+ else
+ {
+ _data = updateData; // all templates have not been registered. We set _data directly here which will be applied after the template is loaded with the initial data
+ }
+ };
+
+ // This function does the update by calling the methods on the current state
+ var doUpdate = function (data)
+ {
+ if (_currentState == null)
+ {
+ return;
+ }
+
+ data = _currentState.onBeforeUpdate(data);
+
+ if (data === false)
+ {
+ reportError('data is false, return');
+ return; // A beforeUpdateCallback returned a false value, this prevents the render from occuring
+ }
+
+ _data = data;
+
+ _currentState.onUpdate(_data);
+
+ _currentState.onAfterUpdate(_data);
+ };
+
+ // Execute all callbacks in the callbacks array/object provided, updateData is passed to them for processing and potential modification
+ var executeCallbacks = function (callbacks, data)
+ {
+ for (var key in callbacks)
+ {
+ if (callbacks.hasOwnProperty(key) && jQuery.isFunction(callbacks[key]))
+ {
+ data = callbacks[key].call(this, data);
+ }
+ }
+
+ return data;
+ };
+
+ return {
+ init: function ()
+ {
+ init();
+ },
+ receiveUpdateData: function (jsonString)
+ {
+ receiveUpdateData(jsonString);
+ },
+ addBeforeUpdateCallback: function (key, callbackFunction)
+ {
+ _beforeUpdateCallbacks[key] = callbackFunction;
+ },
+ addBeforeUpdateCallbacks: function (callbacks) {
+ for (var callbackKey in callbacks) {
+ if (!callbacks.hasOwnProperty(callbackKey))
+ {
+ continue;
+ }
+ NanoStateManager.addBeforeUpdateCallback(callbackKey, callbacks[callbackKey]);
+ }
+ },
+ removeBeforeUpdateCallback: function (key)
+ {
+ if (_beforeUpdateCallbacks.hasOwnProperty(key))
+ {
+ delete _beforeUpdateCallbacks[key];
+ }
+ },
+ executeBeforeUpdateCallbacks: function (data) {
+ return executeCallbacks(_beforeUpdateCallbacks, data);
+ },
+ addAfterUpdateCallback: function (key, callbackFunction)
+ {
+ _afterUpdateCallbacks[key] = callbackFunction;
+ },
+ addAfterUpdateCallbacks: function (callbacks) {
+ for (var callbackKey in callbacks) {
+ if (!callbacks.hasOwnProperty(callbackKey))
+ {
+ continue;
+ }
+ NanoStateManager.addAfterUpdateCallback(callbackKey, callbacks[callbackKey]);
+ }
+ },
+ removeAfterUpdateCallback: function (key)
+ {
+ if (_afterUpdateCallbacks.hasOwnProperty(key))
+ {
+ delete _afterUpdateCallbacks[key];
+ }
+ },
+ executeAfterUpdateCallbacks: function (data) {
+ return executeCallbacks(_afterUpdateCallbacks, data);
+ },
+ addState: function (state)
+ {
+ if (!(state instanceof NanoStateClass))
+ {
+ reportError('ERROR: Attempted to add a state which is not instanceof NanoStateClass');
+ return;
+ }
+ if (!state.key)
+ {
+ reportError('ERROR: Attempted to add a state with an invalid stateKey');
+ return;
+ }
+ _states[state.key] = state;
+ },
+ setCurrentState: function (stateKey)
+ {
+ if (typeof stateKey == 'undefined' || !stateKey) {
+ reportError('ERROR: No state key was passed!');
+ return false;
+ }
+ if (!_states.hasOwnProperty(stateKey))
+ {
+ reportError('ERROR: Attempted to set a current state which does not exist: ' + stateKey);
+ return false;
+ }
+
+ var previousState = _currentState;
+
+ _currentState = _states[stateKey];
+
+ if (previousState != null) {
+ previousState.onRemove(_currentState);
+ }
+
+ _currentState.onAdd(previousState);
+
+ return true;
+ },
+ getCurrentState: function ()
+ {
+ return _currentState;
+ },
+ getData: function ()
+ {
+ return _data;
+ }
+ };
+} ();
+
\ No newline at end of file
diff --git a/nano/scripts/nano/nano_template.js b/nano/scripts/nano/nano_template.js
new file mode 100644
index 0000000000..7350e62d48
--- /dev/null
+++ b/nano/scripts/nano/nano_template.js
@@ -0,0 +1,129 @@
+
+var NanoTemplate = function () {
+
+ var _templateData = {};
+
+ var _templates = {};
+ var _compiledTemplates = {};
+
+ var _helpers = {};
+
+ var init = function () {
+ // We store templateData in the body tag, it's as good a place as any
+ _templateData = $('body').data('templateData');
+
+ if (_templateData == null)
+ {
+ reportError('Error: Template data did not load correctly.');
+ }
+
+ loadNextTemplate();
+ };
+
+ var loadNextTemplate = function () {
+ // we count the number of templates for this ui so that we know when they've all been rendered
+ var templateCount = Object.size(_templateData);
+
+ if (!templateCount)
+ {
+ $(document).trigger('templatesLoaded');
+ return;
+ }
+
+ // load markup for each template and register it
+ for (var key in _templateData)
+ {
+ if (!_templateData.hasOwnProperty(key))
+ {
+ continue;
+ }
+
+ $.when($.ajax({
+ url: _templateData[key],
+ cache: false,
+ dataType: 'text'
+ }))
+ .done( function(templateMarkup) {
+
+ templateMarkup += '
';
+
+ try {
+ NanoTemplate.addTemplate(key, templateMarkup);
+ }
+ catch(error) {
+ reportError('ERROR: An error occurred while loading the UI: ' + error.message);
+ return;
+ }
+
+ delete _templateData[key];
+ loadNextTemplate();
+ })
+ .fail( function () {
+ reportError('ERROR: Loading template ' + key + '(' + _templateData[key] + ') failed!');
+ });
+
+ return;
+ }
+ };
+
+ var compileTemplates = function () {
+ for (var key in _templates) {
+ try {
+ _compiledTemplates[key] = doT.template(_templates[key], null, _templates);
+ }
+ catch (error) {
+ reportError(error.message);
+ }
+ }
+ };
+
+ return {
+ init: function () {
+ init();
+ },
+ addTemplate: function (key, templateString) {
+ _templates[key] = templateString;
+ },
+ templateExists: function (key) {
+ return _templates.hasOwnProperty(key);
+ },
+ parse: function (templateKey, data) {
+ if (!_compiledTemplates.hasOwnProperty(templateKey) || !_compiledTemplates[templateKey]) {
+ if (!_templates.hasOwnProperty(templateKey)) {
+ reportError('ERROR: Template "' + templateKey + '" does not exist in _compiledTemplates!');
+ return '
Template error (does not exist)
';
+ }
+ compileTemplates();
+ }
+ if (typeof _compiledTemplates[templateKey] != 'function') {
+ reportError(_compiledTemplates[templateKey]);
+ reportError('ERROR: Template "' + templateKey + '" failed to compile!');
+ return '
Template error (failed to compile)
';
+ }
+ return _compiledTemplates[templateKey].call(this, data['data'], data['config'], _helpers);
+ },
+ addHelper: function (helperName, helperFunction) {
+ if (!jQuery.isFunction(helperFunction)) {
+ reportError('NanoTemplate.addHelper failed to add ' + helperName + ' as it is not a function.');
+ return;
+ }
+
+ _helpers[helperName] = helperFunction;
+ },
+ addHelpers: function (helpers) {
+ for (var helperName in helpers) {
+ if (!helpers.hasOwnProperty(helperName))
+ {
+ continue;
+ }
+ NanoTemplate.addHelper(helperName, helpers[helperName]);
+ }
+ },
+ removeHelper: function (helperName) {
+ if (helpers.hasOwnProperty(helperName))
+ {
+ delete _helpers[helperName];
+ }
+ }
+ };
+}();
diff --git a/nano/scripts/nano/nano_transition.js b/nano/scripts/nano/nano_transition.js
new file mode 100644
index 0000000000..d40d9575bf
--- /dev/null
+++ b/nano/scripts/nano/nano_transition.js
@@ -0,0 +1,182 @@
+NanoTransition = function() {
+ var idCache = {};
+ var stateCache = {};
+ var currentID = 0;
+
+ NanoStateManager.addBeforeUpdateCallback("TransitionReset", function(updateData) {
+ currentID = 0;
+ return updateData;
+ });
+
+ // get a transition handle, add this to your element's class
+ var _allocID = function(uniqueID) {
+ if(uniqueID == "_Hide_Map_close") // hack because nano does weird thing on first tick
+ return "transition__map";
+
+ if(uniqueID === undefined)
+ uniqueID = "";
+ return "transition__" + (currentID++) + "_" + uniqueID.toString();
+ }
+
+ // setup transitions, call this exactly once a refresh, returns old state
+ var _updateElement = function(id, newState, merge) {
+ var cache = idCache[id];
+ var oldState;
+
+ if(merge === undefined)
+ merge = false;
+
+ var element = document.getElementsByClassName(id);
+ if(element.length == 0)
+ return newState;
+
+ // verify that the id still points to the same thing
+ // and get old state
+ element = element[0];
+ if(cache
+ && cache.tagName == element.tagName
+ && cache.tabIndex == element.tabIndex
+ && cache.parent == element.parentNode.tagName
+ && cache.children == element.children.length) {
+
+ // yep, this is continuation of old object
+ oldState = stateCache[id];
+
+ if(merge) {
+ var newStateCopy = {};
+ for(k in oldState)
+ newStateCopy[k] = oldState[k];
+ for(k in newState)
+ newStateCopy[k] = newState[k];
+ newState = newStateCopy;
+ }
+ } else {
+ // nop!
+ oldState = newState;
+
+ idCache[id] = {
+ tagName: element.tagName,
+ tabIndex: element.tabIndex,
+ parent: element.parentNode.tagName,
+ children: element.children.length
+ };
+ }
+
+ // save new state for next cycle
+ stateCache[id] = newState;
+ return oldState;
+ }
+
+ var _resolveTarget = function(id, filter) {
+ var element = $("." + id);
+
+ return filter ? $(element).children(filter) : $(element);
+ }
+
+ // animate a state change
+ var _animateElement = function(id, filter, oldState, newState, time) {
+ var target = _resolveTarget(id, filter);
+
+ if(time === undefined)
+ time = 1900;
+
+ // do the animation
+ target
+ .css(oldState)
+ .animate(newState, {
+ duration: time,
+ queue: false
+ });
+
+ // maybe class too?
+ var oldClass = oldState["..class"];
+ var newClass = newState["..class"];
+ if(oldClass && newClass) {
+ target.addClass(oldClass);
+ if(oldClass != newClass) {
+ // strip classes that are in both
+ var oldClass = oldClass.split(" ");
+ var newClass = newClass.split(" ");
+
+ var filteredOldClass = oldClass.filter(function(x) { return newClass.indexOf(x) == -1; });
+ var filteredNewClass = newClass.filter(function(x) { return oldClass.indexOf(x) == -1; });
+
+ oldClass = filteredOldClass.join(" ");
+ newClass = filteredNewClass.join(" ");
+
+ target.switchClass(oldClass, newClass, {
+ duration: time,
+ queue: false
+ });
+ }
+ }
+ }
+
+ var _animateTextValue = function(id, filter, places, oldValue, newValue, time) {
+ var target = _resolveTarget(id, filter);
+
+ if(time === undefined)
+ time = 1900;
+
+ if(places == -1)
+ target.text(oldValue.toString());//oldValue.toString());
+ else
+ target.text(oldValue.toFixed(places));//oldValue.toFixed(places));
+
+ if(oldValue != newValue)
+ target.animate({i: 1}, {
+ duration: time,
+ queue: false,
+ step: function(now, fx) {
+ if(places == -1) {
+ fx.elem.textContent = (oldValue * (1 - now) + newValue * now).toString();
+ } else {
+ fx.elem.textContent = (oldValue * (1 - now) + newValue * now).toFixed(places);
+ }
+ }
+ });
+ }
+
+ var _animateHover = function(id, filter, oldState, newState, time) {
+ var target = _resolveTarget(id, filter);
+
+ if(time === undefined)
+ time = 200;
+
+ if(oldState["..hover"])
+ target.addClass("hover");
+
+ target.hover(
+ function() {
+ target.stop(1, 1).addClass("hover", {
+ duration: time,
+ queue: false
+ });
+ _updateElement(id, {"..hover": true}, true);
+ },
+ function() {
+ target.stop(1, 1).removeClass("hover", {
+ duration: time,
+ queue: false
+ });
+ _updateElement(id, {"..hover": false}, true);
+ }
+ );
+ }
+
+ // helper for most common use
+ var _transitionElement = function(id, filter, state, time) {
+ var old = _updateElement(id, state);
+ _animateElement(id, filter, old, state, time);
+ return old;
+ }
+
+ return {
+ allocID: _allocID,
+ updateElement: _updateElement,
+ animateElement: _animateElement,
+ animateTextValue: _animateTextValue,
+ animateHover: _animateHover,
+ transitionElement: _transitionElement
+ };
+}();
diff --git a/nano/scripts/nano/nano_utility.js b/nano/scripts/nano/nano_utility.js
new file mode 100644
index 0000000000..9e00304246
--- /dev/null
+++ b/nano/scripts/nano/nano_utility.js
@@ -0,0 +1,202 @@
+// NanoUtility is the place to store utility functions
+var NanoUtility = function () {
+ var _urlParameters = {}; // This is populated with the base url parameters (used by all links), which is probaby just the "src" parameter
+
+ return {
+ init: function () {
+ var body = $('body'); // We store data in the body tag, it's as good a place as any
+ _urlParameters = body.data('urlParameters');
+ },
+ // generate a Byond href, combines _urlParameters with parameters
+ generateHref: function (parameters) {
+ var queryString = '?';
+
+ for (var key in _urlParameters) {
+ if (_urlParameters.hasOwnProperty(key)) {
+ if (queryString !== '?') {
+ queryString += ';';
+ }
+ queryString += key + '=' + _urlParameters[key];
+ }
+ }
+
+ for (var key in parameters)
+ {
+ if (parameters.hasOwnProperty(key)) {
+ if (queryString !== '?') {
+ queryString += ';';
+ }
+ queryString += key + '=' + parameters[key];
+ }
+ }
+ return queryString;
+ },
+ winset: function (key, value, window) {
+ var obj, params, winsetRef;
+ if (window == null) {
+ window = NanoStateManager.getData().config.window.ref;
+ }
+ params = (
+ obj = {},
+ obj[window + "." + key] = value,
+ obj
+ );
+ return location.href = NanoUtility.href("winset", params);
+ },
+ extend: function(first, second) {
+ Object.keys(second).forEach(function(key) {
+ var secondVal;
+ secondVal = second[key];
+ if (secondVal && Object.prototype.toString.call(secondVal) === "[object Object]") {
+ first[key] = first[key] || {};
+ return NanoUtility.extend(first[key], secondVal);
+ } else {
+ return first[key] = secondVal;
+ }
+ });
+ return first;
+ },
+ href: function(url, params) {
+ if (url == null) {
+ url = "";
+ }
+ if (params == null) {
+ params = {};
+ }
+ url = new Url("byond://" + url);
+ NanoUtility.extend(url.query, params);
+ return url;
+ },
+ close: function() {
+ var params;
+ params = {
+ command: "nanoclose " + _urlParameters.src
+ };
+ this.winset("is-visible", "false");
+ return location.href = NanoUtility.href("winset", params);
+ }
+ }
+} ();
+
+if (typeof jQuery == 'undefined') {
+ reportError('ERROR: Javascript library failed to load!');
+}
+if (typeof doT == 'undefined') {
+ reportError('ERROR: Template engine failed to load!');
+}
+
+var reportError = function (str) {
+ window.location = "byond://?nano_err=" + encodeURIComponent(str);
+ alert(str);
+}
+
+// All scripts are initialised here, this allows control of init order
+$(document).ready(function () {
+ NanoUtility.init();
+ NanoStateManager.init();
+ NanoTemplate.init();
+ NanoWindow.init();
+});
+
+if (!Array.prototype.indexOf)
+{
+ Array.prototype.indexOf = function(elt /*, from*/)
+ {
+ var len = this.length;
+
+ var from = Number(arguments[1]) || 0;
+ from = (from < 0)
+ ? Math.ceil(from)
+ : Math.floor(from);
+ if (from < 0)
+ from += len;
+
+ for (; from < len; from++)
+ {
+ if (from in this &&
+ this[from] === elt)
+ return from;
+ }
+ return -1;
+ };
+};
+
+if (!String.prototype.format)
+{
+ String.prototype.format = function (args) {
+ var str = this;
+ return str.replace(String.prototype.format.regex, function(item) {
+ var intVal = parseInt(item.substring(1, item.length - 1));
+ var replace;
+ if (intVal >= 0) {
+ replace = args[intVal];
+ } else if (intVal === -1) {
+ replace = "{";
+ } else if (intVal === -2) {
+ replace = "}";
+ } else {
+ replace = "";
+ }
+ return replace;
+ });
+ };
+ String.prototype.format.regex = new RegExp("{-?[0-9]+}", "g");
+};
+
+Object.size = function(obj) {
+ var size = 0, key;
+ for (var key in obj) {
+ if (obj.hasOwnProperty(key)) size++;
+ }
+ return size;
+};
+
+if(!window.console) {
+ window.console = {
+ log : function(str) {
+ return false;
+ }
+ };
+};
+
+String.prototype.toTitleCase = function () {
+ var smallWords = /^(a|an|and|as|at|but|by|en|for|if|in|of|on|or|the|to|vs?\.?|via)$/i;
+
+ return this.replace(/([^\W_]+[^\s-]*) */g, function (match, p1, index, title) {
+ if (index > 0 && index + p1.length !== title.length &&
+ p1.search(smallWords) > -1 && title.charAt(index - 2) !== ":" &&
+ title.charAt(index - 1).search(/[^\s-]/) < 0) {
+ return match.toLowerCase();
+ }
+
+ if (p1.substr(1).search(/[A-Z]|\../) > -1) {
+ return match;
+ }
+
+ return match.charAt(0).toUpperCase() + match.substr(1);
+ });
+};
+
+$.ajaxSetup({
+ cache: false
+});
+
+Function.prototype.inheritsFrom = function (parentClassOrObject) {
+ this.prototype = new parentClassOrObject;
+ this.prototype.constructor = this;
+ this.prototype.parent = parentClassOrObject.prototype;
+ return this;
+};
+
+if (!String.prototype.trim) {
+ String.prototype.trim = function () {
+ return this.replace(/^\s+|\s+$/g, '');
+ };
+}
+
+// Replicate the ckey proc from BYOND
+if (!String.prototype.ckey) {
+ String.prototype.ckey = function () {
+ return this.replace(/\W/g, '').toLowerCase();
+ };
+}
\ No newline at end of file
diff --git a/nano/scripts/nano/nano_window.js b/nano/scripts/nano/nano_window.js
new file mode 100644
index 0000000000..b3e74ac87c
--- /dev/null
+++ b/nano/scripts/nano/nano_window.js
@@ -0,0 +1,120 @@
+var NanoWindow = function ()
+{
+ var setPos = function (x, y) {
+ NanoUtility.winset("pos", x + "," + y);
+ };
+ var setSize = function (w, h) {
+ NanoUtility.winset("size", w + "," + h);
+ };
+
+ var init = function () {
+ if(NanoStateManager.getData().config.user.fancy) {
+ fancyChrome();
+ attachButtons();
+ attachDragAndResize();
+ }
+ };
+ var fancyChrome = function() {
+ NanoUtility.winset("titlebar", 0);
+ NanoUtility.winset("can-resize", 0);
+ $('.fancy').show();
+ $('#uiTitleFluff').css("right", "65px");
+ };
+ var attachButtons = function() {
+ var close = function() {
+ return NanoUtility.close();
+ };
+ var minimize = function() {
+ return NanoUtility.winset("is-minimized", "true");
+ };
+ $(".close").on("click", function (event) {
+ close();
+ });
+ $(".minimize").on("click", function (event) {
+ minimize();
+ });
+ };
+ var attachDragAndResize = function() {
+ $(document).on("mousemove", function (event) {
+ drag();
+ resize();
+ });
+
+ $(document).on("mouseup", function (event) {
+ if(dragging) {
+ dragging = false;
+ xDrag = null;
+ yDrag = null;
+ }
+ if(resizing) {
+ resizing = false;
+ xResize = null;
+ yResize = null;
+ }
+ });
+ $("#uiTitleWrapper").on("mousedown", function (event) {
+ dragging = true;
+ });
+ $("#resize").on("mousedown", function (event) {
+ resizing = true;
+ });
+ };
+ var dragging, xDrag, yDrag;
+ var drag = function(event) {
+ var x, y;
+ if (event == null) {
+ event = window.event;
+ }
+
+ if (!dragging) {
+ return;
+ }
+
+ if (xDrag == null) {
+ xDrag = event.screenX;
+ }
+
+ if (yDrag == null) {
+ yDrag = event.screenY;
+ }
+
+ x = (event.screenX - xDrag) + (window.screenLeft);
+ y = (event.screenY - yDrag) + (window.screenTop);
+
+ setPos(x, y);
+ xDrag = event.screenX;
+ yDrag = event.screenY;
+ };
+ var resizing, xResize, yResize;
+ var resize = function() {
+ var x, y;
+ if (event == null) {
+ event = window.event;
+ }
+
+ if (!resizing) {
+ return;
+ }
+
+ if (xResize == null) {
+ xResize = event.screenX;
+ }
+
+ if (yResize == null) {
+ yResize = event.screenY;
+ }
+
+ x = Math.max(150, (event.screenX - xResize) + window.innerWidth);
+ y = Math.max(150, (event.screenY - yResize) + window.innerHeight);
+ setSize(x, y);
+ xResize = event.screenX;
+ yResize = event.screenY;
+ };
+ return {
+ init: function () { //crazy but ensures correct load order (nanoutil - nanotemplate - nanowindow)
+ $(document).on('templatesLoaded', function () {
+ init();
+ });
+ }
+ };
+}();
\ No newline at end of file
diff --git a/nano/styles/_bars.less b/nano/styles/_bars.less
new file mode 100644
index 0000000000..f79c2d05fd
--- /dev/null
+++ b/nano/styles/_bars.less
@@ -0,0 +1,57 @@
+.displayBar {
+ position: relative;
+ width: 236px;
+ height: 16px;
+ border: 1px solid #666666;
+ float: left;
+ margin: 0 5px 0 0;
+ overflow: hidden;
+ background: #000000;
+}
+
+.displayBarText {
+ position: absolute;
+ top: -2px;
+ left: 5px;
+ width: 100%;
+ height: 100%;
+ color: #ffffff;
+ font-weight: normal;
+}
+
+.displayBarFill {
+ width: 0%;
+ height: 100%;
+ background: #40628a;
+ overflow: hidden;
+ float: left;
+}
+
+.displayBarFill.alignRight {
+ float: right;
+}
+
+.displayBarFill.good {
+ color: #ffffff;
+ background: #4f7529;
+}
+
+.displayBarFill.notgood {
+ color: #ffffff;
+ background: #ffb400;
+}
+
+.displayBarFill.average {
+ color: #ffffff;
+ background: #cd6500;
+}
+
+.displayBarFill.bad {
+ color: #ffffff;
+ background: #ee0000;
+}
+
+.displayBarFill.highlight {
+ color: #ffffff;
+ background: #8BA5C4;
+}
\ No newline at end of file
diff --git a/nano/styles/_color.less b/nano/styles/_color.less
new file mode 100644
index 0000000000..599548036e
--- /dev/null
+++ b/nano/styles/_color.less
@@ -0,0 +1,119 @@
+.white {
+ color: white;
+ font-weight: bold;
+}
+
+.good {
+ color: #4f7529;
+ font-weight: bold;
+}
+
+.average {
+ color: #cd6500;
+ font-weight: bold;
+}
+
+.bad {
+ color: #ee0000;
+ font-weight: bold;
+}
+
+.idle {
+ color: #272727;
+ font-weight: bold;
+}
+
+.redButton {
+ background: #ea0000;
+}
+
+.yellowButton {
+ background: #cacc00;
+}
+
+.highlight {
+ color: #8BA5C4;
+}
+
+.dark {
+ color: #272727;
+}
+
+/* Damage colors for crew monitoring computer */
+.burn {
+ color: orange;
+}
+
+.brute {
+ color: red;
+}
+
+.toxin {
+ color: green;
+}
+
+.toxin_light {
+ color: #3ADF00;
+}
+
+.oxyloss {
+ color: blue;
+} /*#4444FF*/
+
+.oxyloss_light {
+ color: #6698FF;
+}
+
+/* Wire colors for nuclear bomb defusion */
+.Red {
+ color: #FF0000;
+}
+
+.Blue {
+ color: #0000FF;
+}
+
+.Green {
+ color: #00FF00;
+}
+
+.Marigold {
+ color: #FDA505;
+}
+
+.Fuschia {
+ color: #FF0080;
+}
+
+.Black {
+ color: #000000;
+}
+
+.Pearl {
+ color: #C6CACB;
+}
+
+/* Status bar colors for sleeper temperatures */
+.cold1 {
+ color: #94B8FF;
+}
+
+.cold2 {
+ color: #6699FF;
+}
+
+.cold3 {
+ color: #293D66;
+}
+
+.hot1 {
+ color: #FF9966;
+}
+
+.hot2 {
+ color: #993D00;
+}
+
+.hot3 {
+ color: #FF6600;
+}
\ No newline at end of file
diff --git a/nano/styles/_config.less b/nano/styles/_config.less
new file mode 100644
index 0000000000..a44ef50fd5
--- /dev/null
+++ b/nano/styles/_config.less
@@ -0,0 +1,7 @@
+// Fonts
+@font: Verdana, Geneva, sans-serif;
+@fontsize: 12px;
+
+// Body Colors
+@background-start: #2a2a2a;
+@background-end: #202020;
\ No newline at end of file
diff --git a/nano/styles/_formatting.less b/nano/styles/_formatting.less
new file mode 100644
index 0000000000..e8f65be969
--- /dev/null
+++ b/nano/styles/_formatting.less
@@ -0,0 +1,197 @@
+.itemGroup {
+ border: 1px solid #e9c183;
+ background: #2c2c2c;
+ padding: 4px;
+ clear: both;
+}
+
+
+.item {
+ width: 100%;
+ margin: 4px 0 0 0;
+ clear: both;
+ overflow: auto;
+}
+
+.itemContentNarrow, .itemContent {
+ float: left;
+}
+
+.itemContentNarrow {
+ width: 30%;
+}
+
+.itemContent {
+ width: 69%;
+}
+
+.itemLabelNarrow, .itemLabel, .itemLabelWide, .itemLabelWider, .itemLabelWidest {
+ float: left;
+ color: #e9c183;
+}
+
+.itemLabelNarrow {
+ width: 20%;
+}
+
+.itemLabel {
+ width: 30%;
+}
+
+.itemLabelWide {
+ width: 45%;
+}
+
+.itemLabelWider {
+ width: 69%;
+}
+
+.itemLabelWidest {
+ width: 100%;
+}
+
+.itemContentWide {
+ float: left;
+ width: 79%;
+}
+
+.itemContentSmall {
+ float: left;
+ width: 33%;
+}
+
+.itemContentMedium {
+ float: left;
+ width: 55%;
+}
+
+.block {
+ padding: 8px;
+ margin: 10px 4px 4px 4px;
+ border: 1px solid #40628a;
+ background-color: #202020;
+}
+
+.block h3 {
+ padding: 0;
+}
+
+.clearBoth {
+ clear: both;
+}
+
+.clearLeft {
+ clear: left;
+}
+
+.clearRight {
+ clear: right;
+}
+
+.line {
+ width: 100%;
+ clear: both;
+}
+
+.fixedLeft {
+ width: 110px;
+ float: left;
+}
+
+.fixedLeftWide {
+ width: 165px;
+ float: left;
+}
+
+.fixedLeftWider {
+ width: 220px;
+ float: left;
+}
+
+.fixedLeftWidest {
+ width: 250px;
+ float: left;
+}
+
+.floatRight {
+ float: right;
+}
+
+.floatLeft {
+ float: left;
+}
+
+.hidden {
+ display: none;
+}
+
+
+.statusDisplay {
+ background: rgba(0, 0, 0, 0.5);
+ color: #ffffff;
+ border: 1px solid #40628a;
+ padding: 4px;
+ margin: 3px 0;
+ overflow: hidden;
+}
+
+.statusDisplayRecords {
+ background: rgba(0, 0, 0, 0.5);
+ color: #ffffff;
+ border: 1px solid #40628a;
+ padding: 4px;
+ margin: 3px 0;
+ overflow-x: hidden;
+ overflow-y: auto;
+}
+
+.statusLabel {
+ width: 138px;
+ float: left;
+ overflow: hidden;
+ color: #98B0C3;
+}
+
+.statusValue {
+ float: left;
+}
+
+// CHROMELESS STUFF
+
+.fancy {
+ display: none;
+}
+
+.titlebutton() {
+ color: #E9C183;
+
+ &.hover {
+ color: lighten(#98b0c3, 10%);
+ }
+}
+.minimize {
+ .titlebutton;
+ position: absolute;
+ top: 6px;
+ right: 46px;
+}
+.close {
+ .titlebutton;
+ position: absolute;
+ top: 4px;
+ right: 12px;
+}
+
+div.resize {
+ position: fixed;
+ bottom: 0;
+ right: 0;
+ width: 0;
+ height: 0;
+
+ border-style: solid;
+ border-width: 0 0 30px 30px;
+ border-color: transparent transparent #363636 transparent;
+
+ transform: rotate(360deg);
+}
diff --git a/nano/styles/_icon.less b/nano/styles/_icon.less
new file mode 100644
index 0000000000..7de6544e0e
--- /dev/null
+++ b/nano/styles/_icon.less
@@ -0,0 +1,62 @@
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.uiLinkPendingIcon {
+ display: none;
+ float: left;
+ width: 16px;
+ height: 16px;
+ margin: 2px 2px 0 2px;
+ background-image: url(uiLinkPendingIcon.gif);
+}
+
+.linkPending .fa {
+ display: none;
+}
+
+.linkPending .uiLinkPendingIcon {
+ display: block;
+}
+
+.mapIcon16 {
+ position: absolute;
+ width: 16px;
+ height: 16px;
+ background-image: url(uiIcons16Green.png);
+ background-position: -144px -96px;
+ background-repeat: no-repeat;
+ zoom: 0.125;
+ margin-left: -1px;
+ /*margin-bottom: -1px;*/
+}
+.mapIcon16.dead { background-image: url(uiIcons16Red.png); }
+.mapIcon16.bad { background-image: url(uiIcons16Red.png); }
+.mapIcon16.average { background-image: url(uiIcons16Orange.png); }
+.mapIcon16.good { background-image: url(uiIcons16Green.png); }
+/* AirAlarm */
+.mapIcon16.icon-airalarm { background-position: 0 -144px; }
+/* Command Positions */
+.mapIcon16.rank-captain { background-position: -224px -112px; }
+.mapIcon16.rank-headofpersonnel { background-position: -112px -96px; }
+.mapIcon16.rank-headofsecurity { background-position: -112px -128px; }
+.mapIcon16.rank-chiefengineer { background-position: -176px -112px; }
+.mapIcon16.rank-researchdirector { background-position: -128px -128px; }
+.mapIcon16.rank-chiefmedicalofficer { background-position: -32px -128px; }
+/* Engineering Positions */
+.mapIcon16.rank-stationengineer { background-position: -176px -112px; }
+.mapIcon16.rank-atmospherictechnician { background-position: -176px -112px; }
+/* Medical Positions */
+.mapIcon16.rank-medicaldoctor {background-position: -32px -128px; }
+.mapIcon16.rank-geneticist {background-position: -32px -128px; }
+.mapIcon16.rank-psychiatrist {background-position: -32px -128px; }
+.mapIcon16.rank-chemist { background-position: -32px -128px; }
+/* Science Positions */
+.mapIcon16.rank-scientist { background-position: -128px -128px; }
+.mapIcon16.rank-geneticist { background-position: -128px -128px; }
+.mapIcon16.rank-roboticist { background-position: -128px -128px; }
+.mapIcon16.rank-xenobiologist { background-position: -128px -128px; }
+/* Security Positions */
+.mapIcon16.rank-warden { background-position: -112px -128px; }
+.mapIcon16.rank-detective { background-position: -112px -128px; }
+.mapIcon16.rank-securityofficer { background-position: -112px -128px; }
\ No newline at end of file
diff --git a/nano/styles/_link.less b/nano/styles/_link.less
new file mode 100644
index 0000000000..24a8c33c7b
--- /dev/null
+++ b/nano/styles/_link.less
@@ -0,0 +1,89 @@
+.link, .linkOn, .linkOff, .selected, .disabled, .yellowButton, .redButton {
+ float: left;
+ min-width: 15px;
+ height: 16px;
+ text-align: center;
+ color: #ffffff;
+ text-decoration: none;
+ background: #40628a;
+ border: 1px solid #161616;
+ padding: 0px 4px 4px 4px;
+ margin: 0 2px 2px 0;
+ cursor: default;
+ white-space: nowrap;
+}
+
+.hasIcon {
+ padding: 0px 4px 4px 0px;
+}
+
+a.hover, .zoomLink.hover, .linkActive.hover {
+ background: #507aac;
+}
+
+@keyframes pendingAnimation {
+ to {
+ color: #ffffff;
+ background: #507aac;
+ }
+}
+
+.linkPending, .linkPending.hover {
+ animation-name: pendingAnimation;
+ animation-duration: 0.25s;
+}
+
+a.white, a.white:link, a.white:visited, a.white:active {
+ color: #40628a;
+ text-decoration: none;
+ background: #ffffff;
+ border: 1px solid #161616;
+ padding: 1px 4px 1px 4px;
+ margin: 0 2px 0 0;
+ cursor: default;
+}
+
+a.white.hover {
+ color: #ffffff;
+ background: #40628a;
+}
+
+.linkOn, a.linkOn:link, a.linkOn:visited, a.linkOn:active, a.linkOn.hover, .selected, a.selected:link, a.selected:visited, a.selected:active, a.selected.hover {
+ color: #ffffff;
+ background: #2f943c;
+}
+
+.linkOff, a.linkOff:link, a.linkOff:visited, a.linkOff:active, a.linkOff.hover, .disabled, a.disabled:link, a.disabled:visited, a.disabled:active, a.disabled.hover {
+ color: #ffffff;
+ background: #999999;
+ border-color: #666666;
+}
+
+a.icon, .linkOn.icon, .linkOff.icon, .selected.icon, .disabled.icon {
+ position: relative;
+ padding: 1px 4px 2px 20px;
+}
+
+a.icon img, .linkOn.icon img, .linkOff.icon img, .selected.icon img, .disabled.icon img {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 18px;
+ height: 18px;
+}
+
+.linkDanger, a.linkDanger:link, a.linkDanger:visited, a.linkDanger:active {
+ color: #ffffff;
+ background-color: #ff0000;
+ border-color: #aa0000;
+}
+
+.linkDanger.hover {
+ background-color: #ff6666;
+}
+
+.inactive, a.inactive:link, a.inactive:visited, a.inactive:active, a.inactive.hover {
+ color: #ffffff;
+ background: #999999;
+ border-color: #666666;
+}
\ No newline at end of file
diff --git a/nano/styles/_lists.less b/nano/styles/_lists.less
new file mode 100644
index 0000000000..5dff836c22
--- /dev/null
+++ b/nano/styles/_lists.less
@@ -0,0 +1,83 @@
+ul {
+ padding: 4px 0 0 10px;
+ margin: 0;
+ list-style-type: none;
+}
+
+li {
+ padding: 0 0 2px 0;
+}
+
+table.fixed {
+ table-layout:fixed;
+}
+
+table.fixed td {
+ overflow: hidden;
+}
+
+/* Table styling for the power monitor */
+table.pmon {
+ border: 2px solid RoyalBlue;
+}
+
+table.pmon td, table.pmon th {
+ border-bottom: 1px dotted black;
+ padding: 0px 5px 0px 5px;
+}
+
+/* Table styling for the crew manifest */
+th.command {
+ background: #3333FF;
+ font-weight: bold;
+ color: #ffffff;
+}
+
+th.sec {
+ background: #8e0000;
+ font-weight: bold;
+ color: #ffffff;
+}
+
+th.med {
+ background: #006600;
+ font-weight: bold;
+ color: #ffffff;
+}
+
+th.eng {
+ background: #b27300;
+ font-weight: bold;
+ color: #ffffff;
+}
+
+th.sci {
+ background: #a65ba6;
+ font-weight: bold;
+ color: #ffffff;
+}
+
+th.civ {
+ background: #a32800;
+ font-weight: bold;
+ color: #ffffff;
+}
+
+th.misc {
+ background: #666666;
+ font-weight: bold;
+ color: #ffffff;
+}
+
+/* Striped lines for tables and divs */
+.striped {
+ .striped-base() {
+ background-color: #404040;
+ }
+ tr:nth-child(even) {
+ .striped-base;
+ }
+ &:not(table) > :nth-child(even) {
+ .striped-base;
+ }
+}
\ No newline at end of file
diff --git a/nano/styles/_misc.less b/nano/styles/_misc.less
new file mode 100644
index 0000000000..f54408f6e4
--- /dev/null
+++ b/nano/styles/_misc.less
@@ -0,0 +1,179 @@
+#uiNoScript {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ margin: -60px 0 0 -150px;
+ width: 280px;
+ height: 120px;
+ background: #ffffff;
+ border: 2px solid #ff0000;
+ color: #000000;
+ font-size: 10px;
+ font-weight: bold;
+ z-index: 9999;
+ padding: 0px 10px;
+ text-align: center;
+}
+
+/**********************************
+ .notice | Caution Stripes
+**********************************/
+.notice {
+ position: relative;
+ background: url(uiNoticeBackground.jpg) 50% 50%;
+ color: #000000;
+ font-size: 12px;
+ font-style: italic;
+ font-weight: bold;
+ padding: 3px 4px 3px 4px;
+ margin: 4px 0 4px 0;
+}
+
+.noticePlaceholder {
+ position: relative;
+ font-size: 12px;
+ font-style: italic;
+ font-weight: bold;
+ padding: 3px 4px 3px 4px;
+ margin: 4px 0 4px 0;
+}
+
+.notice.icon {
+ padding: 2px 4px 0 20px;
+}
+
+.notice img {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 16px;
+ height: 16px;
+}
+
+div.notice {
+ clear: both;
+}
+/********************\
+\********************/
+
+
+/**********************************
+ PDA, DNA Modifier styles
+ Don't fit anywhere else.
+**********************************/
+
+/* PDA styling */
+.wholeScreen {
+ position: absolute;
+ color: #517087;
+ font-size: 16px;
+ font-weight: bold;
+ text-align: center;
+}
+
+.pdalink {
+ float: left;
+ white-space: nowrap;
+ cursor: default !important;
+
+ &.disabled {
+ background: none;
+ border: none;
+ color: #999;
+ text-align: left;
+ }
+}
+
+.pdanote {
+ color: #cd6500;
+ font-weight: bold;
+}
+
+.item.mainmenu {
+ padding-bottom: 0.5em;
+}
+
+div.clock {
+ color: white;
+ font-weight: bold;
+ float: right;
+ padding-right: 2em;
+}
+
+#pdaFooter {
+ position: fixed;
+ left: 0;
+ bottom: 0;
+ width: 100%;
+ background: @dark-color;
+}
+
+.pdaFooterButton {
+ width: 33%;
+ padding: 0.5em 0;
+ font-size: 200%;
+ border: none;
+ background: none;
+ color: #fff;
+
+ &.hover {
+ background: #507aac;
+ }
+
+ &.disabled {
+ background: none !important;
+ color: #555 !important;
+ }
+
+ .uiLinkPendingIcon {
+ position: absolute;
+ width: 33%;
+ background-repeat: no-repeat;
+ background-position: center;
+ }
+}
+
+/* DNA Modifier styling */
+.dnaBlock {
+ float: left;
+ width: 90px;
+ padding: 0 0 5px 0;
+}
+
+.dnaBlockNumber {
+ font-family: Fixed, monospace;
+ float: left;
+ color: #ffffff;
+ background: #363636;
+ min-width: 20px;
+ height: 20px;
+ padding: 0;
+ text-align: center;
+}
+
+.dnaSubBlock {
+ font-family: Fixed, monospace;
+ float: left;
+ padding: 0;
+ min-width: 16px;
+ height: 20px;
+ text-align: center;
+}
+
+.mask {
+ position: fixed;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ background: url(uiMaskBackground.png);
+}
+
+.maskContent {
+ width: 100%;
+ height: 200px;
+ margin: 200px 0;
+ text-align: center;
+}
+/********************\
+\********************/
\ No newline at end of file
diff --git a/nano/styles/_native.less b/nano/styles/_native.less
new file mode 100644
index 0000000000..8114d75133
--- /dev/null
+++ b/nano/styles/_native.less
@@ -0,0 +1,44 @@
+@import "_config";
+
+body {
+ background: #272727;
+ color: #ffffff;
+ font-family: @font;
+ font-size: @fontsize;
+ line-height: 170%;
+ margin: 0;
+ padding: 0;
+}
+
+hr {
+ background-color: #40628a;
+ height: 1px;
+ border: none;
+}
+
+img, a img {
+ border-style: none;
+}
+
+h1, h2, h3, h4, h5, h6 {
+ margin: 0;
+ padding: 12px 0 6px 0;
+ color: #FFFFFF;
+ clear: both;
+}
+
+h1 {
+ font-size: 18px;
+}
+
+h2 {
+ font-size: 16px;
+}
+
+h3 {
+ font-size: 14px;
+}
+
+h4 {
+ font-size: 12px;
+}
\ No newline at end of file
diff --git a/nano/styles/nanoui.less b/nano/styles/nanoui.less
new file mode 100644
index 0000000000..a90b819c9f
--- /dev/null
+++ b/nano/styles/nanoui.less
@@ -0,0 +1,140 @@
+@charset "utf-8";
+
+@import "_bars";
+@import "_link";
+@import "_color";
+@import "_config";
+@import "_formatting";
+@import "_icon";
+@import "_lists";
+@import "_misc";
+@import "_native";
+
+
+body {
+ background: data-uri('nanotrasen.svg') no-repeat fixed center/70% 70%,
+ linear-gradient(to bottom,
+ @background-start 0%,
+ @background-end 100%) no-repeat fixed center/100% 100%;
+}
+
+#uiWrapper {
+ width: 100%;
+ height: 100%;
+ -ms-user-select: none;
+ user-select: none;
+}
+
+#uiTitleWrapper {
+ position: relative;
+ height: 30px;
+}
+
+#uiTitleText {
+ position: absolute;
+ top: 6px;
+ left: 44px;
+ width: 66%;
+ overflow: hidden;
+ color: #E9C183;
+ font-size: 16px;
+}
+
+#uiTitle.icon {
+ padding: 6px 8px 6px 42px;
+ background-position: 2px 50%;
+ background-repeat: no-repeat;
+}
+
+#uiTitleFluff {
+ position: absolute;
+ top: 4px;
+ right: 12px;
+ width: 42px;
+ height: 24px;
+ background: data-uri('nanotrasen.svg') 50% 50% no-repeat;
+}
+
+.statusicon {
+ position: absolute;
+ top: 4px;
+ left: 12px;
+}
+
+#uiMapWrapper {
+ clear: both;
+ padding: 8px;
+}
+
+#uiMapHeader {
+ position: relative;
+ clear: both;
+}
+
+#uiMapContainer {
+ position: relative;
+ width: 100%;
+ height: 600px;
+ overflow: hidden;
+ border: 1px solid #40628a;
+ background: url(nanomapBackground.png);
+}
+
+#uiMap {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ margin: -512px 0 0 -512px;
+ width: 256px;
+ height: 256px;
+ overflow: hidden;
+ zoom: 4;
+}
+
+#uiMapImage {
+ position: absolute;
+ bottom: 1px;
+ left: 1px;
+ width: 256px;
+ height: 256px;
+}
+
+#uiMapContent {
+ position: absolute;
+ bottom: -13.5px;
+ left: 0px;
+ width: 256px;
+ height: 256px;
+}
+
+#uiMapFooter {
+ position: relative;
+ clear: both;
+}
+
+#uiContent {
+ clear: both;
+ padding: 8px;
+}
+
+#uiMapTooltip {
+ position: absolute;
+ right: 10px;
+ top: 10px;
+ border: 1px solid #40628a;
+ background-color: #272727;
+ padding: 8px;
+ display: none;
+ z-index: 9999;
+}
+
+#uiLoadingNotice {
+ position: relative;
+ background: url(uiNoticeBackground.jpg) 50% 50%;
+ color: #000000;
+ font-size: 14px;
+ font-style: italic;
+ font-weight: bold;
+ padding: 3px 4px 3px 4px;
+ margin: 4px 0 4px 0;
+}
\ No newline at end of file