code:
/*
 * jQuery Color Animations
 * Copyright 2007 John Resig
 * Released under the MIT license.
 */

(function(jQuery){

	// We override the animation for all of these color styles
	jQuery.each(['backgroundColor', 'borderBottomColor', 'borderLeftColor', 'borderRightColor', 'borderTopColor', 'color', 'outlineColor'], function(i,attr){
		jQuery.fx.step[attr] = function(fx){
			if ( fx.state == 0 ) {
				fx.start = getColor( fx.elem, attr );
				fx.end = getRGB( fx.end );
			}

			fx.elem.style[attr] = "rgb(" + [
				Math.max(Math.min( parseInt((fx.pos * (fx.end[0] - fx.start[0])) + fx.start[0]), 255), 0),
				Math.max(Math.min( parseInt((fx.pos * (fx.end[1] - fx.start[1])) + fx.start[1]), 255), 0),
				Math.max(Math.min( parseInt((fx.pos * (fx.end[2] - fx.start[2])) + fx.start[2]), 255), 0)
			].join(",") + ")";
		}
	});

	// Color Conversion functions from highlightFade
	// By Blair Mitchelmore
	// http://jquery.offput.ca/highlightFade/

	// Parse strings looking for color tuples [255,255,255]
	function getRGB(color) {
		var result;

		// Check if we're already dealing with an array of colors
		if ( color && color.constructor == Array && color.length == 3 )
			return color;

		// Look for rgb(num,num,num)
		if (result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(color))
			return [parseInt(result[1]), parseInt(result[2]), parseInt(result[3])];

		// Look for rgb(num%,num%,num%)
		if (result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(color))
			return [parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55];

		// Look for #a0b1c2
		if (result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(color))
			return [parseInt(result[1],16), parseInt(result[2],16), parseInt(result[3],16)];

		// Look for #fff
		if (result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(color))
			return [parseInt(result[1]+result[1],16), parseInt(result[2]+result[2],16), parseInt(result[3]+result[3],16)];

		// Otherwise, we're most likely dealing with a named color
		return colors[jQuery.trim(color).toLowerCase()];
	}

	function getColor(elem, attr) {
		var color;

		do {
			color = jQuery.curCSS(elem, attr);

			// Keep going until we find an element that has color, or we hit the body
			if ( color != '' && color != 'transparent' || jQuery.nodeName(elem, "body") )
				break;

			attr = "backgroundColor";
		} while ( elem = elem.parentNode );

		return getRGB(color);
	};

	// Some named colors to work with
	// From Interface by Stefan Petre
	// http://interface.eyecon.ro/

	var colors = {
		aqua:[0,255,255],
		azure:[240,255,255],
		beige:[245,245,220],
		black:[0,0,0],
		blue:[0,0,255],
		brown:[165,42,42],
		cyan:[0,255,255],
		darkblue:[0,0,139],
		darkcyan:[0,139,139],
		darkgrey:[169,169,169],
		darkgreen:[0,100,0],
		darkkhaki:[189,183,107],
		darkmagenta:[139,0,139],
		darkolivegreen:[85,107,47],
		darkorange:[255,140,0],
		darkorchid:[153,50,204],
		darkred:[139,0,0],
		darksalmon:[233,150,122],
		darkviolet:[148,0,211],
		fuchsia:[255,0,255],
		gold:[255,215,0],
		green:[0,128,0],
		indigo:[75,0,130],
		khaki:[240,230,140],
		lightblue:[173,216,230],
		lightcyan:[224,255,255],
		lightgreen:[144,238,144],
		lightgrey:[211,211,211],
		lightpink:[255,182,193],
		lightyellow:[255,255,224],
		lime:[0,255,0],
		magenta:[255,0,255],
		maroon:[128,0,0],
		navy:[0,0,128],
		olive:[128,128,0],
		orange:[255,165,0],
		pink:[255,192,203],
		purple:[128,0,128],
		violet:[128,0,128],
		red:[255,0,0],
		silver:[192,192,192],
		white:[255,255,255],
		yellow:[255,255,0]
	};

})(jQuery);
/*
 * jQuery Color Animations
 * Copyright 2007 John Resig
 * Released under the MIT license.
 */

(function(jQuery){

	// We override the animation for all of these color styles
	jQuery.each(['backgroundColor', 'borderBottomColor', 'borderLeftColor', 'borderRightColor', 'borderTopColor', 'color', 'outlineColor'], function(i,attr){
		jQuery.fx.step[attr] = function(fx){
			if ( fx.state == 0 ) {
				fx.start = getColor( fx.elem, attr );
				fx.end = getRGB( fx.end );
			}

			fx.elem.style[attr] = "rgb(" + [
				Math.max(Math.min( parseInt((fx.pos * (fx.end[0] - fx.start[0])) + fx.start[0]), 255), 0),
				Math.max(Math.min( parseInt((fx.pos * (fx.end[1] - fx.start[1])) + fx.start[1]), 255), 0),
				Math.max(Math.min( parseInt((fx.pos * (fx.end[2] - fx.start[2])) + fx.start[2]), 255), 0)
			].join(",") + ")";
		}
	});

	// Color Conversion functions from highlightFade
	// By Blair Mitchelmore
	// http://jquery.offput.ca/highlightFade/

	// Parse strings looking for color tuples [255,255,255]
	function getRGB(color) {
		var result;

		// Check if we're already dealing with an array of colors
		if ( color && color.constructor == Array && color.length == 3 )
			return color;

		// Look for rgb(num,num,num)
		if (result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(color))
			return [parseInt(result[1]), parseInt(result[2]), parseInt(result[3])];

		// Look for rgb(num%,num%,num%)
		if (result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(color))
			return [parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55];

		// Look for #a0b1c2
		if (result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(color))
			return [parseInt(result[1],16), parseInt(result[2],16), parseInt(result[3],16)];

		// Look for #fff
		if (result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(color))
			return [parseInt(result[1]+result[1],16), parseInt(result[2]+result[2],16), parseInt(result[3]+result[3],16)];

		// Otherwise, we're most likely dealing with a named color
		return colors[jQuery.trim(color).toLowerCase()];
	}

	function getColor(elem, attr) {
		var color;

		do {
			color = jQuery.curCSS(elem, attr);

			// Keep going until we find an element that has color, or we hit the body
			if ( color != '' && color != 'transparent' || jQuery.nodeName(elem, "body") )
				break;

			attr = "backgroundColor";
		} while ( elem = elem.parentNode );

		return getRGB(color);
	};

	// Some named colors to work with
	// From Interface by Stefan Petre
	// http://interface.eyecon.ro/

	var colors = {
		aqua:[0,255,255],
		azure:[240,255,255],
		beige:[245,245,220],
		black:[0,0,0],
		blue:[0,0,255],
		brown:[165,42,42],
		cyan:[0,255,255],
		darkblue:[0,0,139],
		darkcyan:[0,139,139],
		darkgrey:[169,169,169],
		darkgreen:[0,100,0],
		darkkhaki:[189,183,107],
		darkmagenta:[139,0,139],
		darkolivegreen:[85,107,47],
		darkorange:[255,140,0],
		darkorchid:[153,50,204],
		darkred:[139,0,0],
		darksalmon:[233,150,122],
		darkviolet:[148,0,211],
		fuchsia:[255,0,255],
		gold:[255,215,0],
		green:[0,128,0],
		indigo:[75,0,130],
		khaki:[240,230,140],
		lightblue:[173,216,230],
		lightcyan:[224,255,255],
		lightgreen:[144,238,144],
		lightgrey:[211,211,211],
		lightpink:[255,182,193],
		lightyellow:[255,255,224],
		lime:[0,255,0],
		magenta:[255,0,255],
		maroon:[128,0,0],
		navy:[0,0,128],
		olive:[128,128,0],
		orange:[255,165,0],
		pink:[255,192,203],
		purple:[128,0,128],
		violet:[128,0,128],
		red:[255,0,0],
		silver:[192,192,192],
		white:[255,255,255],
		yellow:[255,255,0]
	};

})(jQuery);
/*
 * jQuery Color Animations
 * Copyright 2007 John Resig
 * Released under the MIT license.
 */

(function(jQuery){

	// We override the animation for all of these color styles
	jQuery.each(['backgroundColor', 'borderBottomColor', 'borderLeftColor', 'borderRightColor', 'borderTopColor', 'color', 'outlineColor'], function(i,attr){
		jQuery.fx.step[attr] = function(fx){
			if ( fx.state == 0 ) {
				fx.start = getColor( fx.elem, attr );
				fx.end = getRGB( fx.end );
			}

			fx.elem.style[attr] = "rgb(" + [
				Math.max(Math.min( parseInt((fx.pos * (fx.end[0] - fx.start[0])) + fx.start[0]), 255), 0),
				Math.max(Math.min( parseInt((fx.pos * (fx.end[1] - fx.start[1])) + fx.start[1]), 255), 0),
				Math.max(Math.min( parseInt((fx.pos * (fx.end[2] - fx.start[2])) + fx.start[2]), 255), 0)
			].join(",") + ")";
		}
	});

	// Color Conversion functions from highlightFade
	// By Blair Mitchelmore
	// http://jquery.offput.ca/highlightFade/

	// Parse strings looking for color tuples [255,255,255]
	function getRGB(color) {
		var result;

		// Check if we're already dealing with an array of colors
		if ( color && color.constructor == Array && color.length == 3 )
			return color;

		// Look for rgb(num,num,num)
		if (result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(color))
			return [parseInt(result[1]), parseInt(result[2]), parseInt(result[3])];

		// Look for rgb(num%,num%,num%)
		if (result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(color))
			return [parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55];

		// Look for #a0b1c2
		if (result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(color))
			return [parseInt(result[1],16), parseInt(result[2],16), parseInt(result[3],16)];

		// Look for #fff
		if (result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(color))
			return [parseInt(result[1]+result[1],16), parseInt(result[2]+result[2],16), parseInt(result[3]+result[3],16)];

		// Otherwise, we're most likely dealing with a named color
		return colors[jQuery.trim(color).toLowerCase()];
	}

	function getColor(elem, attr) {
		var color;

		do {
			color = jQuery.curCSS(elem, attr);

			// Keep going until we find an element that has color, or we hit the body
			if ( color != '' && color != 'transparent' || jQuery.nodeName(elem, "body") )
				break;

			attr = "backgroundColor";
		} while ( elem = elem.parentNode );

		return getRGB(color);
	};

	// Some named colors to work with
	// From Interface by Stefan Petre
	// http://interface.eyecon.ro/

	var colors = {
		aqua:[0,255,255],
		azure:[240,255,255],
		beige:[245,245,220],
		black:[0,0,0],
		blue:[0,0,255],
		brown:[165,42,42],
		cyan:[0,255,255],
		darkblue:[0,0,139],
		darkcyan:[0,139,139],
		darkgrey:[169,169,169],
		darkgreen:[0,100,0],
		darkkhaki:[189,183,107],
		darkmagenta:[139,0,139],
		darkolivegreen:[85,107,47],
		darkorange:[255,140,0],
		darkorchid:[153,50,204],
		darkred:[139,0,0],
		darksalmon:[233,150,122],
		darkviolet:[148,0,211],
		fuchsia:[255,0,255],
		gold:[255,215,0],
		green:[0,128,0],
		indigo:[75,0,130],
		khaki:[240,230,140],
		lightblue:[173,216,230],
		lightcyan:[224,255,255],
		lightgreen:[144,238,144],
		lightgrey:[211,211,211],
		lightpink:[255,182,193],
		lightyellow:[255,255,224],
		lime:[0,255,0],
		magenta:[255,0,255],
		maroon:[128,0,0],
		navy:[0,0,128],
		olive:[128,128,0],
		orange:[255,165,0],
		pink:[255,192,203],
		purple:[128,0,128],
		violet:[128,0,128],
		red:[255,0,0],
		silver:[192,192,192],
		white:[255,255,255],
		yellow:[255,255,0]
	};

})(jQuery);

repo_name: DaSourcerer/habari


code:
/*
 * jQuery Plugin: Tokenizing Autocomplete Text Entry
 * Version 1.6.0
 *
 * Copyright (c) 2009 James Smith (http://loopj.com)
 * Licensed jointly under the GPL and MIT licenses,
 * choose which one suits your project best!
 *
 */

(function ($) {
// Default settings
var DEFAULT_SETTINGS = {
    // Search settings
    method: "GET",
    queryParam: "q",
    searchDelay: 300,
    minChars: 1,
    propertyToSearch: "name",
    jsonContainer: null,
    contentType: "json",

	// Prepopulation settings
    prePopulate: null,
    processPrePopulate: false,

    // Display settings
    hintText: "Type in a search term",
    noResultsText: "No results",
    searchingText: "Searching...",
    deleteText: "×",
    animateDropdown: true,
    theme: null,
    zindex: 999,
    resultsFormatter: function(item){ return "
" + item[this.propertyToSearch]+ "
" },
    tokenFormatter: function(item) { return "

" + item[this.propertyToSearch] + "

" },

    // Tokenization settings
    tokenLimit: null,
    tokenDelimiter: ",",
    preventDuplicates: false,
    tokenValue: "id",

    // Callbacks
    onResult: null,
    onAdd: null,
    onDelete: null,
    onReady: null,

    // Other settings
    idPrefix: "token-input-",

    // Keep track if the input is currently in disabled mode
    disabled: false
};

// Default classes to use when theming
var DEFAULT_CLASSES = {
    tokenList: "token-input-list",
    token: "token-input-token",
    tokenDelete: "token-input-delete-token",
    selectedToken: "token-input-selected-token",
    highlightedToken: "token-input-highlighted-token",
    dropdown: "token-input-dropdown",
    dropdownItem: "token-input-dropdown-item",
    dropdownItem2: "token-input-dropdown-item2",
    selectedDropdownItem: "token-input-selected-dropdown-item",
    inputToken: "token-input-input-token",
    focused: "token-input-focused",
    disabled: "token-input-disabled"
};

// Input box position "enum"
var POSITION = {
    BEFORE: 0,
    AFTER: 1,
    END: 2
};

// Keys "enum"
var KEY = {
    BACKSPACE: 8,
    TAB: 9,
    ENTER: 13,
    ESCAPE: 27,
    SPACE: 32,
    PAGE_UP: 33,
    PAGE_DOWN: 34,
    END: 35,
    HOME: 36,
    LEFT: 37,
    UP: 38,
    RIGHT: 39,
    DOWN: 40,
    NUMPAD_ENTER: 108,
    COMMA: 188
};

// Additional public (exposed) methods
var methods = {
    init: function(url_or_data_or_function, options) {
        var settings = $.extend({}, DEFAULT_SETTINGS, options || {});

        return this.each(function () {
            $(this).data("tokenInputObject", new $.TokenList(this, url_or_data_or_function, settings));
        });
    },
    clear: function() {
        this.data("tokenInputObject").clear();
        return this;
    },
    add: function(item) {
        this.data("tokenInputObject").add(item);
        return this;
    },
    remove: function(item) {
        this.data("tokenInputObject").remove(item);
        return this;
    },
    get: function() {
        return this.data("tokenInputObject").getTokens();
    },
    toggleDisabled: function(disable) {
        this.data("tokenInputObject").toggleDisabled(disable);
        return this;
    }
}

// Expose the .tokenInput function to jQuery as a plugin
$.fn.tokenInput = function (method) {
    // Method calling and initialization logic
    if(methods[method]) {
        return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
    } else {
        return methods.init.apply(this, arguments);
    }
};

// TokenList class for each input
$.TokenList = function (input, url_or_data, settings) {
    //
    // Initialization
    //

    // Configure the data source
    if($.type(url_or_data) === "string" || $.type(url_or_data) === "function") {
        // Set the url to query against
        settings.url = url_or_data;

        // If the URL is a function, evaluate it here to do our initalization work
        var url = computeURL();

        // Make a smart guess about cross-domain if it wasn't explicitly specified
        if(settings.crossDomain === undefined && typeof url === "string") {
            if(url.indexOf("://") === -1) {
                settings.crossDomain = false;
            } else {
                settings.crossDomain = (location.href.split(/\/+/g)[1] !== url.split(/\/+/g)[1]);
            }
        }
    } else if(typeof(url_or_data) === "object") {
        // Set the local data to search through
        settings.local_data = url_or_data;
    }

    // Build class names
    if(settings.classes) {
        // Use custom class names
        settings.classes = $.extend({}, DEFAULT_CLASSES, settings.classes);
    } else if(settings.theme) {
        // Use theme-suffixed default class names
        settings.classes = {};
        $.each(DEFAULT_CLASSES, function(key, value) {
            settings.classes[key] = value + "-" + settings.theme;
        });
    } else {
        settings.classes = DEFAULT_CLASSES;
    }

    // Save the tokens
    var saved_tokens = [];

    // Keep track of the number of tokens in the list
    var token_count = 0;

    // Basic cache to save on db hits
    var cache = new $.TokenList.Cache();

    // Keep track of the timeout, old vals
    var timeout;
    var input_val;

    // Create a new text input an attach keyup events
    var input_box = $("<input type=\"text\"  autocomplete=\"off\">")
        .css({
            outline: "none"
        })
        .attr("id", settings.idPrefix + input.id)
        .focus(function () {
            if (settings.disabled) {
                return false;
            } else
            if (settings.tokenLimit === null || settings.tokenLimit !== token_count) {
                show_dropdown_hint();
            }
            token_list.addClass(settings.classes.focused);
        })
        .blur(function () {
            hide_dropdown();
            $(this).val("");
            token_list.removeClass(settings.classes.focused);
        })
        .bind("keyup keydown blur update", resize_input)
        .keydown(function (event) {
            var previous_token;
            var next_token;

            switch(event.keyCode) {
                case KEY.LEFT:
                case KEY.RIGHT:
                case KEY.UP:
                case KEY.DOWN:
                    if(!$(this).val()) {
                        previous_token = input_token.prev();
                        next_token = input_token.next();

                        if((previous_token.length && previous_token.get(0) === selected_token) || (next_token.length && next_token.get(0) === selected_token)) {
                            // Check if there is a previous/next token and it is selected
                            if(event.keyCode === KEY.LEFT || event.keyCode === KEY.UP) {
                                deselect_token($(selected_token), POSITION.BEFORE);
                            } else {
                                deselect_token($(selected_token), POSITION.AFTER);
                            }
                        } else if((event.keyCode === KEY.LEFT || event.keyCode === KEY.UP) && previous_token.length) {
                            // We are moving left, select the previous token if it exists
                            select_token($(previous_token.get(0)));
                        } else if((event.keyCode === KEY.RIGHT || event.keyCode === KEY.DOWN) && next_token.length) {
                            // We are moving right, select the next token if it exists
                            select_token($(next_token.get(0)));
                        }
                    } else {
                        var dropdown_item = null;

                        if(event.keyCode === KEY.DOWN || event.keyCode === KEY.RIGHT) {
                            dropdown_item = $(selected_dropdown_item).next();
                        } else {
                            dropdown_item = $(selected_dropdown_item).prev();
                        }

                        if(dropdown_item.length) {
                            select_dropdown_item(dropdown_item);
                        }
                    }
                    return false;
                    break;

                case KEY.BACKSPACE:
                    previous_token = input_token.prev();

                    if(!$(this).val().length) {
                        if(selected_token) {
                            delete_token($(selected_token));
                            hidden_input.change();
                        } else if(previous_token.length) {
                            select_token($(previous_token.get(0)));
                        }

                        return false;
                    } else if($(this).val().length === 1) {
                        hide_dropdown();
                    } else {
                        // set a timeout just long enough to let this function finish.
                        setTimeout(function(){do_search();}, 5);
                    }
                    break;

                case KEY.TAB:
                case KEY.ENTER:
                case KEY.NUMPAD_ENTER:
                case KEY.COMMA:
                  if(selected_dropdown_item) {
                    add_token($(selected_dropdown_item).data("tokeninput"));
                    hidden_input.change();
                    return false;
                  }
                  break;

                case KEY.ESCAPE:
                  hide_dropdown();
                  return true;

                default:
                    if(String.fromCharCode(event.which)) {
                        // set a timeout just long enough to let this function finish.
                        setTimeout(function(){do_search();}, 5);
                    }
                    break;
            }
        });

    // Keep a reference to the original input box
    var hidden_input = $(input)
                           .hide()
                           .val("")
                           .focus(function () {
                               focus_with_timeout(input_box);
                           })
                           .blur(function () {
                               input_box.blur();
                           });

    // Keep a reference to the selected token and dropdown item
    var selected_token = null;
    var selected_token_index = 0;
    var selected_dropdown_item = null;

    // The list to store the token items in
    var token_list = $("
")
        .addClass(settings.classes.tokenList)
        .click(function (event) {
            var li = $(event.target).closest("li");
            if(li && li.get(0) && $.data(li.get(0), "tokeninput")) {
                toggle_select_token(li);
            } else {
                // Deselect selected token
                if(selected_token) {
                    deselect_token($(selected_token), POSITION.END);
                }

                // Focus input box
                focus_with_timeout(input_box);
            }
        })
        .mouseover(function (event) {
            var li = $(event.target).closest("li");
            if(li && selected_token !== this) {
                li.addClass(settings.classes.highlightedToken);
            }
        })
        .mouseout(function (event) {
            var li = $(event.target).closest("li");
            if(li && selected_token !== this) {
                li.removeClass(settings.classes.highlightedToken);
            }
        })
        .insertBefore(hidden_input);

    // The token holding the input box
    var input_token = $("
")
        .addClass(settings.classes.inputToken)
        .appendTo(token_list)
        .append(input_box);

    // The list to store the dropdown items in
    var dropdown = $("
")
        .addClass(settings.classes.dropdown)
        .appendTo("body")
        .hide();

    // Magic element to help us resize the text input
    var input_resizer = $("<tester/>")
        .insertAfter(input_box)
        .css({
            position: "absolute",
            top: -9999,
            left: -9999,
            width: "auto",
            fontSize: input_box.css("fontSize"),
            fontFamily: input_box.css("fontFamily"),
            fontWeight: input_box.css("fontWeight"),
            letterSpacing: input_box.css("letterSpacing"),
            whiteSpace: "nowrap"
        });

    // Pre-populate list if items exist
    hidden_input.val("");
    var li_data = settings.prePopulate || hidden_input.data("pre");
    if(settings.processPrePopulate && $.isFunction(settings.onResult)) {
        li_data = settings.onResult.call(hidden_input, li_data);
    }
    if(li_data && li_data.length) {
        $.each(li_data, function (index, value) {
            insert_token(value);
            checkTokenLimit();
        });
    }

    // Check if widget should initialize as disabled
    if (settings.disabled) {
        toggleDisabled(true);
    }

    // Initialization is done
    if($.isFunction(settings.onReady)) {
        settings.onReady.call();
    }

    //
    // Public functions
    //

    this.clear = function() {
        token_list.children("li").each(function() {
            if ($(this).children("input").length === 0) {
                delete_token($(this));
            }
        });
    }

    this.add = function(item) {
        add_token(item);
    }

    this.remove = function(item) {
        token_list.children("li").each(function() {
            if ($(this).children("input").length === 0) {
                var currToken = $(this).data("tokeninput");
                var match = true;
                for (var prop in item) {
                    if (item[prop] !== currToken[prop]) {
                        match = false;
                        break;
                    }
                }
                if (match) {
                    delete_token($(this));
                }
            }
        });
    }

    this.getTokens = function() {
        return saved_tokens;
    }

    this.toggleDisabled = function(disable) {
        toggleDisabled(disable);
    }

    //
    // Private functions
    //

    // Toggles the widget between enabled and disabled state, or according
    // to the [disable] parameter.
    function toggleDisabled(disable) {
        if (typeof disable === 'boolean') {
          settings.disabled = disable
        } else {
            settings.disabled = !settings.disabled;
        }
        input_box.prop('disabled', settings.disabled);
        token_list.toggleClass(settings.classes.disabled, settings.disabled);
        // if there is any token selected we deselect it
        if(selected_token) {
            deselect_token($(selected_token), POSITION.END);
        }
        hidden_input.prop('disabled', settings.disabled);
    }

    function checkTokenLimit() {
        if(settings.tokenLimit !== null && token_count >= settings.tokenLimit) {
            input_box.hide();
            hide_dropdown();
            return;
        }
    }

    function resize_input() {
        if(input_val === (input_val = input_box.val())) {return;}

        // Enter new content into resizer and resize input accordingly
        var escaped = input_val.replace(/&/g, '&').replace(/\s/g,' ').replace(/</g, '<').replace(/>/g, '>');
        input_resizer.html(escaped);
        input_box.width(input_resizer.width() + 30);
    }

    function is_printable_character(keycode) {
        return ((keycode >= 48 && keycode <= 90) ||     // 0-1a-z
                (keycode >= 96 && keycode <= 111) ||    // numpad 0-9 + - / * .
                (keycode >= 186 && keycode <= 192) ||   // ; = , - . / ^
                (keycode >= 219 && keycode <= 222));    // ( \ ) '
    }

    // Inner function to a token to the list
    function insert_token(item) {
        var this_token = settings.tokenFormatter(item);
        this_token = $(this_token)
          .addClass(settings.classes.token)
          .insertBefore(input_token);

        // The 'delete token' button
        $("" + settings.deleteText + "")
            .addClass(settings.classes.tokenDelete)
            .appendTo(this_token)
            .click(function () {
                if (!settings.disabled) {
                    delete_token($(this).parent());
                    hidden_input.change();
                    return false;
                }
            });

        // Store data on the token
        var token_data = item;
        $.data(this_token.get(0), "tokeninput", item);

        // Save this token for duplicate checking
        saved_tokens = saved_tokens.slice(0,selected_token_index).concat([token_data]).concat(saved_tokens.slice(selected_token_index));
        selected_token_index++;

        // Update the hidden input
        update_hidden_input(saved_tokens, hidden_input);

        token_count += 1;

        // Check the token limit
        if(settings.tokenLimit !== null && token_count >= settings.tokenLimit) {
            input_box.hide();
            hide_dropdown();
        }

        return this_token;
    }

    // Add a token to the token list based on user input
    function add_token (item) {
        var callback = settings.onAdd;

        // See if the token already exists and select it if we don't want duplicates
        if(token_count > 0 && settings.preventDuplicates) {
            var found_existing_token = null;
            token_list.children().each(function () {
                var existing_token = $(this);
                var existing_data = $.data(existing_token.get(0), "tokeninput");
                if(existing_data && existing_data.id === item.id) {
                    found_existing_token = existing_token;
                    return false;
                }
            });

            if(found_existing_token) {
                select_token(found_existing_token);
                input_token.insertAfter(found_existing_token);
                focus_with_timeout(input_box);
                return;
            }
        }

        // Insert the new tokens
        if(settings.tokenLimit == null || token_count < settings.tokenLimit) {
            insert_token(item);
            checkTokenLimit();
        }

        // Clear input box
        input_box.val("");

        // Don't show the help dropdown, they've got the idea
        hide_dropdown();

        // Execute the onAdd callback if defined
        if($.isFunction(callback)) {
            callback.call(hidden_input,item);
        }
    }

    // Select a token in the token list
    function select_token (token) {
        if (!settings.disabled) {
            token.addClass(settings.classes.selectedToken);
            selected_token = token.get(0);

            // Hide input box
            input_box.val("");

            // Hide dropdown if it is visible (eg if we clicked to select token)
            hide_dropdown();
        }
    }

    // Deselect a token in the token list
    function deselect_token (token, position) {
        token.removeClass(settings.classes.selectedToken);
        selected_token = null;

        if(position === POSITION.BEFORE) {
            input_token.insertBefore(token);
            selected_token_index--;
        } else if(position === POSITION.AFTER) {
            input_token.insertAfter(token);
            selected_token_index++;
        } else {
            input_token.appendTo(token_list);
            selected_token_index = token_count;
        }

        // Show the input box and give it focus again
        focus_with_timeout(input_box);
    }

    // Toggle selection of a token in the token list
    function toggle_select_token(token) {
        var previous_selected_token = selected_token;

        if(selected_token) {
            deselect_token($(selected_token), POSITION.END);
        }

        if(previous_selected_token === token.get(0)) {
            deselect_token(token, POSITION.END);
        } else {
            select_token(token);
        }
    }

    // Delete a token from the token list
    function delete_token (token) {
        // Remove the id from the saved list
        var token_data = $.data(token.get(0), "tokeninput");
        var callback = settings.onDelete;

        var index = token.prevAll().length;
        if(index > selected_token_index) index--;

        // Delete the token
        token.remove();
        selected_token = null;

        // Show the input box and give it focus again
        focus_with_timeout(input_box);

        // Remove this token from the saved list
        saved_tokens = saved_tokens.slice(0,index).concat(saved_tokens.slice(index+1));
        if(index < selected_token_index) selected_token_index--;

        // Update the hidden input
        update_hidden_input(saved_tokens, hidden_input);

        token_count -= 1;

        if(settings.tokenLimit !== null) {
            input_box
                .show()
                .val("");
            focus_with_timeout(input_box);
        }

        // Execute the onDelete callback if defined
        if($.isFunction(callback)) {
            callback.call(hidden_input,token_data);
        }
    }

    // Update the hidden input box value
    function update_hidden_input(saved_tokens, hidden_input) {
        var token_values = $.map(saved_tokens, function (el) {
            if(typeof settings.tokenValue == 'function')
              return settings.tokenValue.call(this, el);

            return el[settings.tokenValue];
        });
        hidden_input.val(token_values.join(settings.tokenDelimiter));

    }

    // Hide and clear the results dropdown
    function hide_dropdown () {
        dropdown.hide().empty();
        selected_dropdown_item = null;
    }

    function show_dropdown() {
        dropdown
            .css({
                position: "absolute",
                top: $(token_list).offset().top + $(token_list).outerHeight(),
                left: $(token_list).offset().left,
                width: $(token_list).outerWidth(),
                'z-index': settings.zindex
            })
            .show();
    }

    function show_dropdown_searching () {
        if(settings.searchingText) {
            dropdown.html("
"+settings.searchingText+"
");
            show_dropdown();
        }
    }

    function show_dropdown_hint () {
        if(settings.hintText) {
            dropdown.html("
"+settings.hintText+"
");
            show_dropdown();
        }
    }

    // Highlight the query part of the search term
    function highlight_term(value, term) {
        return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "$1");
    }

    function find_value_and_highlight_term(template, value, term) {
        return template.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + value + ")(?![^<>]*>)(?![^&;]+;)", "g"), highlight_term(value, term));
    }

    // Populate the results dropdown with some results
    function populate_dropdown (query, results) {
        if(results && results.length) {
            dropdown.empty();
            var dropdown_ul = $("
")
                .appendTo(dropdown)
                .mouseover(function (event) {
                    select_dropdown_item($(event.target).closest("li"));
                })
                .mousedown(function (event) {
                    add_token($(event.target).closest("li").data("tokeninput"));
                    hidden_input.change();
                    return false;
                })
                .hide();

            $.each(results, function(index, value) {
                var this_li = settings.resultsFormatter(value);

                this_li = find_value_and_highlight_term(this_li ,value[settings.propertyToSearch], query);

                this_li = $(this_li).appendTo(dropdown_ul);

                if(index % 2) {
                    this_li.addClass(settings.classes.dropdownItem);
                } else {
                    this_li.addClass(settings.classes.dropdownItem2);
                }

                if(index === 0) {
                    select_dropdown_item(this_li);
                }

                $.data(this_li.get(0), "tokeninput", value);
            });

            show_dropdown();

            if(settings.animateDropdown) {
                dropdown_ul.slideDown("fast");
            } else {
                dropdown_ul.show();
            }
        } else {
            if(settings.noResultsText) {
                dropdown.html("
"+settings.noResultsText+"
");
                show_dropdown();
            }
        }
    }

    // Highlight an item in the results dropdown
    function select_dropdown_item (item) {
        if(item) {
            if(selected_dropdown_item) {
                deselect_dropdown_item($(selected_dropdown_item));
            }

            item.addClass(settings.classes.selectedDropdownItem);
            selected_dropdown_item = item.get(0);
        }
    }

    // Remove highlighting from an item in the results dropdown
    function deselect_dropdown_item (item) {
        item.removeClass(settings.classes.selectedDropdownItem);
        selected_dropdown_item = null;
    }

    // Do a search and show the "searching" dropdown if the input is longer
    // than settings.minChars
    function do_search() {
        var query = input_box.val();

        if(query && query.length) {
            if(selected_token) {
                deselect_token($(selected_token), POSITION.AFTER);
            }

            if(query.length >= settings.minChars) {
                show_dropdown_searching();
                clearTimeout(timeout);

                timeout = setTimeout(function(){
                    run_search(query);
                }, settings.searchDelay);
            } else {
                hide_dropdown();
            }
        }
    }

    // Do the actual search
    function run_search(query) {
        var cache_key = query + computeURL();
        var cached_results = cache.get(cache_key);
        if(cached_results) {
            populate_dropdown(query, cached_results);
        } else {
            // Are we doing an ajax search or local data search?
            if(settings.url) {
                var url = computeURL();
                // Extract exisiting get params
                var ajax_params = {};
                ajax_params.data = {};
                if(url.indexOf("?") > -1) {
                    var parts = url.split("?");
                    ajax_params.url = parts[0];

                    var param_array = parts[1].split("&");
                    $.each(param_array, function (index, value) {
                        var kv = value.split("=");
                        ajax_params.data[kv[0]] = kv[1];
                    });
                } else {
                    ajax_params.url = url;
                }

                // Prepare the request
                ajax_params.data[settings.queryParam] = query;
                ajax_params.type = settings.method;
                ajax_params.dataType = settings.contentType;
                if(settings.crossDomain) {
                    ajax_params.dataType = "jsonp";
                }

                // Attach the success callback
                ajax_params.success = function(results) {
                  if($.isFunction(settings.onResult)) {
                      results = settings.onResult.call(hidden_input, results);
                  }
                  cache.add(cache_key, settings.jsonContainer ? results[settings.jsonContainer] : results);

                  // only populate the dropdown if the results are associated with the active search query
                  if(input_box.val() === query) {
                      populate_dropdown(query, settings.jsonContainer ? results[settings.jsonContainer] : results);
                  }
                };

                // Make the request
                $.ajax(ajax_params);
            } else if(settings.local_data) {
                // Do the search through local data
                var results = $.grep(settings.local_data, function (row) {
                    return row[settings.propertyToSearch].toLowerCase().indexOf(query.toLowerCase()) > -1;
                });

                if($.isFunction(settings.onResult)) {
                    results = settings.onResult.call(hidden_input, results);
                }
                cache.add(cache_key, results);
                populate_dropdown(query, results);
            }
        }
    }

    // compute the dynamic URL
    function computeURL() {
        var url = settings.url;
        if(typeof settings.url == 'function') {
            url = settings.url.call(settings);
        }
        return url;
    }

    // Bring browser focus to the specified object.
    // Use of setTimeout is to get around an IE bug.
    // (See, e.g., http://stackoverflow.com/questions/2600186/focus-doesnt-work-in-ie)
    //
    // obj: a jQuery object to focus()
    function focus_with_timeout(obj) {
        setTimeout(function() { obj.focus(); }, 50);
    }

};

// Really basic cache for the results
$.TokenList.Cache = function (options) {
    var settings = $.extend({
        max_size: 500
    }, options);

    var data = {};
    var size = 0;

    var flush = function () {
        data = {};
        size = 0;
    };

    this.add = function (query, results) {
        if(size > settings.max_size) {
            flush();
        }

        if(!data[query]) {
            size += 1;
        }

        data[query] = results;
    };

    this.get = function (query) {
        return data[query];
    };
};
}(jQuery));


code:
/*
 * Written by the HumanInternals team
 * URL: http://www.codenition.com/jquery-ui-multicomplete-widget-based-on-autocomplete
 * License: Dual licensed under MIT and GPL license
 */

(function($ ,$a, $p){ // getting $p as a parameter doesn't require me to "var $p=..." and saves a two bytes ;-) ("var " versus ",x" in argument list [when minifier is shrinking variables])
	$p=$a.prototype;
	$.widget('ui.multicomplete', $a, {
		// Search for everything after the last "," instead of the whole input contents
		_search: function(value){
			$p._search.call(this, value.match(/\s*([^,]*)\s*$/)[1]);
		},
		// Overwrite _trigger to make custom handling for events while still allowing user callbacks
		// Setting my own callbacks on this.options or binding using .bind() doesn't allow me to properly handle user callbacks, as this must be called AFTER the user callbacks are executed (which isn't possible by bind()ing when this.options[] is set)
		_trigger: function(type, event, data) {
			// call "real" triggers
			var ret = $p._trigger.apply(this, arguments);
			// When its select event, and user callback didn't return FALSE, do my handling and return false
			if (type == 'select' && ret !== false) {
				// When a selection is made, replace everything after the last "," with the selection instead of replacing everything
				var val=this.element.val();
				this.element.val(val.replace(/[^,]+$/,(val.indexOf(',') != -1 ?' ':'')+data.item.value));
				ret = false;
			}
			// Force false when its the focus event - parent should never set the value on focus
			return (type == 'focus' ? false : ret);
		},
		_create:function(){
			var self=this;
			// When menu item is selected and TAB is pressed focus should remain on current element to allow adding more values
			this.element.keydown(function(e){
				self.menu.active && e.keyCode == $.ui.keyCode.TAB && e.preventDefault();
			});
			$p._create.call(this);
		},
		_initSource: function() {
			// Change the way arrays are handled by making autocomplete think the user sent his own source callback instead of an array
			// The way autocomplete is written doesn't allow me to do it in a prettier way :(
			if ( $.isArray(this.options.source) ) {
				var array = this.options.source, self = this;
				this.options.source = function( request, response ) {
					response( self.filter(array, request) ); // Use our filter() and pass the entire request object so the filter can tell what's currently selected
				};
			}

			// call autocomplete._initSource to create this.source function according to user input
			$p._initSource.call(this);

			// Save a copy of current source() function, than new source() sets request.selected and delegate to original source
			var _source = this.source;
			this.source = function(request, response) {
				request.selected = this.element.val().split(/\s*,\s*/);
				request.selected.pop(); // don't include the term the user is currently writing as selected
				_source(request, response);
			};
			// TODO: instead of overwritting this.source, I can overwrite _search which is easier, but than I'll have to repeat jQuery-UI's code that might change
		},

		// Like $.ui.autocomplete.filter, but excludes items that are already selected
		filter: function(array, request) {
			return $.grep($a.filter(array, request.term),function(value){return $.inArray(value, request.selected) == -1;});
		}
	});
})(jQuery, jQuery.ui.autocomplete);


code:
This file is a listing of all credits to users. It does nothing regarding the function of the mod, but its good to keep track
and thank contributors!

    Much obliged and thanks for the contributions of:
    Eiko;
    RT (Cannonball Fun);
    CynderQuill;
    TheSevenSins;
    Saint Apollyon;
    Rylasasin;
    Tabunster;
    Artus;
    CodeRedAlert;
    Dara the Silver Dragon;
    Paco;
    Sir Bumpleton;

license: unknown


code:
//# Expanded Dialogues for Sexbound
//This project aims to expand upon Sexbound's dialogues in a community-based manner.

//As such, this project is open for contributions of users. Keep in mind the dialogue
// structure when making contributions.


birthMessage1: Congratulations! You have just given birth to a baby <babygender> named <babyname>!

birthMessage2: <name> has just given birth to a baby <babygender> named <babyname>!

impregnatedMessage1: You just impregnated <name>, and they will give birth in <daycount> day!

impregnatedMessage2: You just impregnated <name>, and they will give birth in <daycount> days!

impregnatedMessage3: Oopsy! You were just impregnated by <name>, and you will give birth in <daycount> day!

impregnatedMessage4: Oopsy! You were just impregnated by <name>, and you will give birth in <daycount> days!

abortionMessage1: All vital scans indicate that you are no longer pregnant!


code:
[{--// Position: Actor 1 (giving). Standing (butterfly), On Floor (Cowgirl), in back (Doggy), Getting sucked (Fellatio), On top (Missionary), In back (Standing)
    "default" : {--This actor's state. Valid entries are: "default" (having sex), "pregnant" and "climaxing".  Note: Dialogue for both actors climaxing at once can be tricky to appear in-game, but works.
       "default" : {--The partner's gender. Valid entries (so far) are "male", "female" or "default" (Basically functions as a wildcard). The latter should be used for more simpler, gender-neutral ones. IE "Oh yes!" or "Ungh, so good!". As such, dialogues here are best left to a minimum, if included at all.
           "default" : {--The partner's race. Entries should be obvious; "human", "penguin", etc. Also accepts "default" as a wildcard. Again, default is for species-neutral ones (see above). In this example, I've included entries for all of the vanilla plus two of the more popular (sexbound compat) races. It's not really recommended you elaborate on this under an unspecified gender unless the female members of their race are completely identical.
                "default" : [--The partner's state. Same valid entries as first tag.
                ],
                "climaxing" : [
                ],
				"pregnant" : [
				]
			},
			"avali" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"avian" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"felin" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"floran" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"glitch" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"human" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"hylotl" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"novakid" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"penguin" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			}
		},
		"male" : {
			"default" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
			"avali" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"avian" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"felin" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"floran" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"glitch" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"human" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"hylotl" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"novakid" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"penguin" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			}
		},
		"female" : {
			"default" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
			"avali" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"avian" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"felin" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"floran" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"glitch" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"human" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"hylotl" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"novakid" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"penguin" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			}
		}
	},
	"pregnant" : {
		"default" : {
			"default" : {
				"default" : [
				]
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"avali" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"avian" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"felin" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"floran" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"glitch" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"human" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"hylotl" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"novakid" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"penguin" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			}
		},
		"male" : {
			"default" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
			"avali" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"avian" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"felin" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"floran" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"glitch" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"human" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"hylotl" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"novakid" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"penguin" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			}
		},
		"female" : {
			"default" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
			"avali" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"avian" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"felin" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"floran" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"glitch" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"human" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"hylotl" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"novakid" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"penguin" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			}
		}
	},
	"climaxing" : {
		"default" : {
			"default" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"avali" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"avian" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"felin" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"floran" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"glitch" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"human" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"hylotl" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"novakid" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"penguin" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			}
		},
		"male" : {
			"default" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"avali" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"avian" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"felin" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"floran" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"glitch" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"human" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"hylotl" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"novakid" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"penguin" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			}
		},
		"female" : {
			"default" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"avali" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"avian" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"felin" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"floran" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"glitch" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"human" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"hylotl" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"novakid" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
			"penguin" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			}
		}
	}
},
{--// Position: Actor 2 (Recieving). Being held (Butterfly), On top (Cowgirl), In front (Doggie), doing the sucking (Fellatio), Laying down (Missionary), in front (Standing).
    "default" : {--This actor's state. Valid entries are: "default" (having sex), "pregnant" and "climaxing".
        "default" : {--The partner's gender. Valid entries (so far) are "male", "female" or "default" (Basically functions as a wildcard).
            "default" : {--The partner's race. Entries should be obvious; "human", "penguin", etc. Also accepts "default" as a wildcard.
                "default" : [--The partner's state. Same valid entries as first tag. Note: Dialogue for both actors climaxing at once can be tricky to appear in-game, but works.
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "avali" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "avian" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "felin" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "floran" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "glitch" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "human" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "hylotl" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "novakid" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "penguin" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			}
		},
		"male" : {
            "default" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
			"avali" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
            "avian" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "felin" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "floran" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "glitch" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "human" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "hylotl" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "novakid" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "penguin" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			}
		},
		"female" : {
            "default" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
			"avali" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
            "avian" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "felin" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "floran" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "glitch" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "human" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "hylotl" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "novakid" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "penguin" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			}
		}
	},
	"pregnant" : {
		"default" : {
			"default" : {
				"default" : [
				],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "avali" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "avian" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "felin" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "floran" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "glitch" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "human" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "hylotl" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "novakid" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "penguin" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			}
		},
		"male" : {
            "default" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
			"avali" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
            "avian" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "felin" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "floran" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "glitch" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "human" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "hylotl" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "novakid" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "penguin" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			}
		},
		"female" : {
            "default" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
			"avali" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
            "avian" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "felin" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "floran" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "glitch" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "human" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "hylotl" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "novakid" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "penguin" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			}
		}
	},
	"climaxing" : {
		"default" : {
			"default" : {
				"default" : [
				],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "avali" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "avian" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "felin" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "floran" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "glitch" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "human" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "hylotl" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "novakid" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "penguin" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			}
		},
		"male" : {
            "default" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
			"avali" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
            "avian" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "felin" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "floran" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "glitch" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "human" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "hylotl" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "novakid" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "penguin" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			}
		},
		"female" : {
            "default" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
			"avali" : {
				"default" : [
				],
				"pregnant" : [
				],
				"climaxing" : [
				]
			},
            "avian" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "felin" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "floran" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "glitch" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "human" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "hylotl" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "novakid" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			},
            "penguin" : {
                "default" : [
                ],
				"pregnant" : [
				],
                "climaxing" : [
                ]
			}
		}
	}

--// Be aware that you do not have to use ALL of these lines if you don't want to, I have simply included all possible lines (barring non-avali and felin custom races) for the sake of convenience (it's easier to delete lines than it is to copy them). Keep in mind the idea of all of these
}]


code:
Some notes on dialogues for species:

* Apex: Hairy, bestial, yet sexually repressed due to Big Ape gov. References to Big Ape. Also stuff about "for science".
* Avian: Burbs. Puns about seeds, tickling feathers, flight, etc. Also references to Kluex.
* Avali: Cold Burbs. A lot of copyover with Avaian dialogue, but exclude any references to seeds or Kluex. Also refernces their cold bodies/insides.
  * Avali lore techinically says that Avalis have cloacas and mate by bashing micropenis against butts.... but since no one at E621 cares about that shit, neither do I.
* Felin: Cats. Lots of cat puns/lolcat puns. Also, barbed penises. Also some crossover with Avian and Floran (minus Kluex and plant-related stuff obviously.)
* Fenerox: Tribal canids. Bestial, but shy. References about knots. Lots of crossover with the above races.
* Floran: Plants. Lots of stuff about polen, nectar, weird feeling cocks, petals, etc. Also barbarity.
* Glitch: Robots. Cold metal. Vibrating cocks? Also oil. Machine related comments, obviously.
* Human: Refered to as "smoothskins" in unisex. Yes I stole that from Fallout, deal with it. Reference smooth, soft skin. Nothing else of note.
* Hylotl: Fish people. Sea puns, fish puns, wet bodies and cocks, etc. Not really sure about the whole "females shooting eggs" thing since that's more of a seahorse thing and less a fish thing. Oh well.
* Novakid: Gas people. Glow refs. Also very hot bodies and cum.


code:
[{--// Position: Butterfly. I am the one standing (Actor 1)
    "default" : {--This actor's state. Valid entries are: "default" (having sex), "pregnant" and "climaxing".
        "default" : {--The partner's gender. Valid entries (so far) are "male", "female" or "default" (Basically functions as a wildcard).
            "default" : {--The partner's race. Entries should be obvious; "human", "penguin", etc. Also accepts "default" as a wildcard.
                "default" : [--The partner's state. Same valid entries as first tag. Note: Dialogue for both actors climaxing at once can be tricky to appear in-game, but works.
                    "I am fucking a unspecified gender partner of an unspecified race in the X!"
                ],
                "climaxing" : [
                    "My unspecified gender partner of an unspecified race is cumming!"
                ],
				"pregnant" : [
					"My unspecified gender partner is pregnant!"
				]
			}
		},
		"male" : {
			"avali" : {
				"default" : [
					"I am fucking a male avali in the ass!"
				],
				"climaxing" : [
					"My avali male partner is cumming all over the floor!"
				]
			}
		}
	},
	"pregnant" : {
		"default" : {
			"default" : {
				"default" : [
					"I am fucking a unspecified gender partner of an unspecified race, and i am pregnant!"
				]
			}
		}
	}
},
{--// Position: Butterfly. I am the one being held. (Actor 2)
    "default" : {--This actor's state. Valid entries are: "default" (having sex), "pregnant" and "climaxing".
        "default" : {--The partner's gender. Valid entries (so far) are "male", "female" or "default" (Basically functions as a wildcard).
            "default" : {--The partner's race. Entries should be obvious; "human", "penguin", etc. Also accepts "default" as a wildcard.
                "default" : [--The partner's state. Same valid entries as first tag. Note: Dialogue for both actors climaxing at once can be tricky to appear in-game, but works.
                    "I am being fucked by a unspecified gender partner of an unspecified race in the X!"
                ],
                "climaxing" : [
                    "My unspecified gender partner of an unspecified race is cumming!"
                ],
				"pregnant" : [
					"My unspecified gender partner is pregnant!"
				]
			}
		},
		"male" : {
			"avali" : {
				"default" : [
					"I am being fucked by a male avali in the ass!"
				],
				"climaxing" : [
					"My avali male partner is cumming!"
				]
			}
		}
	},
	"pregnant" : {
		"default" : {
			"default" : {
				"default" : [
					"I am fucking a unspecified gender partner of an unspecified race, and i am pregnant!"
				]
			}
		}
	}
}]


code:
*.bat	text eol=crlf
*.tw	text eol=lf
*.sh	text eol=lf
*.py	text eol=lf
*.txt	text eol=lf
compile	text eol=lf
compile-git	text eol=lf
sanityCheck	text eol=lf

repo_name: shoku/fc

path: .gitattributes


code:
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
*.swp
*.swo

# C extensions
*.so

# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
.hypothesis/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# IPython Notebook
.ipynb_checkpoints

# pyenv
.python-version

# celery beat schedule file
celerybeat-schedule

# dotenv
.env

# virtualenv
venv/
ENV/

# Spyder project settings
.spyderproject

# Rope project settings
.ropeproject

# Start.tw
src/config/start.tw

path: .gitignore


code:
further development:
-specialized slave schools
-fortifications
-more levels for militia edict (further militarize society)
-conquering other arcologies?

Events:
-famous criminal escapes to the arcology, followed by another arcology police force

Bugs:
-sometimes troop counts breaks
-sometimes rebel numbers have fractionary parts

Rules Assistant:
- find a way for the new intense drugs to fit in
- everything mentioned in https://gitgud.io/pregmodfan/fc-pregmod/issues/81

main.tw porting:
- slaveart
- slavesummary
- slavesummaryuncached
- clothingSelectionJS.tw pronouns


code:
# How to vector art

Please read the whole document before starting to work.
Some aspects are not obvious right away.

Note: This does not actually describe how to be an artist.

## TL;DR

    killall inkscape
    artTools/vector_layer_split.py artTools/vector_source.svg tw src/art/vector/layers/
    compile

## 1. Be an artist

Make changes to the vector_source.svg.
Inkscape was thoroughly tested.
Adobe Illustrator might work decently, too.

## 2. Respect the structure

While editing, keep the Layers in mind.

* In Inkscape, Layers are special "Inkscape groups".
* In Illustrator, Layers are groups with user-definded IDs.
* All Layers should have an ID that is globally unique
  (not just within their subtree).

* Please use anonymous groups only for the ease of editing. Remove all "helper" groups before finally saving the file.
* Anonymous groups can be used for continous scaling (of e.g. boobs).

* Every asset that should go into a separate file needs to be in a labelled group
  (even if that is a group with only one shape).
* There are some globally available styles defined as CSS classes (e.g. skin, hair).
  Use them if your asset should be changed upon display.
  Do not set the style directly in the object.

## 3. Normalise the document (before committing)

**THIS IS IMPORTANT**

The various editors out there (Inkscape, Illustrator) may use different variants of formatting the SVG XML.
Use

    python3 normalize_svg.py vector_source.svg

before committing to normalise the format so git will not freak out about the changed indentation.

If you use Inkscape, please use in Edit → Settings → Input/Output → SVG-Output → Path Data → Optimized. This is the default.
In case your Editor uses another path data style which cannot be changed, please contact the other artists and developers via the issue tracker to find a new common ground.

What it does:
* Formats the SVG XML according to Pythons lxml module, regardless of editor.
* Adobe Illustrator uses group IDs as layer labels.
  Inkscape however uses layer labels and a separate, auto-generated group ID.
  normalize_svg.py overwrites the group ID with the Inkscape layer label
  so they are synchronised with Inkscape layer labels.
* Inkscape copies the global style definition into the object *every time*
  the object is edited. If an object references a CSS class AND at the same time
  has a local style, the local style is removed
  so global dynamic styling is possible later on.

Note: Behaviour of Adobe Illustrator is untested.

## 4. Split the layers

For NoX original, deepurk extensions, execute

    python3 vector_layer_split.py vector_source_ndmain.svg tw ../src/art/vector/layers/

For faraen revamped art (based on NoX original)

	python3 vector_revamp_layer_split.py vector_revamp_source.svg tw ../src/art/vector_revamp/layers/

. This application reads all groups in `vector_source.svg`.
Each group is stored in a separate file in the target directory `/src/art/vector/layers/`.
The group ID sets the file name. Therefore, the group ID **must not** contain spaces or any other weird characters.

Also consider:
* A group with ID ending in _ is ignored. Use Layers ending in _ to keep overview.
* A group with ID starting with "g" (Inkscape) or "XMLID" (Illustrator) is also ignored.
* Existing files are overwritten **without enquiry**.
* The target directory is not emptied. If a file is no longer needed, you should remove it manually.
* This procedure removes global definitions. This means, SVG filters are currently not supported.

Available output formats are `svg` and `tw`.
`svg` output exists for debug reasons.
`tw` embeds the SVG data into Twine files, but removes the global style definitions so they can be set during display.

## 5. Edit the code

`/src/art/` contains Twine code which shows the assets in the story.
There are many helpful comments in `/src/art/artWidgets.tw`.
The code also generates the previously removed global style definitions on the fly and per display.

## 6. Compile the story

Use the provided compile script (Windows batch or shell) for compiling.

## 7. Advanced usage

You can define multiple CSS classes to one element, e.g. "skin torso". When procedurally generating CSS, you can then set a global "skin" style, and a different one for "torso".

You can put variable text into the image using single quotes, e.g. "'+_tattooText+'". The single quotes *break* the quoting in Twine so the content of the Twine variable `_tattooText` is shown upon display. You can even align text on paths.

An anonymous group can be used for dynamic transformation. However, finding the correct parameters for the affine transformation matrix can be cumbersome. Use any method you see fit. See `src/art/vector/Boob.tw` for one example of the result.


code:
Apron
AttractiveLingerie
AttractiveLingerieForAPregnantWoman
BallGown
Battledress
BodyOil
Bunny
Chains
ChattelHabit
Cheerleader
ClubslutNetting
ComfortableBodysuit
Conservative
Cutoffs
Cybersuit
FallenNunsHabit
FuckdollSuit
HalterTopDress
HaremGauze
Hijab
Huipil
Kimono
LatexCatsuit
Leotard
LongQipao
MaternityDress
MilitaryUniform
MiniDress
Monokini
NiceBusinessAttire
NiceMaid
NiceNurse
No
PenitentNunsHabit
RedArmyUniform
RestrictiveLatex
ScalemailBikini
SchoolgirlUniform
SchutzstaffelUniform
ShibariRopes
SlaveGown
Slutty
SluttyBusinessAttire
SluttyJewelry
SluttyMaid
SluttyNurse
SluttyQipao
SluttySchutzstaffelUniform
Spats
StretchPants
StringBikini
Succubus
Toga
UncomfortableStraps
Western


code:
#!/bin/bash

# This script reads all possible values of $slave.clothes as mentioned by the documentation.
# This script uses the actual implementation of the JS clothing2artSuffix function as defined in src/art/vector/Helper_Functions.tw
# This script outputs suffixes to be used in the SVG naming scheme
# This script is meant to be executed at the project root directory.
# This script depends on bash, grep, sed, paste and nodejs (so best executed on Linux, I guess)

# grep -Porh '(?<=\.clothes = )"[^"]+"' src/ | sort | uniq # searches sources for all clothes strings actually used in the game, even undocumented ones

(
  echo 'var window = {};'
  grep -v '^:' src/art/vector/Helper_Functions.tw
  echo -n 'Array('
  sed '/^clothes:/,/:/!d' "slave variables documentation - Pregmod.txt" | grep '"' | paste -sd,
  echo ').forEach(v => {console.log(window.clothing2artSuffix(v));});'
) | nodejs | sort


code:
#!/usr/bin/env python3

'''
Application for "normalizing" SVGs

These problems are addressed:

* Inkscape notoriously copies class styles into the object definitions.
https://bugs.launchpad.net/inkscape/+bug/167937

* Inkscape uses labels on layers. Layers are basically named groups.
  Inkscape does not sync the group id with the layer label.

Usage Example:
python3 inkscape_svg_fixup.py vector_source.svg
'''

import lxml.etree as etree
import sys

def fix(tree):
  # know namespaces
  ns = {
    'svg' : 'http://www.w3.org/2000/svg',
    'inkscape' : 'http://www.inkscape.org/namespaces/inkscape'
  }

  # find document global style definition
  # mangle it and interpret as python dictionary
  style_element = tree.find('./svg:style',namespaces=ns)
  style_definitions = style_element.text
  pythonic_style_definitions = '{'+style_definitions.\
  replace('\t.','"').replace('{','":"').replace('}','",').\
  replace('/*','#')+'}'
  styles = eval(pythonic_style_definitions)

  # go through all SVG elements
  for elem in tree.iter():
    if (elem.tag == etree.QName(ns['svg'], 'g')):
      # compare inkscape label with group element ID
      l = elem.get(etree.QName(ns['inkscape'], 'label'))
      if l:
        i = elem.get('id')
        if (i != l):
          print("Overwriting ID %s with Label %s..."%(i, l))
          elem.set('id', l)

    # clean styles (for easier manual merging)
    style_string = elem.get('style')
    if style_string:
      split_styles = style_string.strip('; ').split(';')
      styles_pairs = [s.strip('; ').split(':') for s in split_styles]
      filtered_pairs = [ (k,v) for k,v in styles_pairs if not (
        k.startswith('font-') or
        k.startswith('text-') or
        k.endswith('-spacing') or
        k in ["line-height", " direction", " writing", " baseline-shift", " white-space", " writing-mode"]
      )]
      split_styles = [':'.join(p) for p in filtered_pairs]
      style_string = ';'.join(sorted(split_styles))
      elem.attrib["style"] = style_string

    # remove all style attributes offending class styles
    s = elem.get('style')
    c = elem.get('class')
    if (c and s):
      s = s.lower()
      c = c.split(' ')[0] # regard main style only
      cs = ''
      if c in styles:
        cs = styles[c].strip('; ').lower()
      if (c not in styles):
        print("Object id %s references unknown style class %s."%(i,c))
      else:
        if (cs != s.strip('; ')):
          print("Style %s removed from object id %s differed from class %s style %s."%(s,i,c,cs))
        del elem.attrib["style"]

if __name__ == "__main__":
  input_file = sys.argv[1]
  tree = etree.parse(input_file)
  fix(tree)
  # store SVG into file (input file is overwritten)
  svg = etree.tostring(tree, pretty_print=True)
  with open(input_file, 'wb') as f:
    f.write('<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'.encode("utf-8"))
    f.write(svg)


code:
#!/usr/bin/env python3

'''
Application for procedural content adaption
*THIS IS VERY EXPERIMENTAL*
Contains a very poor man's implementation of spline mesh warping.

This application parses SVG path data of an outfit sample aligned to a body part.
The outfit is then replicated for other shapes of the same body part.

Example data is geared towards generating a strap outfit for boobs and torso
for all sizes of boobs and all shapes of torsos based on a single outfit for
boobs of size 2 and a hourglass torso respectively.

Limitations:
* handles paths only
* only svg.path Line and CubicBezier are tested
* only the aforementioned examples are tested

Usage:
python3 vector_clothing_replicator.py infile clothing bodypart destinationfile

Usage Example:
python3 vector_clothing_replicator.py vector_source.svg Straps Boob vector_destination.svg
python3 vector_clothing_replicator.py vector_source.svg Straps Torso vector_destination.svg
'''

from svg.path import parse_path
import copy
import lxml.etree as etree
import sys

REFERENCE_PATH_SAMPLES = 200
EMBED_REPLICATIONS = True # wether to embed all replications into the input file or output separate files

input_file = sys.argv[1]
clothing = sys.argv[2]
bodypart = sys.argv[3]
output_file_embed = sys.argv[4]

# TODO: make these configurable
output_file_pattern = '%s_%s_%s.svg' #bodypart, target_id, clothing

if ('Torso' == bodypart):
  xpath_shape = './svg:g[@id="Torso_"]/svg:g[@id="Torso_%s"]/svg:path[@class="skin torso"]/@d' # TODO: formulate more general, independent of style
  xpath_outfit_container = '//svg:g[@id="Torso_Outfit_%s_"]'%(clothing)
  xpath_outfit = '//svg:g[@id="Torso_Outfit_%s_%s"]'%(clothing,'%s')
  target_ids = "Unnatural,Hourglass,Normal".split(",")
  reference_id = "Hourglass"
else:
  raise RuntimeError("Please specify a bodypart for clothing to replicate.")

tree = etree.parse(input_file)
ns = {'svg' : 'http://www.w3.org/2000/svg'}

canvas = copy.deepcopy(tree)
for e in canvas.xpath('./svg:g',namespaces=ns)+canvas.xpath('./svg:path',namespaces=ns):
  # TODO: this should be "remove all objects, preserve document properties"
  e.getparent().remove(e)

def get_points(xpath_shape):
  '''
  This funciton extracts reference paths by the given xpath selector.
  Each path is used to sample a fixed number of points.
  '''
  paths_data = tree.xpath(xpath_shape,namespaces=ns)
  points = []
  path_length = None
  for path_data in paths_data:
    p = parse_path(path_data)
    points += [
      p.point(1.0/float(REFERENCE_PATH_SAMPLES)*i)
      for i in range(REFERENCE_PATH_SAMPLES)
    ]
  if (not points):
    raise RuntimeError(
      'No paths for reference points found by selector "%s".'%(xpath_shape)
    )
  return points

def point_movement(point, reference_points, target_points):
  '''
  For a given point, finds the nearest point in the reference path.
  Gives distance vector from the nearest reference point to the
  respective target reference point.
  '''
  distances = [abs(point-reference_point) for reference_point in reference_points]
  min_ref_dist_idx = min(enumerate(distances), key=lambda x:x[1])[0]
  movement = target_points[min_ref_dist_idx] - reference_points[min_ref_dist_idx]
  return movement

reference_points = get_points(xpath_shape%(reference_id))
container = tree.xpath(xpath_outfit_container,namespaces=ns)
if (len(container) != 1):
  raise RuntimeError('Outfit container selector "%s" does not yield exactly one layer.'%(xpath_outfit_container))
container = container[0]
outfit_source = container.xpath(xpath_outfit%(reference_id),namespaces=ns)
if (len(outfit_source) != 1):
  raise RuntimeError('Outfit source selector "%s" does not yield exactly one outfit layer in container selected by "%s".'%(xpath_outfit%(reference_id), xpath_outfit_container))
outfit_source = outfit_source[0]

for target_id in target_ids:
  print(
    'Generating variant "%s" of clothing "%s" for bodypart "%s"...'%
    (target_id, clothing, bodypart)
  )
  outfit = copy.deepcopy(outfit_source)
  paths = outfit.xpath('./svg:path',namespaces=ns)
  if target_id == reference_id:
    print("This is the source variant. Skipping...")
  else:
    layerid = outfit.get('id').replace('_%s'%(reference_id),'_%s'%(target_id))
    outfit.set('id', layerid)
    outfit.set(etree.QName('http://www.inkscape.org/namespaces/inkscape', 'label'), layerid) # for the Inkscape-users
    target_points = get_points(xpath_shape%(target_id))
    if (len(reference_points) != len(target_points)):
      raise RuntimeError(
        ('Different amounts of sampled points in reference "%s" and target "%s" paths. '+
        'Selector "%s" probably matches different number of paths in the two layers.')%
        (reference_id, target_id, xpath_shape)
      )
    for path in paths:
      path_data = path.get("d")
      p = parse_path(path_data)
      for segment in p:
        original_distance = abs(segment.end-segment.start)
        start_movement = point_movement(segment.start, reference_points, target_points)
        segment.start += start_movement
        end_movement = point_movement(segment.end, reference_points, target_points)
        segment.end += end_movement
        distance = abs(segment.end-segment.start)
        try:
          # enhance position of CubicBezier control points
          # amplification is relative to the distance gained by movement
          segment.control1 += start_movement
          segment.control1 += (segment.control1-segment.start)*(distance/original_distance-1.0)
          segment.control2 += end_movement
          segment.control2 += (segment.control2-segment.end)*(distance/original_distance-1.0)
        except AttributeError as ae:
          # segment is not a CubicBezier
          pass
      path.set("d", p.d())
    if EMBED_REPLICATIONS:
      container.append(outfit)
  if not EMBED_REPLICATIONS:
    container = copy.deepcopy(canvas).xpath('.',namespaces=ns)[0]
    container.append(outfit)

  if not EMBED_REPLICATIONS:
    svg = etree.tostring(container, pretty_print=True)
    with open((output_file_pattern%(bodypart, target_id, clothing)).lower(), 'wb') as f:
      f.write('<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'.encode("utf-8"))
      f.write(svg)

if EMBED_REPLICATIONS:
  svg = etree.tostring(tree, pretty_print=True)
  with open(output_file_embed, 'wb') as f:
    f.write('<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'.encode("utf-8"))
    f.write(svg)


code:
#!/usr/bin/env python3

'''
Application for splitting groups from one SVG file into separate files

Usage:
python3 vector_layer_split.py infile format outdir

Usage Example:
python3 vector_layer_split.py vector_source.svg tw ../src/art/vector/layers/
'''

import lxml.etree as etree
import sys
import os
import copy
import re
import normalize_svg

input_file = sys.argv[1]
output_format = sys.argv[2]
output_directory = sys.argv[3]
if not os.path.exists(output_directory):
    os.makedirs(output_directory)

ns = {
  'svg' : 'http://www.w3.org/2000/svg',
  'inkscape' : 'http://www.inkscape.org/namespaces/inkscape',
  'sodipodi':"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd",
}

tree = etree.parse(input_file)
normalize_svg.fix(tree)

# prepare output template
template = copy.deepcopy(tree)
root = template.getroot()

# remove all svg root attributes except document size
for a in root.attrib:
  if (a != "viewBox"):
    del root.attrib[a]

# add placeholder for CSS class (needed for workaround for non HTML 5.1 compliant browser)
if output_format == 'tw':
  root.attrib["class"] = "'+_art_display_class+'"

# remove all content, including metadata
# for twine output, style definitions are removed, too
defs = None
for e in root:
  if (e.tag == etree.QName(ns['svg'], 'defs')):
    defs = e
  if (e.tag == etree.QName(ns['svg'], 'g') or
      e.tag == etree.QName(ns['svg'], 'metadata') or
      e.tag == etree.QName(ns['svg'], 'defs') or
      e.tag == etree.QName(ns['sodipodi'], 'namedview') or
      (output_format == 'tw' and e.tag == etree.QName(ns['svg'], 'style'))
      ):
    root.remove(e)
# template preparation finished

# prepare regex for later use
regex_xmlns = re.compile(' xmlns[^ ]+',)
regex_space = re.compile('[>][ ]+[<]',)

# find all groups
layers = tree.xpath('//svg:g',namespaces=ns)
for layer in layers:
  i = layer.get('id')
  if ( # disregard non-content groups
    i.endswith("_") or # manually suppressed with underscore
    i.startswith("XMLID") or # Illustrator generated group
    i.startswith("g") # Inkscape generated group
  ):
    continue
  # create new canvas
  output = copy.deepcopy(template)
  # copy all shapes into template
  canvas = output.getroot()
  for e in layer:
    canvas.append(e)
  # represent template as SVG (binary string)
  svg = etree.tostring(output, pretty_print=False)
  # poor man's conditional defs insertion
  # TODO: extract only referenced defs (filters, gradients, ...)
  # TODO: detect necessity by traversing the elements. do not stupidly search in the string representation
  if ("filter:" in svg.decode('utf-8')):
    # it seems there is a filter referenced in the generated SVG, re-insert defs from main document
    canvas.insert(0,defs)
    # re-generate output
    svg = etree.tostring(output, pretty_print=False)

  if (output_format == 'tw'):
    # remove unnecessary attributes
    # TODO: never generate unnecessary attributes in the first place
    svg = svg.decode('utf-8')
    svg = regex_xmlns.sub('',svg)
    svg = svg.replace(' inkscape:connector-curvature="0"','') # this just saves space
    svg = svg.replace('\n','').replace('\r','') # print cannot be multi-line
    svg = regex_space.sub('><',svg) # remove indentaion
    svg = svg.replace('svg:','') # svg namespace was removed
    svg = svg.replace('<g ','<g transform="\'+_art_transform+\'"') # internal groups are used for scaling
    svg = svg.encode('utf-8')

  # save SVG string to file
  i = layer.get('id')
  output_path = os.path.join(output_directory,i+'.'+output_format)
  with open(output_path, 'wb') as f:
    if (output_format == 'svg'):
      # Header for normal SVG (XML)
      f.write('<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'.encode("utf-8"))
      f.write(svg)
    elif (output_format == 'tw'):
      # Header for SVG in Twine file (SugarCube print statement)
      f.write((':: Art_Vector_%s [nobr]\n\n'%(i)).encode("utf-8"))
      f.write("<<print '<html>".encode("utf-8"))
      f.write(svg)
      f.write("</html>' >>".encode("utf-8"))