/*! Perseus | http://github.com/Khan/perseus */
// commit 4c6433bda16d46b7f2287d07a11cd2758b81e396
// branch twolittleboyshadtwolittletoys
(function webpackUniversalModuleDefinition(root, factory) {
	if(typeof exports === 'object' && typeof module === 'object')
		module.exports = factory();
	else if(typeof define === 'function' && define.amd)
		define(factory);
	else if(typeof exports === 'object')
		exports["Perseus"] = factory();
	else
		root["Perseus"] = factory();
})(this, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/ 	// The module cache
/******/ 	var installedModules = {};
/******/
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/
/******/ 		// Check if module is in cache
/******/ 		if(installedModules[moduleId])
/******/ 			return installedModules[moduleId].exports;
/******/
/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = installedModules[moduleId] = {
/******/ 			exports: {},
/******/ 			id: moduleId,
/******/ 			loaded: false
/******/ 		};
/******/
/******/ 		// Execute the module function
/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ 		// Flag the module as loaded
/******/ 		module.loaded = true;
/******/
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}
/******/
/******/
/******/ 	// expose the modules object (__webpack_modules__)
/******/ 	__webpack_require__.m = modules;
/******/
/******/ 	// expose the module cache
/******/ 	__webpack_require__.c = installedModules;
/******/
/******/ 	// __webpack_public_path__
/******/ 	__webpack_require__.p = "";
/******/
/******/ 	// Load entry module and return exports
/******/ 	return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ function(module, exports, __webpack_require__) {

	__webpack_require__(1);

	var version = __webpack_require__(46);

	module.exports = {
	    apiVersion:         version.apiVersion,
	    itemDataVersion:    version.itemDataVersion,
	    init:               __webpack_require__(2),
	    AnswerAreaRenderer: __webpack_require__(4),
	    ArticleEditor:      __webpack_require__(5),
	    ArticleRenderer:    __webpack_require__(6),
	    Editor:             __webpack_require__(7),
	    EditorPage:         __webpack_require__(8),
	    ItemRenderer:       __webpack_require__(9),
	    Renderer:           __webpack_require__(10),
	    RevisionDiff:       __webpack_require__(13),
	    StatefulEditorPage: __webpack_require__(11),
	    ClassNames:         __webpack_require__(12).ClassNames,
	    Util:               __webpack_require__(3)
	};


/***/ },
/* 1 */
/***/ function(module, exports, __webpack_require__) {

	var _ = __webpack_require__(60);

	var Widgets = __webpack_require__(14);

	_.each([
	    __webpack_require__(15),
	    __webpack_require__(16),
	    __webpack_require__(17),
	    __webpack_require__(18),
	    __webpack_require__(19),
	    __webpack_require__(20),
	    __webpack_require__(21),
	    __webpack_require__(22),
	    __webpack_require__(23),
	    __webpack_require__(24),
	    __webpack_require__(25),
	    __webpack_require__(26),
	    __webpack_require__(27),
	    __webpack_require__(28),
	    __webpack_require__(29),
	    __webpack_require__(30),
	    __webpack_require__(31),
	    __webpack_require__(32),
	    __webpack_require__(33),
	    __webpack_require__(34),
	    __webpack_require__(35),
	    __webpack_require__(36),
	    __webpack_require__(37),
	    __webpack_require__(38),
	    __webpack_require__(39),
	    __webpack_require__(40),
	    __webpack_require__(41),
	    __webpack_require__(42),
	    __webpack_require__(43),
	    __webpack_require__(44),
	    __webpack_require__(45)
	], function(widget) {
	    Widgets.register(widget.name, widget);
	});


/***/ },
/* 2 */
/***/ function(module, exports, __webpack_require__) {

	var _ = __webpack_require__(60);

	var init = function(options) {
	    _.defaults(options, {
	        // Pass skipMathJax: true if MathJax is already loaded and configured.
	        skipMathJax: false
	    });

	    var deferred = $.Deferred();

	    if (options.skipMathJax) {
	        deferred.resolve();
	    } else {
	        MathJax.Hub.Config({
	            messageStyle: "none",
	            skipStartupTypeset: "none",
	            "HTML-CSS": {
	                availableFonts: ["TeX"],
	                imageFont: null,
	                scale: 100,
	                showMathMenu: false
	            }
	        });

	        MathJax.Hub.Configured();
	        MathJax.Hub.Queue(deferred.resolve);
	    }

	    return deferred;
	};

	module.exports = init;


/***/ },
/* 3 */
/***/ function(module, exports, __webpack_require__) {

	var _ = __webpack_require__(60);

	var nestedMap = function(children, func, context) {
	    if (_.isArray(children)) {
	        return _.map(children, function(child) {
	            return nestedMap(child, func);
	        });
	    } else {
	        return func.call(context, children);
	    }
	};

	var Util = {
	    nestedMap: nestedMap,

	    rWidgetParts: /^\[\[\u2603 (([a-z-]+) ([0-9]+))\]\]$/,
	    rWidgetRule:  /^\[\[\u2603 (([a-z-]+) ([0-9]+))\]\]/,
	    rTypeFromWidgetId: /^([a-z-]+) ([0-9]+)$/,
	    snowman: "\u2603",

	    noScore: {
	        type: "points",
	        earned: 0,
	        total: 0,
	        message: null
	    },

	    seededRNG: function(seed) {
	        var randomSeed = seed;

	        return function() {
	            // Robert Jenkins' 32 bit integer hash function.
	            var seed = randomSeed;
	            seed = ((seed + 0x7ed55d16) + (seed << 12)) & 0xffffffff;
	            seed = ((seed ^ 0xc761c23c) ^ (seed >>> 19)) & 0xffffffff;
	            seed = ((seed + 0x165667b1) + (seed << 5)) & 0xffffffff;
	            seed = ((seed + 0xd3a2646c) ^ (seed << 9)) & 0xffffffff;
	            seed = ((seed + 0xfd7046c5) + (seed << 3)) & 0xffffffff;
	            seed = ((seed ^ 0xb55a4f09) ^ (seed >>> 16)) & 0xffffffff;
	            return (randomSeed = (seed & 0xfffffff)) / 0x10000000;
	        };
	    },

	    // Shuffle an array using a given random seed or function.
	    // If `ensurePermuted` is true, the input and ouput are guaranteed to be
	    // distinct permutations.
	    shuffle: function(array, randomSeed, ensurePermuted) {
	        // Always return a copy of the input array
	        var shuffled = _.clone(array);

	        // Handle edge cases (input array is empty or uniform)
	        if (!shuffled.length || _.all(shuffled, function(value) {
	                                    return _.isEqual(value, shuffled[0]);
	                                })) {
	            return shuffled;
	        }

	        var random;
	        if (_.isFunction(randomSeed)) {
	            random = randomSeed;
	        } else {
	            random = Util.seededRNG(randomSeed);
	        }

	        do {
	            // Fischer-Yates shuffle
	            for (var top = shuffled.length; top > 0; top--) {
	                var newEnd = Math.floor(random() * top),
	                    temp = shuffled[newEnd];

	                shuffled[newEnd] = shuffled[top - 1];
	                shuffled[top - 1] = temp;
	            }
	        } while (ensurePermuted && _.isEqual(array, shuffled));

	        return shuffled;
	    },

	    // In IE8, split doesn't work right. Implement it ourselves.
	    split: "x".split(/(.)/g).length ?
	        function(str, r) { return str.split(r); } :
	        function(str, r) {
	            // Based on Steven Levithan's MIT-licensed split, available at
	            // http://blog.stevenlevithan.com/archives/cross-browser-split
	            var output = [];
	            var lastIndex = r.lastIndex = 0;
	            var match;

	            while ((match = r.exec(str))) {
	                output.push(str.slice(lastIndex, match.index));
	                output.push.apply(output, match.slice(1));
	                lastIndex = match.index + match[0].length;
	            }

	            output.push(str.slice(lastIndex));
	            return output;
	        },

	    /**
	     * Given two score objects for two different widgets, combine them so that
	     * if one is wrong, the total score is wrong, etc.
	     */
	    combineScores: function(scoreA, scoreB) {
	        var message;

	        if (scoreA.type === "points" && scoreB.type === "points") {
	            if (scoreA.message && scoreB.message &&
	                    scoreA.message !== scoreB.message) {
	                // TODO(alpert): Figure out how to combine messages usefully
	                message = null;
	            } else {
	                message = scoreA.message || scoreB.message;
	            }

	            return {
	                type: "points",
	                earned: scoreA.earned + scoreB.earned,
	                total: scoreA.total + scoreB.total,
	                message: message
	            };

	        } else if (scoreA.type === "points" && scoreB.type === "invalid") {
	            return scoreB;

	        } else if (scoreA.type === "invalid" && scoreB.type === "points") {
	            return scoreA;

	        } else if (scoreA.type === "invalid" && scoreB.type === "invalid") {
	            if (scoreA.message && scoreB.message &&
	                    scoreA.message !== scoreB.message) {
	                // TODO(alpert): Figure out how to combine messages usefully
	                message = null;
	            } else {
	                message = scoreA.message || scoreB.message;
	            }

	            return {
	                type: "invalid",
	                message: message
	            };
	        }
	    },

	    keScoreFromPerseusScore: function(score, guess) {
	        if (score.type === "points") {
	            return {
	                empty: false,
	                correct: score.earned >= score.total,
	                message: score.message,
	                guess: guess
	            };
	        } else if (score.type === "invalid") {
	            return {
	                empty: true,
	                correct: false,
	                message: score.message,
	                guess: guess
	            };
	        } else {
	            throw new Error("Invalid score type: " + score.type);
	        }
	    },

	    /**
	     * Return the first valid interpretation of 'text' as a number, in the form
	     * {value: 2.3, exact: true}.
	     */
	    firstNumericalParse: function(text) {
	        // TODO(alpert): This is sort of hacky...
	        var first;
	        var val = Khan.answerTypes.predicate.createValidatorFunctional(
	            function(ans) {
	                first = ans;
	                return true;  /* break */
	            }, {
	                simplify: "optional",
	                inexact: true,
	                forms: "integer, proper, improper, pi, log, mixed, decimal"
	            });

	        val(text);
	        return first;
	    },

	    stringArrayOfSize: function(size) {
	        return _(size).times(function() {
	            return "";
	        });
	    },

	    /**
	     * For a graph's x or y dimension, given the tick step,
	     * the ranges extent (e.g. [-10, 10]), the pixel dimension constraint,
	     * and the grid step, return a bunch of configurations for that dimension.
	     *
	     * Example:
	     *      gridDimensionConfig(10, [-50, 50], 400, 5)
	     *
	     * Returns: {
	     *      scale: 4,
	     *      snap: 2.5,
	     *      tickStep: 2,
	     *      unityLabel: true
	     * };
	     */
	    gridDimensionConfig: function(absTickStep, extent, dimensionConstraint,
	                                     gridStep) {
	        var scale = Util.scaleFromExtent(extent, dimensionConstraint);
	        var stepPx = absTickStep * scale;
	        var unityLabel = stepPx > 30;
	        return {
	            scale: scale,
	            tickStep: absTickStep / gridStep,
	            unityLabel: unityLabel
	        };
	    },

	    /**
	     * Given the range, step, and boxSize, calculate the reasonable gridStep.
	     * Used for when one was not given explicitly.
	     *
	     * Example:
	     *      getGridStep([[-10, 10], [-10, 10]], [1, 1], 340)
	     *
	     * Returns: [1, 1]
	     */
	    getGridStep: function(range, step, boxSize) {
	        return _(2).times(function(i) {
	            var scale = Util.scaleFromExtent(range[i], boxSize);
	            var gridStep = Util.gridStepFromTickStep(step[i], scale);
	            return gridStep;
	        });
	    },

	    snapStepFromGridStep: function(gridStep) {
	        return _.map(gridStep, function(step) { return step / 2; });
	    },

	    /**
	     * Given the range and a dimension, come up with the appropriate
	     * scale.
	     * Example:
	     *      scaleFromExtent([-25, 25], 500) // returns 10
	     */
	    scaleFromExtent: function(extent, dimensionConstraint) {
	        var span = extent[1] - extent[0];
	        var scale = dimensionConstraint / span;
	        return scale;
	    },

	    /**
	     * Return a reasonable tick step given extent and dimension.
	     * (extent is [begin, end] of the domain.)
	     * Example:
	     *      tickStepFromExtent([-10, 10], 300) // returns 2
	     */
	    tickStepFromExtent: function(extent, dimensionConstraint) {
	        var span = extent[1] - extent[0];

	        var tickFactor;
	        // If single number digits
	        if (15 < span && span <= 20) {
	            tickFactor = 23;

	        // triple digit or decimal
	        } else if (span > 100 || span < 5) {
	            tickFactor = 10;

	        // double digit
	        } else {
	            tickFactor = 16;
	        }
	        var constraintFactor = dimensionConstraint / 500;
	        var desiredNumTicks = tickFactor * constraintFactor;
	        return Util.tickStepFromNumTicks(span, desiredNumTicks);
	    },

	    /**
	     * Given the tickStep and the graph's scale, find a
	     * grid step.
	     * Example:
	     *      gridStepFromTickStep(200, 0.2) // returns 100
	     */
	    gridStepFromTickStep: function(tickStep, scale) {
	        var tickWidth = tickStep * scale;
	        var x = tickStep;
	        var y = Math.pow(10, Math.floor(Math.log(x) / Math.LN10));
	        var leadingDigit = Math.floor(x / y);
	        if (tickWidth < 25) {
	            return tickStep;
	        }
	        if (tickWidth < 50) {
	            if (leadingDigit === 5) {
	                return tickStep;
	            } else {
	                return tickStep / 2;
	            }
	        }
	        if (leadingDigit === 1) {
	            return tickStep / 2;
	        }
	        if (leadingDigit === 2) {
	            return tickStep / 4;
	        }
	        if (leadingDigit === 5) {
	            return tickStep / 5;
	        }
	    },

	    /**
	     * Find a good tick step for the desired number of ticks in the range
	     * Modified from d3.scale.linear: d3_scale_linearTickRange.
	     * Thanks, mbostock!
	     * Example:
	     *      tickStepFromNumTicks(50, 6) // returns 10
	     */
	    tickStepFromNumTicks: function(span, numTicks) {
	        var step = Math.pow(10, Math.floor(Math.log(span / numTicks) / Math.LN10));
	        var err = numTicks / span * step;

	        // Filter ticks to get closer to the desired count.
	        if (err <= 0.15) {
	            step *= 10;
	        } else if (err <= 0.35) {
	            step *= 5;
	        } else if (err <= 0.75) {
	            step *= 2;
	        }

	        // Round start and stop values to step interval.
	        return step;
	    },

	    /**
	     * Transparently update deprecated props so that the code to deal
	     * with them only lives in one place: (Widget).deprecatedProps
	     *
	     * For example, if a boolean `foo` was deprecated in favor of a
	     * number 'bar':
	     *      deprecatedProps: {
	     *          foo: function(props) {
	     *              return {bar: props.foo ? 1 : 0};
	     *          }
	     *      }
	     */
	    DeprecationMixin: {
	        // This lifecycle stage is only called before first render
	        componentWillMount: function() {
	            var newProps = {};

	            _.each(this.deprecatedProps, function(func, prop) {
	                if (_.has(this.props, prop)) {
	                    _.extend(newProps, func(this.props));
	                }
	            }, this);

	            if (!_.isEmpty(newProps)) {
	                // Set new props directly so that widget renders correctly
	                // when it first mounts, even though these will be overwritten
	                // almost immediately afterwards...
	                _.extend(this.props, newProps);

	                // ...when we propagate the new props upwards and they come
	                // back down again.
	                setTimeout(this.props.onChange, 0, newProps);
	            }
	        }
	    },

	    /**
	     * Approximate equality on numbers and primitives.
	     */
	    eq: function(x, y) {
	        if (_.isNumber(x) && _.isNumber(y)) {
	            return Math.abs(x - y) < 1e-9;
	        } else {
	            return x === y;
	        }
	    },

	    /**
	     * Deep approximate equality on primitives, numbers, arrays, and objects.
	     */
	    deepEq: function(x, y) {
	        if (_.isArray(x) && _.isArray(y)) {
	            if (x.length !== y.length) {
	                return false;
	            }
	            for (var i = 0; i < x.length; i++) {
	                if (!Util.deepEq(x[i], y[i])) {
	                    return false;
	                }
	            }
	            return true;
	        } else if (_.isArray(x) || _.isArray(y)) {
	            return false;
	        } else if (_.isFunction(x) && _.isFunction(y)) {
	            return Util.eq(x, y);
	        } else if (_.isFunction(x) || _.isFunction(y)) {
	            return false;
	        } else if (_.isObject(x) && _.isObject(y)) {
	            return x === y || (
	                _.all(x, function(v, k) { return Util.deepEq(y[k], v); }) &&
	                _.all(y, function(v, k) { return Util.deepEq(x[k], v); })
	            );
	        } else if (_.isObject(x) || _.isObject(y)) {
	            return false;
	        } else {
	            return Util.eq(x, y);
	        }
	    },

	    /**
	     * Query String Parser
	     *
	     * Original from:
	     * http://stackoverflow.com/questions/901115/get-querystring-values-in-javascript/2880929#2880929
	     */
	    parseQueryString: function(query) {
	        query = query || window.location.search.substring(1);
	        var urlParams = {},
	            e,
	            a = /\+/g,  // Regex for replacing addition symbol with a space
	            r = /([^&=]+)=?([^&]*)/g,
	            d = function(s) { return decodeURIComponent(s.replace(a, " ")); };

	        while ((e = r.exec(query))) {
	            urlParams[d(e[1])] = d(e[2]);
	        }

	        return urlParams;
	    },

	    /** 
	     * Query string adder
	     * Works for URLs without #.
	     * Original from:
	     * http://stackoverflow.com/questions/5999118/add-or-update-query-string-parameter
	     */
	    updateQueryString: function(uri, key, value) {
	        value = encodeURIComponent(value);
	        var re = new RegExp("([?&])" + key + "=.*?(&|$)", "i");
	        var separator = uri.indexOf('?') !== -1 ? "&" : "?";
	        if (uri.match(re)) {
	            return uri.replace(re, '$1' + key + "=" + value + '$2');
	        } else {
	            return uri + separator + key + "=" + value;
	        }
	    },

	    /**
	     * A more strict encodeURIComponent that escapes `()'!`s
	     * Especially useful for creating URLs that are embeddable in markdown
	     *
	     * Adapted from
	     * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
	     * This function and the above original available under the
	     * CC-BY-SA 2.5 license.
	     */
	    strongEncodeURIComponent: function(str) {
	        return encodeURIComponent(str)
	            // Note that although RFC3986 reserves "!", RFC5987 does not,
	            // so we do not need to escape it
	            .replace(/['()!]/g, window.escape) // i.e., %27 %28 %29
	            .replace(/\*/g, '%2A');
	    },

	    // There are certain widgets where we don't want to provide the "answered"
	    // highlight indicator.
	    // The issue with just using the `graded` flag on questions is that showing
	    // that a certain widget is ungraded can sometimes reveal the answer to a
	    // question ("is this transformation possible? if so, do it")
	    // This is kind of a hack to get around this.
	    widgetShouldHighlight: function(widget) {
	        if (!widget) {
	            return false;
	        }
	        var HIGHLIGHT_BAR_BLACKLIST = ["measurer", "protractor"];
	        return !_.contains(HIGHLIGHT_BAR_BLACKLIST, widget.type);
	    },

	    /**
	     * If a widget says that it is empty once it is graded.
	     * Trying to encapsulate references to the score format.
	     */
	    scoreIsEmpty: function(score) {
	        return score.type === "invalid";
	    },

	    /**
	     * Extracts the location of a touch or mouse event, allowing you to pass
	     * in a "mouseup", "mousedown", or "mousemove" event and receive the
	     * correct coordinates. Shouldn't be used with "vmouse" events.
	     *
	     * The Util.touchHandlers are used to track the current state of the touch
	     * event, such as whether or not the user is currently pressed down (either
	     * through touch or mouse) on the screen.
	     */

	    touchHandlers: {
	        pointerDown: false,
	        currentTouchIdentifier: null
	    },

	    resetTouchHandlers: function() {
	        _.extend(Util.touchHandlers, {
	            pointerDown: false,
	            currentTouchIdentifier: null
	        });
	    },

	    extractPointerLocation: function(event) {
	        var touchOrEvent;

	        if (Util.touchHandlers.pointerDown) {
	            // Look for the touch matching the one we're tracking; ignore others
	            if (Util.touchHandlers.currentTouchIdentifier != null) {
	                var len = event.changedTouches ? event.changedTouches.length : 0;
	                for (var i = 0; i < len; i++) {
	                    if (event.changedTouches[i].identifier ===
	                            Util.touchHandlers.currentTouchIdentifier) {
	                        touchOrEvent = event.changedTouches[i];
	                    }
	                }
	            } else {
	                touchOrEvent = event;
	            }

	            var isEndish =
	                    event.type === "touchend" || event.type === "touchcancel";
	            if (touchOrEvent && isEndish) {
	                Util.touchHandlers.pointerDown = false;
	                Util.touchHandlers.currentTouchIdentifier = null;
	            }
	        } else {
	            // touchstart or mousedown
	            Util.touchHandlers.pointerDown = true;
	            if (event.touches) {
	                touchOrEvent = event.touches[0];
	                Util.touchHandlers.currentTouchIdentifier = touchOrEvent.identifier;
	            } else {
	                touchOrEvent = event;
	            }
	        }

	        if (touchOrEvent) {
	            return {
	                left: touchOrEvent.pageX,
	                top: touchOrEvent.pageY
	            };
	        }
	    },

	    /**
	     * Pass this function as the touchstart for an element to
	     * avoid sending the touch to the mobile scratchpad
	     */
	    captureScratchpadTouchStart: function(e) {
	        e.stopPropagation();
	    },

	    getImageSize: function(url, callback) {
	        var img = new Image();
	        img.onload = function() {
	            // IE 11 seems to have problems calculating the heights of svgs
	            // if they're not in the DOM. To solve this, we add the element to
	            // the dom, wait for a rerender, and use `.clientWidth` and
	            // `.clientHeight`. I think we could also solve the problem by
	            // adding the image to the document before setting the src, but then
	            // the experience would be worse for other browsers.
	            if (img.width === 0 && img.height === 0) {
	                document.body.appendChild(img);
	                _.defer(function() {
	                    callback(img.clientWidth, img.clientHeight);
	                    document.body.removeChild(img);
	                });
	            } else {
	                callback(img.width, img.height);
	            }
	        };

	        // Require here to prevent recursive imports
	        var SvgImage = __webpack_require__(57);
	        img.src = SvgImage.getRealImageUrl(url);
	    },

	    textarea: {

	        /**
	         * Gets the word right before where the textarea cursor is
	         *
	         * @param {Element} textarea - The textarea DOM element
	         * @return {JSON} - An object with the word and its starting and ending positions in the textarea
	         */
	        getWordBeforeCursor: function(textarea) {
	            var text = textarea.value;

	            var endPos = textarea.selectionStart - 1;
	            var startPos = Math.max(text.lastIndexOf("\n", endPos), text.lastIndexOf(' ', endPos)) + 1;

	            return {
	                string: text.substring(startPos, endPos + 1),
	                pos: {
	                    start: startPos,
	                    end: endPos
	                }
	            };
	        },

	        /**
	         * Moves the textarea cursor at the specified position
	         *
	         * @param {Element} textarea - The textarea DOM element
	         * @param {int} pos - The position where the cursor will be moved
	         */
	        moveCursor: function(textarea, pos) {
	            textarea.selectionStart = pos;
	            textarea.selectionEnd = pos;
	        }
	    }
	};

	Util.random = Util.seededRNG(new Date().getTime() & 0xffffffff);

	module.exports = Util;


/***/ },
/* 4 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);
	var _ = __webpack_require__(60);

	var Renderer = __webpack_require__(10);
	var QuestionParagraph = __webpack_require__(47);
	var WidgetContainer = __webpack_require__(48);
	var Widgets = __webpack_require__(14);

	var Util = __webpack_require__(3);
	var EnabledFeatures = __webpack_require__(49);
	var ApiOptions = __webpack_require__(12).Options;

	var SINGLE_ITEM_WIDGET_ID = "answer-area";
	var PT = React.PropTypes;

	var AnswerAreaRenderer = React.createClass({displayName: 'AnswerAreaRenderer',
	    propTypes: {
	        type: PT.string,
	        options: PT.object,
	        calculator: PT.bool,
	        problemNum: PT.number,
	        onInteractWithWidget: PT.func.isRequired,
	        enabledFeatures: EnabledFeatures.propTypes,
	        highlightedWidgets: PT.array.isRequired,
	        apiOptions: ApiOptions.propTypes
	    },

	    getDefaultProps: function() {
	        return {
	            problemNum: 0,
	            onInteractWithWidget: function() {},
	            enabledFeatures: EnabledFeatures.defaults,
	            highlightedWidgets: [],
	            apiOptions: ApiOptions.defaults
	        };
	    },

	    getInitialState: function() {
	        // TODO(alpert): Move up to parent props?
	        return {
	            widget: {},
	            cls: this.getClass(this.props.type)
	        };
	    },

	    componentWillReceiveProps: function(nextProps) {
	        this.setState({cls: this.getClass(nextProps.type)});
	    },

	    getWidgetInstance: function() {
	        // Half the time this is a Renderer; the other half of the time it's a
	        // real widget.
	        if (this.props.type === "multiple") {
	            return this.refs.widget;
	        } else {
	            return this.refs.container.getWidget();
	        }
	    },

	    getClass: function(type) {
	        if (type === "multiple") {
	            return Renderer;
	        } else {
	            return Widgets.getWidget(type, this.props.enabledFeatures);
	        }
	    },

	    render: function() {
	        if (this.props.type === "multiple") {
	            return this.renderMultiple();
	        } else {
	            return this.renderSingle();
	        }
	    },

	    emptyWidgets: function() {
	        if (this.props.type === "multiple") {
	            return this.getWidgetInstance().emptyWidgets();
	        } else {
	            return Util.scoreIsEmpty(
	                this.getWidgetInstance().simpleValidate(this.props.options)) ?
	                [SINGLE_ITEM_WIDGET_ID] : [];
	        }
	    },

	    // Gets a focus object fixed up with an "answer-" prefix for
	    // onFocusChange when type === "multiple"
	    _getAnswerAreaFocusObj: function(rendererFocusPath) {
	        if (rendererFocusPath == null) {
	            return rendererFocusPath;
	        }
	        // TODO(jack): make "answer" the first element of the prefix
	        // array, rather than modifying the widgetId, once we have
	        // expunged widgetIds from the rest of the api calls in
	        // favor of focus paths
	        var answerPath = ["answer-" + _.first(rendererFocusPath)].concat(
	            _.rest(rendererFocusPath));
	        return answerPath;
	    },

	    renderMultiple: function() {
	        var parentOnFocusChange = this.props.apiOptions.onFocusChange;
	        var apiOptions = _.extend(
	            {},
	            ApiOptions.defaults,
	            this.props.apiOptions,
	            parentOnFocusChange && {
	                onFocusChange: function(newFocus, oldFocus)  {
	                    // If we have an apiOptions.onFocusChange, call
	                    // it with an "answer-" prefix on our widget id
	                    parentOnFocusChange(
	                        this._getAnswerAreaFocusObj(newFocus),
	                        this._getAnswerAreaFocusObj(oldFocus)
	                    );
	                }.bind(this)
	            }
	        );

	        return React.createElement(this.state.cls, React.__spread({
	            ref: "widget", 
	            problemNum: this.props.problemNum, 
	            onChange: this.handleChangeRenderer, 
	            onInteractWithWidget: this.props.onInteractWithWidget, 
	            highlightedWidgets: this.props.highlightedWidgets, 
	            enabledFeatures: _.extend({}, this.props.enabledFeatures, {
	                // Hide answer area tooltip formats,
	                // the "Acceptable formats" box already works
	                toolTipFormats: false
	            }), 
	            apiOptions: apiOptions}, 
	            this.props.options, 
	            this.state.widget)
	        );
	    },

	    renderSingle: function() {
	        var shouldHighlight = _.contains(this.props.highlightedWidgets,
	                                    SINGLE_ITEM_WIDGET_ID);

	        return React.createElement(QuestionParagraph, null, 
	            React.createElement(WidgetContainer, {
	                ref: "container", 
	                key: this.props.type, 
	                type: this.state.cls, 
	                initialProps: this.getSingleWidgetProps(), 
	                shouldHighlight: shouldHighlight})
	        );
	    },

	    getSingleWidgetProps: function() {
	        var apiOptions = _.extend(
	            {},
	            ApiOptions.defaults,
	            this.props.apiOptions
	        );

	        // Pass onFocus/onBlur handlers to each widget, so they
	        // can trigger `onFocusChange`s if/when those happen.
	        // Since we're just a single widget, any focusing has
	        // to be from nothing (path: null), and any blurring has
	        // to be to nothing. Our parent ItemRenderer will handle
	        // connecting the dots between these events and any
	        // focusing/blurring between elements in the question
	        // area to combine this event with those into a single
	        // onChangeFocus at the ItemRenderer level.
	        var onFocus = function(path, elem)  {
	            this._isFocused = true;
	            apiOptions.onFocusChange(
	                [SINGLE_ITEM_WIDGET_ID].concat(path),
	                // we're pretending we're a renderer, so if we got
	                // focus, we must not have had it before
	                null);
	        }.bind(this);
	        var onBlur = function(path, elem)  {
	            this._isFocused = false;
	            apiOptions.onFocusChange(null,
	                [SINGLE_ITEM_WIDGET_ID].concat(path));
	        }.bind(this);

	        var initialWidgetProps = Widgets.getRendererPropsForWidgetInfo(
	            this.props,
	            this.props.problemNum
	        );

	        return _.extend({
	            widgetId: SINGLE_ITEM_WIDGET_ID,
	            problemNum: this.props.problemNum,
	            onChange: this.handleChangeRenderer,
	            enabledFeatures: _.extend({}, this.props.enabledFeatures, {
	                // Hide answer area tooltip formats,
	                // the "Acceptable formats" box already works
	                toolTipFormats: false
	            }),
	            apiOptions: apiOptions,
	            onFocus: onFocus,
	            onBlur: onBlur
	        }, initialWidgetProps, this.state.widget);
	    },

	    _setWidgetProps: function(widgetId, newProps, cb) {
	        // "area" -> global id "answer-area" ;)
	        if (widgetId === "area" && this.props.type !== "multiple") {
	            // We have a single widget
	            this.handleChangeRenderer(newProps, cb);
	        } else if (this.props.type === "multiple") {
	            // We have a `Renderer`
	            this.getWidgetInstance()._setWidgetProps(widgetId, newProps, cb);
	        } else if ((typeof console) !== "undefined" && console.error) {
	            // We have a widget id other than area in a non-renderer area
	            console.error(
	                "Sent invalid widget id `answer-" + widgetId +
	                "` to an answerArea of type `" + this.props.type + "`."
	            );
	        }
	    },

	    handleChangeRenderer: function(newProps, cb) {
	        var widget = _.extend({}, this.state.widget, newProps);
	        this.setState({widget: widget}, function()  {
	            if (this.props.type !== "multiple") {
	                var cbResult = cb && cb();
	                this.props.onInteractWithWidget(SINGLE_ITEM_WIDGET_ID);
	                // If we're not type === "multiple", send an onFocusChange
	                // event to focus to this widget if we aren't already focused.
	                // For type "multiple" these events are handled in the
	                // multiple's Renderer

	                if (cbResult !== false &&
	                        this.props.apiOptions.onFocusChange &&
	                        !this._isFocused) {
	                    this._isFocused = true;
	                    this.props.apiOptions.onFocusChange(
	                        [SINGLE_ITEM_WIDGET_ID],
	                        // we're pretending we're a renderer, so if we got
	                        // focus, we must not have had it before
	                        null);
	                }
	            }
	        }.bind(this));
	    },

	    componentDidMount: function() {
	        // Storing things directly on components should be avoided!
	        this._isFocused = false;
	        this.update();
	    },

	    componentDidUpdate: function(prevProps) {
	        this.update();

	        if (this.props.type !== "multiple" &&
	                prevProps.type === this.props.type) {
	            this.refs.container.replaceWidgetProps(
	                this.getSingleWidgetProps());
	        }
	    },

	    update: function() {
	        $("#calculator").toggle(this.props.calculator);
	    },

	    componentWillUnmount: function() {
	        if (this.props.calculator) {
	            $("#calculator").hide();
	        }
	    },

	    getDOMNodeForPath: function(path) {
	        var prefixedWidgetId = _.first(path);
	        var interWidgetPath = _.rest(path);

	        if (this.props.type === "multiple") {
	            var widgetId = prefixedWidgetId.replace('answer-', '');
	            var relativePath = [widgetId].concat(interWidgetPath);

	            // Answer-area is a renderer, so we can pass down the path
	            return this.getWidgetInstance().getDOMNodeForPath(relativePath);
	        } else {
	            // Answer-area is a widget, so we treat it like a widget, returning
	            // the outer node in the special-case that there is no remaining
	            // path.
	            var widget = this.getWidgetInstance();
	            var getNode = widget.getDOMNodeForPath;
	            if (getNode) {
	                return getNode(interWidgetPath);
	            } else if (interWidgetPath.length === 0) {
	                return widget.getDOMNode();
	            }
	        }
	    },

	    getGrammarTypeForPath: function(path) {
	        // TODO(emily): refactor this kind of logic out, or wait until alex
	        // kills the answer-area-renderer and solves it for me.
	        var prefixedWidgetId = _.first(path);
	        var interWidgetPath = _.rest(path);

	        if (this.props.type === "multiple") {
	            var widgetId = prefixedWidgetId.replace('answer-', '');
	            var relativePath = [widgetId].concat(interWidgetPath);

	            // Answer-area is a renderer, so we can pass down the path
	            return this.getWidgetInstance().getGrammarTypeForPath(
	                relativePath);
	        } else {
	            // Answer-area is a widget, so we treat it like a widget.
	            var widget = this.getWidgetInstance();
	            return widget.getGrammarTypeForPath(interWidgetPath);
	        }
	    },

	    getInputPaths: function() {
	        if (this.props.type === "multiple") {
	            var inputPaths = this.getWidgetInstance().getInputPaths();
	            // We need to prefix our widgetIds with 'answer-' to preserve
	            // uniqueness when we return these to the item-renderer.
	            return _.map(inputPaths, function(path)  {
	                var prefixedWidget = 'answer-' + _.first(path);
	                return [prefixedWidget].concat(_.rest(path));
	            });
	        } else {
	            var widgetId = "answer-area";
	            var inputPaths = [];
	            var widget = this.getWidgetInstance();
	            if (widget.getInputPaths) {
	                // Grab all input paths and add widgetID to the front
	                var widgetInputPaths = widget.getInputPaths();

	                if (widgetInputPaths === widget) {
	                    // Special case: we allow you to just return the widget
	                    inputPaths.push([
	                        widgetId
	                    ]);
	                } else {
	                    // Add to collective list of inputs
	                    _.each(widgetInputPaths, function(inputPath)  {
	                        var relativeInputPath = [widgetId].concat(inputPath);
	                        inputPaths.push(relativeInputPath);
	                    });
	                }
	            }
	            return inputPaths;
	        }
	    },

	    focusPath: function(path) {
	        var prefixedWidgetId = _.first(path);
	        var interWidgetPath = _.rest(path);

	        if (this.props.type === "multiple") {
	            var widgetId = prefixedWidgetId.replace('answer-', '');
	            var relativePath = [widgetId].concat(interWidgetPath);

	            // Answer-area is a renderer, so we can pass down the path
	            this.getWidgetInstance().focusPath(relativePath);
	        } else {
	            // Answer-area is a widget
	            var focusWidget = this.getWidgetInstance().focusInputPath;
	            focusWidget && focusWidget(interWidgetPath);
	        }
	    },

	    blurPath: function(path) {
	        var prefixedWidgetId = _.first(path);
	        var interWidgetPath = _.rest(path);

	        if (this.props.type === "multiple") {
	            var widgetId = prefixedWidgetId.replace('answer-', '');
	            var relativePath = [widgetId].concat(interWidgetPath);

	            // Answer-area is a renderer, so we can pass down the path
	            this.getWidgetInstance().blurPath(relativePath);
	        } else {
	            // Answer-area is a widget
	            var blurWidget = this.getWidgetInstance().blurInputPath;
	            blurWidget && blurWidget(interWidgetPath);
	        }
	    },

	    focus: function() {
	        var focusContents = this.getWidgetInstance().focus;
	        focusContents && focusContents();
	    },

	    blur: function() {
	        var blurContents = this.getWidgetInstance().blur;
	        blurContents && blurContents();
	    },

	    setInputValue: function(path, newValue, focus) {
	        var prefixedWidgetId = _.first(path);
	        var interWidgetPath = _.rest(path);

	        var newPath;
	        if (this.props.type === "multiple") {
	            var widgetId = prefixedWidgetId.replace('answer-', '');
	            var relativePath = [widgetId].concat(interWidgetPath);
	            newPath = relativePath;
	        } else {
	            newPath = interWidgetPath;
	        }

	        // Thankfully, the API is agnostic! So it doesn't matter if this is a
	        // renderer or a widget.
	        this.getWidgetInstance().setInputValue(newPath, newValue, focus);
	    },

	    guessAndScore: function() {
	        // TODO(alpert): These should probably have the same signature...
	        if (this.props.type === "multiple") {
	            return this.getWidgetInstance().guessAndScore();
	        } else {
	            var guess = this.getWidgetInstance().getUserInput();

	            var score;
	            if (this.props.graded == null || this.props.graded) {
	                // props.graded is unset or true
	                // TODO(alpert): Separate out the rubric
	                score = this.getWidgetInstance().simpleValidate(
	                    this.props.options);
	            } else {
	                score = Util.noScore;
	            }

	            return [guess, score];
	        }
	    }
	});

	module.exports = AnswerAreaRenderer;


/***/ },
/* 5 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);
	var _ = __webpack_require__(60);

	var ApiOptions = __webpack_require__(12).Options;
	var ArticleRenderer = __webpack_require__(6);
	var Editor = __webpack_require__(7);
	var EnabledFeatures = __webpack_require__(49);
	var JsonEditor = __webpack_require__(50);
	var Renderer = __webpack_require__(10);

	var rendererProps = React.PropTypes.shape({
	    content: React.PropTypes.string,
	    widgets: React.PropTypes.object,
	    images: React.PropTypes.object,
	});

	var SectionControlButton = React.createClass({displayName: 'SectionControlButton',
	    render: function() {
	        return React.createElement("a", {
	                href: "#", 
	                className: 
	                    "section-control-button " +
	                    "simple-button " +
	                    "simple-button--small " +
	                    "orange", 
	                
	                onClick: function(e)  {
	                    e.preventDefault();
	                    this.props.onClick();
	                }.bind(this)}, 
	            React.createElement("span", {className: this.props.icon})
	        );
	    }
	});

	var ArticleEditor = React.createClass({displayName: 'ArticleEditor',

	    propTypes: {
	        json: React.PropTypes.oneOfType([
	            rendererProps,
	            React.PropTypes.arrayOf(rendererProps)
	        ]),
	        apiOptions: React.PropTypes.object,
	        developerMode: React.PropTypes.bool,
	        enabledFeatures: EnabledFeatures.propTypes,
	        imageUploader: React.PropTypes.func,
	        onChange: React.PropTypes.func.isRequired,
	    },

	    getDefaultProps: function() {
	        return {
	            developerMode: false,
	            json: [{}],
	            enabledFeatures: {
	                toolTipFormats: true,
	                useMathQuill: true
	            },
	        };
	    },

	    getInitialState: function() {
	        return {
	            mode: "edit"
	        };
	    },

	    render: function() {

	        return React.createElement("div", {className: "framework-perseus perseus-article"}, 
	            React.createElement("div", null, 
	                React.createElement("label", null, 
	                    "Mode:", " ", 
	                    React.createElement("select", {
	                            value: this.state.mode, 
	                            onChange: this._changeMode}, 
	                        React.createElement("option", {value: "edit"}, "Edit"), 
	                        React.createElement("option", {value: "preview"}, "Preview"), 
	                        this.props.developerMode &&
	                            React.createElement("option", {value: "json"}, 
	                                "Dev-only JSON"
	                            )
	                        
	                    )
	                )
	            ), 

	            (this.state.mode === "edit") &&
	                this._renderEditor(), 
	            

	            (this.state.mode === "preview") &&
	                this._renderPreviewMode(), 
	            

	            (this.props.developerMode && this.state.mode === "json") &&
	                React.createElement("div", null, 
	                    React.createElement(JsonEditor, {
	                        multiLine: true, 
	                        value: this.props.json, 
	                        onChange: this._handleJsonChange})
	                )
	            
	        );
	    },

	    _sections: function() {
	        return _.isArray(this.props.json) ?
	            this.props.json :
	            [this.props.json];
	    },

	    _renderEditor: function() {
	        return React.createElement("div", null, 
	            this._renderSections(), 
	            this._renderAddSection()
	        );
	    },

	    _renderSections: function() {
	        var apiOptions = _.extend(
	            {},
	            ApiOptions.defaults,
	            this.props.apiOptions
	        );

	        var sections = this._sections();

	        return React.createElement("div", {className: "perseus-editor-table"}, 
	            sections.map(function(section, i)  {
	                return [
	                    React.createElement("div", {className: "perseus-editor-row"}, 
	                        React.createElement("div", {className: "perseus-editor-left-cell"}, 
	                            React.createElement("div", {className: "pod-title"}, 
	                                "Section ", i+1, 
	                                React.createElement("div", {style: {
	                                    display: "inline-block",
	                                    float: "right"
	                                }}, 
	                                    (i + 1 < sections.length) &&
	                                        React.createElement(SectionControlButton, {
	                                            icon: "icon-circle-arrow-down", 
	                                            onClick: function()  {
	                                                this._handleMoveSectionLater(i);
	                                            }.bind(this)}), 
	                                    
	                                    (i > 0) &&
	                                        React.createElement(SectionControlButton, {
	                                            icon: "icon-circle-arrow-up", 
	                                            onClick: function()  {
	                                                this._handleMoveSectionEarlier(i);
	                                            }.bind(this)}), 
	                                    
	                                    React.createElement(SectionControlButton, {
	                                        icon: "icon-trash", 
	                                        onClick: function()  {
	                                            var msg = "Are you sure you " +
	                                                "want to remove section " +
	                                                (i + 1) + "?";
	                                            if (confirm(msg)) {
	                                                this._handleRemoveSection(i);
	                                            }
	                                        }.bind(this)}), 
	                                    React.createElement(SectionControlButton, {
	                                        icon: "icon-plus", 
	                                        onClick: function()  {
	                                            this._handleAddSectionAfter(i);
	                                        }.bind(this)})
	                                )
	                            ), 
	                            React.createElement(Editor, React.__spread({}, 
	                                section, 
	                                {ref: "editor" + i, 
	                                placeholder: "Type your section text here...", 
	                                imageUploader: this.props.imageUploader, 
	                                onChange: 
	                                    _.partial(this._handleEditorChange, i), 
	                                
	                                apiOptions: apiOptions, 
	                                enabledFeatures: this.props.enabledFeatures}))
	                        ), 

	                        React.createElement("div", {className: "perseus-editor-right-cell"}, 
	                            React.createElement(Renderer, React.__spread({}, 
	                                section, 
	                                {ref: "renderer" + i, 
	                                apiOptions: apiOptions, 
	                                enabledFeatures: this.props.enabledFeatures}))
	                        )
	                    )
	                ];
	            }.bind(this))
	        );
	    },

	    _renderAddSection: function() {
	        return React.createElement("div", {className: "perseus-editor-row"}, 
	            React.createElement("div", {className: "perseus-editor-left-cell"}, 
	                React.createElement("a", {href: "#", className: "simple-button orange", 
	                        onClick: function()  {
	                            this._handleAddSectionAfter(
	                                this._sections().length - 1
	                            );
	                        }.bind(this)}, 
	                    React.createElement("span", {className: "icon-plus"}), " Add a section"
	                )
	            ), 
	            React.createElement("div", {className: "perseus-editor-right-cell"})
	        );
	    },

	    _renderPreviewMode: function() {
	        return React.createElement(ArticleRenderer, {
	            json: this.props.json, 
	            apiOptions: this.props.apiOptions, 
	            enabledFeatures: this.props.enabledFeatures});
	    },

	    _changeMode: function(e) {
	        var newMode = e.target.value;
	        this.props.onChange({
	            json: this.serialize()
	        }, function()  {
	            this.setState({mode: newMode});
	        }.bind(this));
	    },

	    _handleJsonChange: function(newJson) {
	        this.props.onChange({json: newJson});
	    },

	    _handleEditorChange: function(i, newProps) {
	        var sections = _.clone(this._sections());
	        sections[i] = _.extend({}, sections[i], newProps);
	        this.props.onChange({json: sections});
	    },

	    _handleMoveSectionEarlier: function(i) {
	        if (i === 0) {
	            return;
	        }
	        var sections = _.clone(this._sections());
	        var section = sections[i];
	        sections.splice(i, 1);
	        sections.splice(i - 1, 0, section);
	        this.props.onChange({
	            json: sections
	        });
	    },

	    _handleMoveSectionLater: function(i) {
	        var sections = _.clone(this._sections());
	        if (i + 1 === sections.length) {
	            return;
	        }
	        var section = sections[i];
	        sections.splice(i, 1);
	        sections.splice(i + 1, 0, section);
	        this.props.onChange({
	            json: sections
	        });
	    },

	    _handleAddSectionAfter: function(i) {
	        // We do a full serialization here because we
	        // might be copying widgets:
	        var sections = _.clone(this.serialize());
	        // Here we do magic to allow you to copy-paste
	        // things from the previous section into the new
	        // section while preserving widgets.
	        // To enable this, we preserve the widgets
	        // object for the new section, but wipe out
	        // the content.
	        var newSection = (i >= 0) ? {
	            widgets: sections[i].widgets
	        } : {};
	        sections.splice(i + 1, 0, newSection);
	        this.props.onChange({
	            json: sections
	        });
	    },

	    _handleRemoveSection: function(i) {
	        var sections = _.clone(this._sections());
	        sections.splice(i, 1);
	        this.props.onChange({
	            json: sections
	        });
	    },

	    serialize: function() {
	        if (this.state.mode === "edit") {
	            return this._sections().map(function(section, i)  {
	                return this.refs["editor" + i].serialize();
	            }.bind(this));
	        } else if (this.state.mode === "preview" ||
	                this.state.mode === "json") {
	            return this.props.json;
	        } else {
	            throw new Error("Could not serialize; mode " +
	                this.state.mode + " not found"
	            );
	        }
	    },
	});

	module.exports = ArticleEditor;


/***/ },
/* 6 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);

	var ApiOptions = __webpack_require__(12).Options;
	var Renderer = __webpack_require__(10);
	var Util = __webpack_require__(3);

	var ArticleRenderer = React.createClass({displayName: 'ArticleRenderer',

	    shouldComponentUpdate: function(nextProps, nextState) {
	        return nextProps !== this.props || nextState !== this.state;
	    },

	    render: function() {
	        var content = this._sections().map(function(section, i)  {
	            return "[[" + Util.snowman + " group " + i + "]]";
	        }).join("\n\n");
	        
	        var widgets = {};
	        _.each(this._sections(), function(section, i)  {
	            var widgetId = "group " + i;
	            widgets[widgetId] = {
	                type: "group",
	                graded: true,
	                version: {major: 0, minor: 0},
	                options: section
	            };
	        });

	        return React.createElement(Renderer, {
	            content: content, 
	            widgets: widgets, 
	            apiOptions: this.props.apiOptions, 
	            enabledFeatures: this.props.enabledFeatures});
	    },

	    _sections: function() {
	        return _.isArray(this.props.json) ?
	            this.props.json :
	            [this.props.json];
	    },
	});

	module.exports = ArticleRenderer;


/***/ },
/* 7 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);
	var _ = __webpack_require__(60);

	var ApiOptions = __webpack_require__(12).Options;
	var DragTarget = __webpack_require__(64);
	var EnabledFeatures = __webpack_require__(49);
	var PropCheckBox = __webpack_require__(58);
	var Util = __webpack_require__(3);
	var Widgets = __webpack_require__(14);

	var WIDGET_PROP_BLACKLIST = __webpack_require__(62);

	// like [[snowman input-number 1]]
	var widgetPlaceholder = "[[\u2603 {id}]]";
	var widgetRegExp = "(\\[\\[\u2603 {id}\\]\\])";
	var rWidgetSplit = new RegExp(widgetRegExp.replace('{id}', '[a-z-]+ [0-9]+'),
	                              'g');

	var shortcutRegexp = /^\[\[([a-z]+)$/; // like [[nu, [[int, etc

	var ENDS_WITH_A_PARAGRAPH = /(?:\n{2,}|^\n*)$/;
	var TRAILING_NEWLINES = /(\n*)$/;
	var LEADING_NEWLINES = /^(\n*)/;

	var makeEndWithAParagraphIfNecessary = function(content)  {
	    if (!ENDS_WITH_A_PARAGRAPH.test(content)) {
	        var newlines = TRAILING_NEWLINES.exec(content)[1];
	        return content + "\n\n".slice(0, 2 - newlines.length);
	    } else {
	        return content;
	    }
	};
	var makeStartWithAParagraphAlways = function(content)  {
	    var newlines = LEADING_NEWLINES.exec(content)[1];
	    return "\n\n".slice(0, 2 - newlines.length) + content;
	};

	var WidgetSelect = React.createClass({displayName: 'WidgetSelect',
	    shouldComponentUpdate: function() {
	        return false;
	    },

	    render: function() {
	        var widgets = Widgets.getPublicWidgets();
	        var orderedWidgetNames = _.sortBy(_.keys(widgets), function(name)  {
	            return widgets[name].displayName;
	        });

	        return React.createElement("select", {value: "", onChange: this.handleChange}, 
	            React.createElement("option", {value: ""}, "Add a widget", "\u2026"), 
	            React.createElement("option", {disabled: true}, "--"), 
	            _.map(orderedWidgetNames, function(name)  {
	                return React.createElement("option", {key: name, value: name}, 
	                    widgets[name].displayName
	                );
	            })
	        );
	    },

	    handleChange: function(e) {
	        var widgetType = e.target.value;
	        if (widgetType === "") {
	            // TODO(alpert): Not sure if change will trigger here
	            // but might as well be safe
	            return;
	        }
	        if (this.props.onChange) {
	            this.props.onChange(widgetType);
	        }
	    },
	});


	var WidgetEditor = React.createClass({displayName: 'WidgetEditor',
	    propTypes: {
	        type: React.PropTypes.string,
	        id: React.PropTypes.string,
	        graded: React.PropTypes.bool,
	        onChange: React.PropTypes.func,
	        apiOptions: ApiOptions.propTypes,
	    },

	    getDefaultProps: function() {
	        return {
	            graded: true,
	            options: {}
	        };
	    },

	    getInitialState: function() {
	        return {
	            showWidget: true
	        };
	    },

	    render: function() {
	        // We can't call our widget's `serialize` here, because on
	        // first render that ref hasn't mounted yet.
	        // This means that on first render we'll send in
	        // `options: {}` to `upgradeWidgetInfoToLatestVersion`, but
	        // `upgradeWidgetInfoToLatestVersion` accounts for that
	        // before sending {} to any of our prop upgrade functions.
	        var upgradedWidgetInfo = Widgets.upgradeWidgetInfoToLatestVersion(
	            this.props
	        );
	        var type = upgradedWidgetInfo.type;

	        var Ed = Widgets.getEditor(type);

	        var isUngradedEnabled = (type === "transformer");
	        var direction = this.state.showWidget ? "down" : "right";

	        var gradedPropBox = React.createElement(PropCheckBox, {label: "Graded:", 
	                                graded: upgradedWidgetInfo.graded, 
	                                onChange: this.props.onChange});

	        return React.createElement("div", {className: "perseus-widget-editor"}, 
	            React.createElement("div", {className: "perseus-widget-editor-title " +
	                    (this.state.showWidget ? "open" : "closed")}, 
	                React.createElement("a", {href: "#", onClick: this.toggleWidget}, 
	                    this.props.id, 
	                    React.createElement("i", {className: "icon-chevron-" + direction})
	                ), 
	                React.createElement("a", {href: "#", className: 
	                            "remove-widget " +
	                            "simple-button simple-button--small orange", 
	                        
	                        onClick: function(e)  {
	                            e.preventDefault();
	                            this.props.onRemove();
	                        }.bind(this)}, 
	                    React.createElement("span", {className: "icon-trash"})
	                )
	            ), 
	            React.createElement("div", {className: "perseus-widget-editor-content " +
	                    (this.state.showWidget ? "enter" : "leave")}, 
	                isUngradedEnabled && gradedPropBox, 
	                React.createElement(Ed, React.__spread({
	                    ref: "widget", 
	                    onChange: this._handleWidgetChange, 
	                    apiOptions: this.props.apiOptions}, 
	                    upgradedWidgetInfo.options))
	            )
	        );
	    },

	    toggleWidget: function(e) {
	        e.preventDefault();
	        this.setState({showWidget: !this.state.showWidget});
	    },

	    _handleWidgetChange: function(newProps, cb, silent) {
	        // TODO(jack): It is unfortunate to call serialize here, but is
	        // important so that the widgetInfo we pass to our upgrade functions is
	        // always complete. If we just sent this.props in, we could run into
	        // situations where we would send things like { answerType: "decimal" }
	        // to our upgrade functions, without the rest of the props representing
	        // the widget.
	        var currentWidgetInfo = _.extend(
	            _.omit(this.props, WIDGET_PROP_BLACKLIST),
	            { options: this.refs.widget.serialize() }
	        );
	        var newWidgetInfo = Widgets.upgradeWidgetInfoToLatestVersion(
	            currentWidgetInfo
	        );
	        newWidgetInfo.options = _.extend(newWidgetInfo.options, newProps);
	        this.props.onChange(newWidgetInfo, cb, silent);
	    },

	    getSaveWarnings: function() {
	        var issuesFunc = this.refs.widget.getSaveWarnings;
	        return issuesFunc ? issuesFunc() : [];
	    },

	    serialize: function() {
	        return {
	            type: this.props.type,
	            graded: this.props.graded,
	            options: this.refs.widget.serialize(),
	            version: Widgets.getVersion(this.props.type)
	        };
	    }
	});

	// This is more general than the actual markdown image parsing regex,
	// which is fine for correctness since it should only mean we could
	// store extra image dimensions, unless the question is insanely
	// formatted.
	// A simplified regex here should hopefully be easier to keep in
	// sync if the markdown parsing changes, though if it becomes
	// easy to hook into the actual markdown regex without copy-pasting
	// it, we should do that.
	var IMAGE_REGEX = /!\[[^\]]*\]\(([^\s\)]+)[^\)]*\)/g;

	/**
	 * Find all the matches to a /g regex.
	 *
	 * Returns an array of the regex matches. Infinite loops if `regex` does not
	 * have a /g modifier.
	 *
	 * Note: Returns an array of the capture objects, whereas String::match
	 * ignores captures. If you don't need captures, use String::match
	 */
	var allMatches = function(regex, str) {
	    var result = [];
	    while (true) {
	        var match = regex.exec(str);
	        if (!match) {
	            break;
	        }
	        result.push(match);
	    }
	    return result;
	};

	/**
	 * Return an array of URLs of all the images in the given renderer
	 * markdown.
	 */
	var imageUrlsFromContent = function(content) {
	    return _.map(
	        allMatches(IMAGE_REGEX, content),
	        function(capture)  {return capture[1];}
	    );
	};

	var Editor = React.createClass({displayName: 'Editor',
	    propTypes: {
	        imageUploader: React.PropTypes.func,
	        apiOptions: ApiOptions.propTypes,
	    },

	    getDefaultProps: function() {
	        return {
	            content: "",
	            placeholder: "",
	            widgets: {},
	            images: {},
	            widgetEnabled: true,
	            immutableWidgets: false,
	            apiOptions: ApiOptions.defaults,
	        };
	    },

	    getWidgetEditor: function(id, type) {
	        if (!Widgets.getEditor(type)) {
	            return;
	        }
	        return React.createElement(WidgetEditor, React.__spread({
	            ref: id, 
	            id: id, 
	            type: type, 
	            onChange: this._handleWidgetEditorChange.bind(this, id), 
	            onRemove: this._handleWidgetEditorRemove.bind(this, id), 
	            apiOptions: this.props.apiOptions}, 
	            this.props.widgets[id]));
	    },

	    _handleWidgetEditorChange: function(id, newProps, cb, silent) {
	        var widgets = _.clone(this.props.widgets);
	        widgets[id] = _.extend({}, widgets[id], newProps);
	        this.props.onChange({widgets: widgets}, cb, silent);
	    },

	    _handleWidgetEditorRemove: function(id) {
	        var re = new RegExp(widgetRegExp.replace('{id}', id), 'gm');
	        var textarea = this.refs.textarea.getDOMNode();

	        this.props.onChange({content: textarea.value.replace(re, '')});
	    },

	    /**
	     * Calculate the size of all the images in props.content, and send
	     * those sizes to this.props.images using props.onChange.
	     */
	    _sizeImages: function(props) {
	        var imageUrls = imageUrlsFromContent(props.content);

	        // Discard any images in our dimension table that no
	        // longer exist in content.
	        var images = _.pick(props.images, imageUrls);

	        // Only calculate sizes for images that were not present previously.
	        // Most content edits shouldn't have new images.
	        // This could get weird in the case of multiple images with the same
	        // URL, if you've changed the backing image size, but given graphie
	        // hashes it's probably an edge case.
	        var newImageUrls = _.filter(imageUrls, function(url)  {return !images[url];});

	        // TODO(jack): Q promises would make this nicer and only
	        // fire once.
	        _.each(newImageUrls, function(url)  {
	            Util.getImageSize(url, function(width, height)  {
	                // We keep modifying the same image object rather than a new
	                // copy from this.props because all changes here are additive.
	                // Maintaining old changes isn't strictly necessary if
	                // props.onChange calls are not batched, but would be if they
	                // were, so this is nice from that anti-race-condition
	                // perspective as well.
	                images[url] = {
	                    width: width,
	                    height: height
	                };
	                props.onChange({
	                        images: _.clone(images)
	                    },
	                    null, // callback
	                    true // silent
	                );
	            });
	        });
	    },

	    render: function() {
	        var pieces;
	        var widgets;
	        var underlayPieces;
	        var widgetsDropDown;
	        var templatesDropDown;
	        var widgetsAndTemplates;

	        if (this.props.widgetEnabled) {
	            pieces = Util.split(this.props.content, rWidgetSplit);
	            widgets = {};
	            underlayPieces = [];

	            for (var i = 0; i < pieces.length; i++) {
	                var type = i % 2;
	                if (type === 0) {
	                    // Normal text
	                    underlayPieces.push(pieces[i]);
	                } else {
	                    // Widget reference
	                    var match = Util.rWidgetParts.exec(pieces[i]);
	                    var id = match[1];
	                    var type = match[2];

	                    var selected = false;
	                    // TODO(alpert):
	                    // var selected = focused && selStart === selEnd &&
	                    //         offset <= selStart &&
	                    //         selStart < offset + text.length;
	                    // if (selected) {
	                    //     selectedWidget = id;
	                    // }

	                    var duplicate = id in widgets;

	                    widgets[id] = this.getWidgetEditor(id, type);
	                    var classes = (duplicate || !widgets[id] ? "error " : "") +
	                            (selected ? "selected " : "");
	                    var key = duplicate ? i : id;
	                    underlayPieces.push(
	                            React.createElement("b", {className: classes, key: key}, pieces[i]));
	                }
	            }

	            // TODO(alpert): Move this to the content-change event handler
	            // _.each(_.keys(this.props.widgets), function(id) {
	            //     if (!(id in widgets)) {
	            //         // It's strange if these preloaded options stick around
	            //         // since it's inconsistent with how things work if you
	            //         // don't have the serialize/deserialize step in the
	            //         // middle
	            //         // TODO(alpert): Save options in a consistent manner so
	            //         // that you can undo the deletion of a widget
	            //         delete this.props.widgets[id];
	            //     }
	            // }, this);

	            this.widgetIds = _.keys(widgets);
	            widgetsDropDown = React.createElement(WidgetSelect, {
	                    ref: "widgetSelect", 
	                    onChange: this._addWidget});

	            templatesDropDown = React.createElement("select", {onChange: this.addTemplate}, 
	                React.createElement("option", {value: ""}, "Insert template", "\u2026"), 
	                React.createElement("option", {disabled: true}, "--"), 
	                React.createElement("option", {value: "table"}, "Table"), 
	                React.createElement("option", {value: "titledTable"}, "Titled table"), 
	                React.createElement("option", {value: "alignment"}, "Aligned equations"), 
	                React.createElement("option", {value: "piecewise"}, "Piecewise function")
	            );

	            if (!this.props.immutableWidgets) {
	                widgetsAndTemplates = React.createElement("div", {className: "perseus-editor-widgets"}, 
	                    React.createElement("div", {className: "perseus-editor-widgets-selectors"}, 
	                        widgetsDropDown, 
	                        templatesDropDown
	                    ), 
	                    widgets
	                );
	            }
	        } else {
	            underlayPieces = [this.props.content];
	        }

	        // Without this, the underlay isn't the proper size when the text ends
	        // with a newline.
	        underlayPieces.push(React.createElement("br", {key: "end"}));

	        var completeTextarea = [
	                React.createElement("div", {className: "perseus-textarea-underlay", 
	                     ref: "underlay", 
	                     key: "underlay"}, 
	                    underlayPieces
	                ),
	                React.createElement("textarea", {ref: "textarea", 
	                          key: "textarea", 
	                          onChange: this.handleChange, 
	                          onKeyDown: this._handleKeyDown, 
	                          placeholder: this.props.placeholder, 
	                          value: this.props.content})
	            ];
	        var textareaWrapper;
	        if (this.props.imageUploader) {
	            textareaWrapper = React.createElement(DragTarget, {
	                    onDrop: this.handleDrop, 
	                    className: "perseus-textarea-pair"}, 
	                completeTextarea
	            );
	        } else {
	            textareaWrapper = React.createElement("div", {className: "perseus-textarea-pair"}, 
	                completeTextarea
	            );
	        }

	        return React.createElement("div", {className: "perseus-single-editor " +
	                (this.props.className || "")}, 
	            textareaWrapper, 
	            widgetsAndTemplates
	        );
	    },

	    componentDidMount: function() {
	        // This can't be in componentWillMount because that's happening during
	        // the middle of our parent's render, so we can't call
	        // this.props.onChange during that, since it calls our parent's
	        // setState
	        this._sizeImages(this.props);
	    },

	    componentDidUpdate: function(prevProps) {
	        // TODO(alpert): Maybe fix React so this isn't necessary
	        var textarea = this.refs.textarea.getDOMNode();
	        textarea.value = this.props.content;

	        // This can't be in componentWillReceiveProps because that's happening
	        // during the middle of our parent's render.
	        if (this.props.content !== prevProps.content) {
	            this._sizeImages(this.props);
	        }
	    },

	    handleDrop: function(e) {
	        var content = this.props.content;
	        var dataTransfer = e.nativeEvent.dataTransfer;

	        // files will hold something if the drag was from the desktop or a file
	        // located on the user's computer.
	        var files = dataTransfer.files;

	        // ... but we only get a url if the drag originated in another window
	        if (files.length === 0) {
	            var imageUrl = dataTransfer.getData("URL");

	            if (imageUrl) {
	                // TODO(joel) - relocate when the image upload dialog lands
	                var newContent = content + "\n\n![](" + imageUrl + ")";
	                this.props.onChange({ content: newContent });
	            }

	            return;
	        }

	        /* For each file we make sure it's an image, then create a sentinel -
	         * snowman + identifier to insert into the current text. The sentinel
	         * only lives there temporarily until we get a response back from the
	         * server that the image is now hosted on AWS, at which time we replace
	         * the temporary sentinel with the permanent url for the image.
	         *
	         * There is an abuse of tap in the middle of the pipeline to make sure
	         * everything is sequenced in the correct order. We want to modify the
	         * content (given any number of images) at the same time, i.e. only
	         * once, so we do that step with the tap. After the content has been
	         * changed we send off the request for each image.
	         *
	         * Note that the snowman doesn't do anything special in this case -
	         * it's effectively just part of a broken link. Perseus could be
	         * extended to recognize this sentinel and highlight it like for
	         * widgets.
	         */
	        _(files)
	            .chain()
	            .map(function(file) {
	                if (!file.type.match('image.*')) {
	                    return null;
	                }

	                var sentinel = "\u2603 " + _.uniqueId("image_");
	                // TODO(joel) - figure out how to temporarily include the image
	                // before the server returns.
	                content += "\n\n![](" + sentinel + ")";

	                return { file: file, sentinel: sentinel };
	            })
	            .reject(_.isNull)
	            .tap(function()  { this.props.onChange({ content: content }); }.bind(this))
	            .each(function(fileAndSentinel)  {
	                this.props.imageUploader(fileAndSentinel.file, function(url)  {
	                    this.props.onChange({
	                        content: this.props.content.replace(
	                            fileAndSentinel.sentinel, url)
	                    });
	                }.bind(this));
	            }.bind(this));
	    },

	    handleChange: function() {
	        var textarea = this.refs.textarea.getDOMNode();
	        this.props.onChange({content: textarea.value});
	    },

	    _handleKeyDown: function(e) {
	        if (e.key === "Tab") {
	            var textarea = this.refs.textarea.getDOMNode();

	            var word = Util.textarea.getWordBeforeCursor(textarea);
	            var matches = word.string.toLowerCase().match(shortcutRegexp);

	            if (matches != null) {
	                var text = matches[1];
	                var widgets = Widgets.getAllWidgetTypes();
	                var matchingWidgets = _.filter(widgets, function(name)  {
	                    return name.substring(0, text.length) === text;
	                });

	                if (matchingWidgets.length === 1) {
	                    var widgetType = matchingWidgets[0];

	                    this._addWidgetToContent(
	                        this.props.content,
	                        [word.pos.start, word.pos.end + 1],
	                        widgetType
	                    );
	                }

	                e.preventDefault();
	            }
	        }
	    },

	    _addWidgetToContent:function(oldContent, cursorRange, widgetType) {
	        var textarea = this.refs.textarea.getDOMNode();

	        // Note: we have to use _.map here instead of Array::map
	        // because the results of a .match might be null if no
	        // widgets were found.
	        var allWidgetIds = _.map(oldContent.match(rWidgetSplit), function(syntax)  {
	            var match = Util.rWidgetParts.exec(syntax);
	            var type = match[2];
	            var num = +match[3];
	            return [type, num];
	        });

	        var widgetNum = _.reduce(allWidgetIds, function(currentNum, otherId)  {
	            var $__0=   otherId,otherType=$__0[0],otherNum=$__0[1];
	            if (otherType === widgetType) {
	                return Math.max(otherNum + 1, currentNum);
	            } else {
	                return currentNum;
	            }
	        }, 1);

	        var id = widgetType + " " + widgetNum;
	        var widgetContent = widgetPlaceholder.replace("{id}", id);

	        // Add newlines before block-display widgets like graphs
	        var Widget = Widgets.getWidget(widgetType, EnabledFeatures.defaults);
	        var isBlock = Widget.displayMode === "block";
	        var prelude = oldContent.slice(0, cursorRange[0]);
	        var postlude = oldContent.slice(cursorRange[1]);

	        var newPrelude = isBlock ?
	            makeEndWithAParagraphIfNecessary(prelude) :
	            prelude;
	        var newPostlude = isBlock ?
	            makeStartWithAParagraphAlways(postlude) :
	            postlude;

	        var newContent = newPrelude + widgetContent + newPostlude;

	        this.props.onChange({
	            content: newContent
	        }, function() {
	            Util.textarea.moveCursor(
	                textarea,
	                // We want to put the cursor after the widget
	                // and after any added newlines
	                newContent.length - postlude.length
	            );
	        });
	    },

	    _addWidget: function(widgetType) {
	        var textarea = this.refs.textarea.getDOMNode();
	        this._addWidgetToContent(
	            this.props.content,
	            [textarea.selectionStart, textarea.selectionEnd],
	            widgetType
	        );
	        textarea.focus();
	    },

	    addTemplate: function(e) {
	        var templateType = e.target.value;
	        if (templateType === "") {
	            return;
	        }
	        e.target.value = "";

	        var oldContent = this.props.content;

	        // Force templates to have a blank line before them,
	        // as they are usually used as block elements
	        // (especially important for tables)
	        oldContent = oldContent.replace(/\n*$/, "\n\n");

	        var template;
	        if (templateType === "table") {
	            template = "header 1 | header 2 | header 3\n" +
	                       "- | - | -\n" +
	                       "data 1 | data 2 | data 3\n" +
	                       "data 4 | data 5 | data 6\n" +
	                       "data 7 | data 8 | data 9";
	        } else if (templateType === "titledTable") {
	            template = "|| **Table title** ||\n" +
	                       "header 1 | header 2 | header 3\n" +
	                       "- | - | -\n" +
	                       "data 1 | data 2 | data 3\n" +
	                       "data 4 | data 5 | data 6\n" +
	                       "data 7 | data 8 | data 9";
	        } else if (templateType === "alignment") {
	            template = "$\\begin{align} x+5 &= 30 \\\\\n" +
	                       "x+5-5 &= 30-5 \\\\\n" +
	                       "x &= 25 \\end{align}$";
	        } else if (templateType === "piecewise") {
	            template = "$f(x) = \\begin{cases}\n" +
	                       "7 & \\text{if $x=1$} \\\\\n" +
	                       "f(x-1)+5 & \\text{if $x > 1$}\n" +
	                       "\\end{cases}$";
	        } else {
	            throw new Error("Invalid template type: " + templateType);
	        }

	        var newContent = oldContent + template;

	        this.props.onChange({content: newContent}, this.focusAndMoveToEnd);
	    },

	    getSaveWarnings: function() {
	        var widgetIds = _.intersection(this.widgetIds, _.keys(this.refs));
	        return _(widgetIds)
	            .chain()
	            .map(function(id)  {
	                var issuesFunc = this.refs[id].getSaveWarnings;
	                return issuesFunc ? issuesFunc() : [];
	            }.bind(this))
	            .flatten(true)
	            .value();
	    },

	    serialize: function(options) {
	        // need to serialize the widgets since the state might not be
	        // completely represented in props. ahem //transformer// (and
	        // interactive-graph and plotter).
	        var widgets = {};
	        var widgetIds = _.intersection(this.widgetIds, _.keys(this.refs));
	        _.each(widgetIds, function(id)  {
	            widgets[id] = this.refs[id].serialize();
	        }.bind(this));

	        // Preserve the data associated with deleted widgets in their last
	        // modified form. This is only intended to be useful in the context of
	        // immediate cut and paste operations if Editor.serialize() is called
	        // in between the two (which ideally should not be happening).
	        // TODO(alex): Remove this once all widget.serialize() methods
	        //             have been fixed to only return props,
	        //             and the above no longer applies.
	        if (options && options.keepDeletedWidgets) {
	            _.chain(this.props.widgets)
	                .keys()
	                .reject(function(id)  {return _.contains(widgetIds, id);})
	                .each(function(id)  { widgets[id] = this.props.widgets[id]; }.bind(this));
	        }

	        return {
	            content: this.props.content,
	            images: this.props.images,
	            widgets: widgets,
	        };
	    },

	    focus: function() {
	        this.refs.textarea.getDOMNode().focus();
	    },

	    focusAndMoveToEnd: function() {
	        this.focus();
	        var textarea = this.refs.textarea.getDOMNode();
	        textarea.selectionStart = textarea.value.length;
	        textarea.selectionEnd = textarea.value.length;
	    }
	});

	module.exports = Editor;


/***/ },
/* 8 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);
	var _ = __webpack_require__(60);

	var CombinedHintsEditor = __webpack_require__(51);
	var EnabledFeatures = __webpack_require__(49);
	var ItemEditor = __webpack_require__(52);
	var ItemRenderer = __webpack_require__(9);
	var JsonEditor = __webpack_require__(50);
	var ApiOptions = __webpack_require__(12).Options;

	var EditorPage = React.createClass({displayName: 'EditorPage',
	    propTypes: {
	        // A function which takes a file object (guaranteed to be an image) and
	        // a callback, then calls the callback with the url where the image
	        // will be hosted. Image drag and drop is disabled when imageUploader
	        // is null.
	        imageUploader: React.PropTypes.func,
	        enabledFeatures: EnabledFeatures.propTypes,
	        // We don't specify a more specific type here because it's valid
	        // for a client of Perseus to specify a subset of the API options,
	        // in which case we default the rest in `this._apiOptions()`
	        apiOptions: React.PropTypes.object,
	    },

	    getDefaultProps: function() {
	        return {
	            developerMode: false,
	            jsonMode: false,
	            enabledFeatures: {
	                toolTipFormats: true,
	                useMathQuill: true
	            },
	            apiOptions: {} // deep defaults on updateRenderer
	        };
	    },

	    getInitialState: function() {
	        return {
	            json: {
	                question: this.props.question,
	                answer: this.props.answerArea,
	                hints: this.props.hints
	            },
	            gradeMessage: "",
	            wasAnswered: false
	        };
	    },

	    render: function() {

	        return React.createElement("div", {id: "perseus", className: "framework-perseus"}, 
	            this.props.developerMode &&
	                React.createElement("div", null, 
	                    React.createElement("label", null, 
	                        ' ', "Developer JSON Mode:", ' ', 
	                        React.createElement("input", {type: "checkbox", 
	                            checked: this.props.jsonMode, 
	                            onChange: this.toggleJsonMode})
	                    )
	                ), 
	            

	            this.props.developerMode && this.props.jsonMode &&
	                React.createElement("div", null, 
	                    React.createElement(JsonEditor, {
	                        multiLine: true, 
	                        value: this.state.json, 
	                        onChange: this.changeJSON})
	                ), 
	            

	            (!this.props.developerMode || !this.props.jsonMode) &&
	                React.createElement(ItemEditor, {
	                    ref: "itemEditor", 
	                    rendererOnly: this.props.jsonMode, 
	                    question: this.props.question, 
	                    answerArea: this.props.answerArea, 
	                    imageUploader: this.props.imageUploader, 
	                    onChange: this.handleChange, 
	                    wasAnswered: this.state.wasAnswered, 
	                    gradeMessage: this.state.gradeMessage, 
	                    onCheckAnswer: this.handleCheckAnswer, 
	                    apiOptions: this._apiOptions()}), 
	            

	            (!this.props.developerMode || !this.props.jsonMode) &&
	                React.createElement(CombinedHintsEditor, {
	                    ref: "hintsEditor", 
	                    hints: this.props.hints, 
	                    imageUploader: this.props.imageUploader, 
	                    onChange: this.handleChange})
	            
	        );

	    },

	    handleCheckAnswer: function() {
	        var result = this.scorePreview();
	        this.setState({
	            gradeMessage: result.message,
	            wasAnswered: result.correct
	        });
	    },

	    toggleJsonMode: function() {
	        this.setState({
	            json: this.serialize({keepDeletedWidgets: true})
	        }, function() {
	            this.props.onChange({
	                jsonMode: !this.props.jsonMode
	            });
	        });
	    },

	    componentDidMount: function() {
	        this.rendererMountNode = document.createElement("div");
	        this.updateRenderer();
	    },

	    componentDidUpdate: function() {
	        this.updateRenderer();
	    },

	    updateRenderer: function(cb) {
	        // Some widgets (namely the image widget) like to call onChange before
	        // anything has actually been mounted, which causes problems here. We
	        // just ensure don't update until we've mounted
	        if (this.rendererMountNode == null || this.props.jsonMode) {
	            return;
	        }
	        var rendererConfig = _({
	            item: this.serialize(),
	            enabledFeatures: {
	                toolTipFormats: true
	            },
	            apiOptions: this._apiOptions(),
	            initialHintsVisible: 0  /* none; to be displayed below */
	        }).extend(
	            _(this.props).pick("workAreaSelector",
	                               "solutionAreaSelector",
	                               "hintsAreaSelector",
	                               "problemNum",
	                               "enabledFeatures")
	        );

	        this.renderer = React.render(
	            React.createElement(ItemRenderer, React.__spread({},  rendererConfig)),
	            this.rendererMountNode,
	            cb);
	    },

	    _apiOptions: function() {
	        return _.extend(
	            {},
	            ApiOptions.defaults,
	            this.props.apiOptions
	        );
	    },

	    handleChange: function(toChange, cb, silent) {
	        var newProps = _(this.props).pick("question", "hints", "answerArea");
	        _(newProps).extend(toChange);
	        this.props.onChange(newProps, cb, silent);
	    },

	    changeJSON: function(newJson) {
	        this.setState({
	            json: newJson,
	        });
	        this.props.onChange(newJson);
	    },

	    scorePreview: function() {
	        if (this.renderer) {
	            return this.renderer.scoreInput();
	        } else {
	            return null;
	        }
	    },

	    getSaveWarnings: function() {
	        var issues1 = this.refs.itemEditor.getSaveWarnings();
	        var issues2 = this.refs.hintsEditor.getSaveWarnings();
	        return issues1.concat(issues2);
	    },

	    serialize: function(options) {
	        if (this.props.jsonMode) {
	            return this.state.json;
	        } else {
	            return _.extend(this.refs.itemEditor.serialize(options), {
	                hints: this.refs.hintsEditor.serialize(options)
	            });
	        }
	    }

	});

	module.exports = EditorPage;


/***/ },
/* 9 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);
	var _ = __webpack_require__(60);

	var AnswerAreaRenderer = __webpack_require__(4);
	var HintRenderer = __webpack_require__(53);
	var Renderer = __webpack_require__(10);
	var Util = __webpack_require__(3);
	var ApiOptions = __webpack_require__(12).Options;
	var EnabledFeatures = __webpack_require__(49);

	var $__0=  __webpack_require__(63),mapObject=$__0.mapObject;

	var HintsRenderer = React.createClass({displayName: 'HintsRenderer',
	    render: function() {
	        var hintsVisible = this._hintsVisible();
	        var hints = this.props.hints
	            .slice(0, hintsVisible)
	            .map(function(hint, i) {
	                var shouldBold = i === this.props.hints.length - 1 &&
	                                 !(/\*\*/).test(hint.content);
	                return React.createElement(HintRenderer, {
	                            bold: shouldBold, 
	                            hint: hint, 
	                            ref: "hintRenderer" + i, 
	                            key: "hintRenderer" + i, 
	                            enabledFeatures: this.props.enabledFeatures, 
	                            apiOptions: this.props.apiOptions});
	            }, this);

	        return React.createElement("div", null, hints);
	    },

	    _hintsVisible: function() {
	        if (this.props.hintsVisible == null ||
	                this.props.hintsVisible === -1) {
	            return this.props.hints.length;
	        } else {
	            return this.props.hintsVisible;
	        }
	    },

	    getSerializedState: function() {
	        return _.times(this._hintsVisible(), function(i)  {
	            return this.refs["hintRenderer" + i].getSerializedState();
	        }.bind(this));
	    },

	    restoreSerializedState: function(state, callback) {
	        // We need to wait until all the renderers are finished restoring their
	        // state before we fire our callback.
	        var numCallbacks = 1;
	        var fireCallback = function()  {
	            --numCallbacks;
	            if (callback && numCallbacks === 0) {
	                callback();
	            }
	        };

	        _.each(state, function(hintState, i)  {
	            var hintRenderer = this.refs["hintRenderer" + i];
	            // This is not ideal in that it doesn't restore state
	            // if the hint isn't visible, but we can't exactly restore
	            // the state to an unmounted renderer, so...
	            // If you want to restore state to hints, make sure to
	            // have the appropriate number of hints visible already.
	            if (hintRenderer) {
	                ++numCallbacks;
	                hintRenderer.restoreSerializedState(hintState, fireCallback);
	            }
	        }.bind(this));

	        // This makes sure that the callback is fired if there aren't any
	        // mounted renderers.
	        fireCallback();
	    },
	});

	var highlightedWidgets = function(widgetList) 
	    {return _.filter(widgetList, Util.widgetShouldHighlight);};

	var ItemRenderer = React.createClass({displayName: 'ItemRenderer',
	    getDefaultProps: function() {
	        return {
	            initialHintsVisible: 0,

	            // TODO(joel) - handle this differently. Pass around nodes or
	            // something half reasonable.
	            workAreaSelector: "#workarea",
	            solutionAreaSelector: "#solutionarea",
	            hintsAreaSelector: "#hintsarea",

	            enabledFeatures: {},  // a deep default is done in `this.update()`
	            apiOptions: {}  // likewise ^
	        };
	    },

	    getInitialState: function() {
	        return {
	            hintsVisible: this.props.initialHintsVisible,
	            questionCompleted: false,
	            questionHighlightedWidgets: [],
	            answerHighlightedWidgets: []
	        };
	    },

	    componentWillReceiveProps: function(nextProps) {
	        this.setState({
	            questionHighlightedWidgets: [],
	            answerHighlightedWidgets: []
	        });
	    },

	    componentDidMount: function() {
	        if (Khan.scratchpad) {
	            Khan.scratchpad.enable();
	        }
	        this._currentFocus = null;
	        this.update();
	    },

	    componentDidUpdate: function() {
	        this.update();
	    },

	    update: function() {
	        var enabledFeatures = _.extend(
	            {},
	            EnabledFeatures.defaults,
	            this.props.enabledFeatures
	        );

	        var apiOptions = _.extend(
	            {},
	            ApiOptions.defaults,
	            this.props.apiOptions,
	            {
	                onFocusChange: this._handleFocusChange
	            }
	        );

	        // Since the item renderer works by rendering things into three divs
	        // that have completely different places in the DOM, we have to do this
	        // strangeness instead of relying on React's normal render() method.
	        // TODO(alpert): Figure out how to clean this up somehow
	        this.questionRenderer = React.render(
	                React.createElement(Renderer, React.__spread({
	                    problemNum: this.props.problemNum, 
	                    onInteractWithWidget: this.handleInteractWithWidget, 
	                    highlightedWidgets: this.state.questionHighlightedWidgets, 
	                    enabledFeatures: enabledFeatures, 
	                    apiOptions: apiOptions, 
	                    questionCompleted: this.state.questionCompleted, 
	                    savedState: this.props.savedState}, 
	                    this.props.item.question)
	                ),
	                document.querySelector(this.props.workAreaSelector));

	        this.answerAreaRenderer = React.render(
	                React.createElement(AnswerAreaRenderer, {
	                    type: this.props.item.answerArea.type, 
	                    options: this.props.item.answerArea.options, 
	                    calculator: this.props.item.answerArea.calculator || false, 
	                    problemNum: this.props.problemNum, 
	                    onInteractWithWidget: this.handleInteractWithAnswerWidget, 
	                    highlightedWidgets: this.state.answerHighlightedWidgets, 
	                    enabledFeatures: enabledFeatures, 
	                    apiOptions: apiOptions}
	                ),
	                document.querySelector(this.props.solutionAreaSelector));

	        this.hintsRenderer = React.render(
	                React.createElement(HintsRenderer, {
	                    hints: this.props.item.hints, 
	                    hintsVisible: this.state.hintsVisible, 
	                    enabledFeatures: enabledFeatures, 
	                    apiOptions: apiOptions}
	                ),
	                document.querySelector(this.props.hintsAreaSelector));
	    },

	    _handleFocusChange: function(newFocus, oldFocus) {
	        if (newFocus != null) {
	            this._setCurrentFocus(newFocus);
	        } else {
	            this._onRendererBlur(oldFocus);
	        }
	    },

	    // Sets the current focus path and element and
	    // send an onChangeFocus event back to our parent.
	    _setCurrentFocus: function(newFocus) {
	        // By the time this happens, newFocus cannot be a prefix of
	        // prevFocused, since we must have either been called from
	        // an onFocusChange within a renderer, which is only called when
	        // this is not a prefix, or between the question and answer areas,
	        // which can never prefix each other.
	        var prevFocus = this._currentFocus;
	        this._currentFocus = newFocus;
	        if (this.props.apiOptions.onFocusChange != null) {
	            this.props.apiOptions.onFocusChange(this._currentFocus, prevFocus);
	        }
	    },

	    _onRendererBlur: function(blurPath) {
	        var blurringFocusPath = this._currentFocus;

	        // Failsafe: abort if ID is different, because focus probably happened
	        // before blur
	        if (!_.isEqual(blurPath, blurringFocusPath)) {
	            return;
	        }

	        // Wait until after any new focus events fire this tick before
	        // declaring that nothing is focused.
	        // If a different widget was focused, we'll see an onBlur event
	        // now, but then an onFocus event on a different element before
	        // this callback is executed
	        _.defer(function()  {
	            if (_.isEqual(this._currentFocus, blurringFocusPath)) {
	                this._setCurrentFocus(null);
	            }
	        }.bind(this));
	    },

	    /**
	     * Accepts a question area widgetId, or an answer area widgetId of
	     * the form "answer-input-number 1", or the string "answer-area"
	     * for the whole answer area (if the answer area is a single widget).
	     */
	    _setWidgetProps: function(widgetId, newProps, callback) {
	        var maybeAnswerAreaWidget = widgetId.match(/^answer-(.*)$/);

	        if (maybeAnswerAreaWidget) {
	            var answerAreaWidgetId = maybeAnswerAreaWidget[1];
	            this.answerAreaRenderer._setWidgetProps(
	                answerAreaWidgetId,
	                newProps,
	                callback
	            );
	        } else {
	            this.questionRenderer._setWidgetProps(
	                widgetId,
	                newProps,
	                callback
	            );
	        }
	    },

	    _handleAPICall: function(functionName, path) {
	        // Get arguments to pass to function, including `path`
	        var functionArgs = _.rest(arguments);

	        // Decide on which caller should handle the API call
	        var caller;
	        var isAnswerArea = path[0].match(/^answer-(.*)$/);
	        if (isAnswerArea) {
	            caller = this.answerAreaRenderer;
	        } else {
	            caller = this.questionRenderer;
	        }

	        return caller[functionName].apply(caller, functionArgs);
	    },

	    setInputValue: function(path, newValue, focus) {
	        return this._handleAPICall('setInputValue', path, newValue, focus);
	    },

	    focusPath: function(path) {
	        // TODO(charlie): Find a better way to handle blurring between answer-
	        // and question-area.
	        if (path && path.length > 0) {
	            var focusAnswer = path[0].match(/^answer-(.*)$/);
	            if (focusAnswer) {
	                this.questionRenderer.blur();
	            } else {
	                this.answerAreaRenderer.blur();
	            }
	        }

	        return this._handleAPICall('focusPath', path);
	    },

	    blurPath: function(path) {
	        return this._handleAPICall('blurPath', path);
	    },

	    getDOMNodeForPath: function(path) {
	        return this._handleAPICall('getDOMNodeForPath', path);
	    },

	    getGrammarTypeForPath: function(path) {
	        return this._handleAPICall('getGrammarTypeForPath', path);
	    },

	    getInputPaths: function() {
	        var questionAreaInputPaths = this.questionRenderer.getInputPaths();
	        var answerAreaInputPaths = this.answerAreaRenderer.getInputPaths();
	        return questionAreaInputPaths.concat(answerAreaInputPaths);
	    },

	    handleInteractWithWidget: function(widgetId) {
	        var withRemoved = _.difference(this.state.questionHighlightedWidgets,
	                                       [widgetId]);
	        this.setState({
	            questionCompleted: false,
	            questionHighlightedWidgets: withRemoved
	        });
	    },

	    handleInteractWithAnswerWidget: function(widgetId) {
	        var withRemoved = _.difference(this.state.answerHighlightedWidgets,
	                                       [widgetId]);
	        this.setState({
	            answerHighlightedWidgets: withRemoved
	        });
	    },

	    render: function() {
	        return React.createElement("div", null);
	    },

	    focus: function() {
	        return this.questionRenderer.focus() ||
	                this.answerAreaRenderer.focus();
	    },

	    componentWillUnmount: function() {
	        React.unmountComponentAtNode(
	                document.querySelector(this.props.workAreaSelector));
	        React.unmountComponentAtNode(
	                document.querySelector(this.props.solutionAreaSelector));
	        React.unmountComponentAtNode(
	                document.querySelector(this.props.hintsAreaSelector));
	    },

	    showHint: function() {
	        if (this.state.hintsVisible < this.getNumHints()) {
	            this.setState({
	                hintsVisible: this.state.hintsVisible + 1
	            });
	        }
	    },

	    getNumHints: function() {
	        return this.props.item.hints.length;
	    },

	    /**
	     * Grades the item.
	     *
	     * Returns a KE-style score of {
	     *     empty: bool,
	     *     correct: bool,
	     *     message: string|null,
	     *     guess: Array
	     * }
	     */
	    scoreInput: function() {
	        var qGuessAndScore = this.questionRenderer.guessAndScore();
	        var aGuessAndScore = this.answerAreaRenderer.guessAndScore();

	        var qGuess = qGuessAndScore[0], qScore = qGuessAndScore[1];
	        var aGuess = aGuessAndScore[0], aScore = aGuessAndScore[1];

	        var emptyQuestionAreaWidgets = this.questionRenderer.emptyWidgets();
	        var emptyAnswerAreaWidgets = this.answerAreaRenderer.emptyWidgets();
	        this.setState({
	            questionHighlightedWidgets: emptyQuestionAreaWidgets,
	            answerHighlightedWidgets: emptyAnswerAreaWidgets
	        });

	        var guess, score;
	        if (qGuess.length === 0) {
	            // No widgets in question. For compatability with old guess format,
	            // leave it out here completely.
	            guess = aGuess;
	            score = aScore;
	        } else {
	            guess = [qGuess, aGuess];
	            score = Util.combineScores(qScore, aScore);
	        }

	        var keScore = Util.keScoreFromPerseusScore(score, guess);
	        this.setState({
	            questionCompleted: keScore.correct
	        });

	        return keScore;
	    },

	    /**
	     * Returns an array of all widget IDs in the order they occur in
	     * the question content.
	     *
	     * NOTE: This ignores the answer area.
	     */
	    getWidgetIds: function() {
	        return this.questionRenderer.getWidgetIds();
	    },

	    /**
	     * Returns an object mapping from widget ID to KE-style score.
	     * The keys of this object are the values of the array returned
	     * from `getWidgetIds`.
	     *
	     * NOTE: This ignores the answer area.
	     */
	    scoreWidgets: function() {
	        var qScore = this.questionRenderer.scoreWidgets();
	        var qGuess = this.questionRenderer.getUserInputForWidgets();
	        return mapObject(qScore, function(score, id)  {
	            return Util.keScoreFromPerseusScore(score, qGuess[id]);
	        });
	    },

	    /**
	     * Get a representation of the current state of the item.
	     *
	     * Note: this ignores the answer area.
	     */
	    getSerializedState: function() {
	        return {
	            question: this.questionRenderer.getSerializedState(),
	            hints: this.hintsRenderer.getSerializedState(),
	        };
	    },

	    restoreSerializedState: function(state, callback) {
	        // We need to wait for both the question renderer and the hints
	        // renderer to finish restoring their states.
	        var numCallbacks = 2;
	        var fireCallback = function()  {
	            --numCallbacks;
	            if (callback && numCallbacks === 0) {
	                callback();
	            }
	        };

	        this.questionRenderer.restoreSerializedState(
	            state.question, fireCallback);
	        this.hintsRenderer.restoreSerializedState(state.hints, fireCallback);
	    },
	});

	module.exports = ItemRenderer;


/***/ },
/* 10 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);
	var _ = __webpack_require__(60);

	var PerseusMarkdown = __webpack_require__(54);
	var QuestionParagraph = __webpack_require__(47);
	var SvgImage = __webpack_require__(57);
	var TeX = __webpack_require__(65);
	var WidgetContainer = __webpack_require__(48);
	var Widgets = __webpack_require__(14);

	var Util = __webpack_require__(3);
	var EnabledFeatures = __webpack_require__(49);
	var ApiOptions = __webpack_require__(12).Options;
	var ApiClassNames = __webpack_require__(12).ClassNames;

	var $__0=   __webpack_require__(63),mapObject=$__0.mapObject,mapObjectFromArray=$__0.mapObjectFromArray;

	var specialChars = {
	    // escaped: original
	    "\\a": "\u0007", // \a isn't valid javascript
	    "\\b": "\b",
	    "\\t": "\t",
	    "\\n": "\n",
	    "\\v": "\v",
	    "\\f": "\f",
	    "\\r": "\r",
	    "\\\\": "\\"
	};

	var rEscapedChars = /\\a|\\b|\\t|\\n|\\v|\\f|\\r|\\\\/g;
	var rContainsNonWhitespace = /\S/;

	if (typeof KA !== "undefined" && KA.language === "en-PT") {
	    // When using crowdin's jipt (Just in place translation), we need to keep a
	    // registry of crowdinId's to component so that we can update the
	    // component's state as the translator enters their translation.
	    window.PerseusTranslationComponents = [];

	    if (!KA.jipt_dom_insert_checks) {
	        KA.jipt_dom_insert_checks = [];
	    }

	    // We add a function that will get called whenever jipt says the dom needs
	    // to be updated
	    KA.jipt_dom_insert_checks.push(function(text, node, attribute) {
	        var index = $(node).data("perseus-component-index");
	        // We only update if we had added an index onto the node's data.
	        if (node && typeof index !== "undefined") {
	            var component = window.PerseusTranslationComponents[index];

	            if (!component) {
	                // The component has disappeared, so we tell jipt not to try
	                // and insert anything
	                return false;
	            }
	            // Jipt sends down the escaped translation, so we need to
	            // unescape \\t to \t among other characters here
	            text = text.replace(
	                rEscapedChars,
	                function(ch) {
	                    return specialChars[ch];
	                });

	            component.setState({
	                jiptContent: text
	            });

	            // Return false to tell jipt not to insert anything into the DOM
	            // itself, otherwise it will mess up what React expects there to be
	            return false;
	        }
	        // The string updated wasn't part of perseus, so we tell jipt to just
	        // insert the translation as-is.
	        return text;
	    });
	}

	var SHOULD_CLEAR_WIDGETS_PROP_LIST = [
	    "content",
	    "problemNum",
	    "widgets"
	];

	// Check if one focus path / id path is a prefix of another
	// The focus path null will never be a prefix of any non-null
	// path, since it represents no focus.
	// Otherwise, prefix is calculated by whether every array
	// element in the prefix is present in the same position in the
	// wholeArray path.
	var isIdPathPrefix = function(prefixArray, wholeArray) {
	    if (prefixArray === null || wholeArray === null) {
	        return prefixArray === wholeArray;
	    }
	    return _.every(prefixArray, function(elem, i)  {
	        return _.isEqual(elem, wholeArray[i]);
	    });
	};

	var Renderer = React.createClass({displayName: 'Renderer',
	    propTypes: {
	        highlightedWidgets: React.PropTypes.array,
	        enabledFeatures: EnabledFeatures.propTypes,
	        apiOptions: React.PropTypes.object,
	        questionCompleted: React.PropTypes.bool,
	        onInteractWithWidget: React.PropTypes.func,
	        interWidgets: React.PropTypes.func,
	        alwaysUpdate: React.PropTypes.bool,
	        reviewMode: React.PropTypes.bool,
	    },

	    componentWillReceiveProps: function(nextProps) {
	        if (!_.isEqual(_.pick(this.props, SHOULD_CLEAR_WIDGETS_PROP_LIST),
	                       _.pick(nextProps, SHOULD_CLEAR_WIDGETS_PROP_LIST))) {
	            this.setState(this._getInitialWidgetState(nextProps));
	        }
	    },

	    getDefaultProps: function() {
	        return {
	            content: "",
	            widgets: {},
	            images: {},
	            // TODO(aria): Remove this now that it is true everywhere
	            // (here and in perseus-i18n)
	            ignoreMissingWidgets: true,
	            highlightedWidgets: [],
	            enabledFeatures: EnabledFeatures.defaults,
	            apiOptions: {},  // we'll do a deep defaults in render()
	            // onRender may be called multiple times per render, for example
	            // if there are multiple images or TeX pieces within `content`.
	            // It is a good idea to debounce any functions passed here.
	            questionCompleted: false,
	            onRender: function() {},
	            onInteractWithWidget: function() {},
	            interWidgets: function()  {return null;},
	            alwaysUpdate: false,
	            reviewMode: false,
	        };
	    },

	    getInitialState: function() {
	        return _.extend(
	            {jiptContent: null},
	            this._getInitialWidgetState());
	    },

	    _getInitialWidgetState: function(props) {
	        props = props || this.props;
	        var allWidgetInfo = this._getAllWidgetsInfo(props);
	        return {
	            widgetInfo: allWidgetInfo,
	            widgetProps: this._getAllWidgetsStartProps(allWidgetInfo, props),
	        };
	    },

	    _getAllWidgetsInfo: function(props) {
	        props = props || this.props;
	        return mapObject(props.widgets, function(widgetInfo, widgetId)  {
	            if (!widgetInfo.type) {
	                var type = widgetId.split(" ")[0];
	                widgetInfo = _.extend({}, widgetInfo, {
	                    type: type
	                });
	            }
	            return Widgets.upgradeWidgetInfoToLatestVersion(widgetInfo);
	        });
	    },

	    _getAllWidgetsStartProps: function(allWidgetInfo, props) {
	        return mapObject(allWidgetInfo, function(editorProps)  {
	            return Widgets.getRendererPropsForWidgetInfo(
	                editorProps,
	                props.problemNum
	            );
	        });
	    },

	    shouldComponentUpdate: function(nextProps, nextState) {
	        if (this.props.alwaysUpdate) {
	            // TOTAL hacks so that interWidgets doesn't break
	            // when one widget updates without the other.
	            // See passage-refs inside radios, which was why
	            // this was introduced.
	            // I'm sorry!
	            // TODO(aria): cry
	            return true;
	        }
	        var stateChanged = !_.isEqual(this.state, nextState);
	        var propsChanged = !_.isEqual(this.props, nextProps);
	        return propsChanged || stateChanged;
	    },

	    _getDefaultWidgetInfo: function(widgetId) {
	        var widgetIdParts = Util.rTypeFromWidgetId.exec(widgetId);
	        if (widgetIdParts == null) {
	            return {};
	        }
	        return {
	            type: widgetIdParts[1],
	            graded: true,
	            options: {}
	        };
	    },

	    _getWidgetInfo: function(widgetId) {
	        return this.state.widgetInfo[widgetId] ||
	            this._getDefaultWidgetInfo(widgetId);
	    },

	    renderWidget: function(impliedType, id) {
	        var widgetInfo = this.state.widgetInfo[id];
	        if (widgetInfo || this.props.ignoreMissingWidgets) {

	            var type = (widgetInfo && widgetInfo.type) || impliedType;
	            var cls = Widgets.getWidget(type, this.props.enabledFeatures);
	            var shouldHighlight = _.contains(
	                this.props.highlightedWidgets,
	                id
	            );

	            // By this point we should have no duplicates, which are
	            // filtered out in this.render(), so we shouldn't have to
	            // worry about using this widget key and ref:
	            return React.createElement(WidgetContainer, {
	                    ref: "container:" + id, 
	                    key: "container:" + id, 
	                    type: cls, 
	                    initialProps: this.getWidgetProps(id), 
	                    shouldHighlight: shouldHighlight});
	        } else {
	            return null;
	        }
	    },

	    getApiOptions: function(props) {
	        return _.extend(
	            {},
	            ApiOptions.defaults,
	            props.apiOptions
	        );
	    },

	    getWidgetProps: function(id) {
	        var widgetProps = this.state.widgetProps[id] || {};

	        // The widget needs access to its "rubric" at all times when in review
	        // mode (which is really just part of its widget info).
	        var reviewModeRubric = null;
	        if (this.props.reviewMode && this.state.widgetInfo[id]) {
	            reviewModeRubric = this.state.widgetInfo[id].options;
	        }

	        return _.extend({}, widgetProps, {
	            ref: id,
	            widgetId: id,
	            problemNum: this.props.problemNum,
	            enabledFeatures: this.props.enabledFeatures,
	            apiOptions: this.getApiOptions(this.props),
	            questionCompleted: this.props.questionCompleted,
	            onFocus: _.partial(this._onWidgetFocus, id),
	            onBlur: _.partial(this._onWidgetBlur, id),
	            interWidgets: this.interWidgets,
	            reviewModeRubric: reviewModeRubric,
	            onChange: function(newProps, cb)  {
	                this._setWidgetProps(id, newProps, cb);
	            }.bind(this)
	        });
	    },

	    /**
	    * Serializes the questions state so it can be recovered.
	    *
	    * The return value of this function can be sent to the
	    * `restoreSerializedState` method to restore this state.
	    */
	    getSerializedState: function() {
	        return mapObject(this.state.widgetProps, function(props, widgetId)  {
	            var widget = this.getWidgetInstance(widgetId);
	            if (widget && widget.getSerializedState) {
	                return widget.getSerializedState();
	            } else {
	                return props;
	            }
	        }.bind(this));
	    },

	    restoreSerializedState: function(serializedState, callback) {
	        // We want to wait until any children widgets who have a
	        // restoreSerializedState function also call their own callbacks before
	        // we declare that the operation is finished.
	        var numCallbacks = 1;
	        var fireCallback = function()  {
	            --numCallbacks;
	            if (callback && numCallbacks === 0) {
	                callback();
	            }
	        };

	        this.setState({
	            widgetProps: mapObject(serializedState, function(props, widgetId)  {
	                var widget = this.getWidgetInstance(widgetId);
	                if (widget && widget.restoreSerializedState) {
	                    // Note that we probably can't call
	                    // `this.change()/this.props.onChange()` in this
	                    // function, so we take the return value and use
	                    // that as props if necessary so that
	                    // `restoreSerializedState` in a widget can
	                    // change the props as well as state.
	                    // If a widget has no props to change, it can
	                    // safely return null.
	                    ++numCallbacks;
	                    var restoreResult =
	                        widget.restoreSerializedState(props, fireCallback);
	                    return _.extend(
	                        {},
	                        this.state.widgetProps[widgetId],
	                        restoreResult
	                    );
	                } else {
	                    return props;
	                }
	            }.bind(this))
	        }, fireCallback);
	    },

	    /**
	     * Allows inter-widget communication.
	     *
	     * Each widget can access this function using `this.props.interWidgets`
	     * Takes a `filterCriterion` on which widgets to return.
	     * `filterCriterion` can be one of:
	     *  * A string widget id
	     *  * A string widget type
	     *  * a function from (id, widgetInfo, widgetComponent) to true or false
	     *
	     * Returns an array of the matching widget components.
	     *
	     * If you need to do logic with more than the components, it is possible
	     * to do such logic inside the filter, rather than on the result array.
	     *
	     * See the passage-ref widget for an example.
	     *
	     * "Remember: abilities are not inherently good or evil, it's how you use
	     * them." ~ Kyle Katarn
	     * Please use this one with caution.
	     */
	    interWidgets: function(filterCriterion) {
	        var filterFunc;
	        // Convenience filters:
	        // "interactive-graph 3" will give you [[interactive-graph 3]]
	        // "interactive-graph" will give you all interactive-graphs
	        if (typeof filterCriterion === "string") {
	            if (filterCriterion.indexOf(' ') !== -1) {
	                var widgetId = filterCriterion;
	                filterFunc = function(id, widgetInfo)  {return id === widgetId;};
	            } else {
	                var widgetType = filterCriterion;
	                filterFunc = function(id, widgetInfo)  {
	                    return widgetInfo.type === widgetType;
	                };
	            }
	        } else {
	            filterFunc = filterCriterion;
	        }

	        var results = this.widgetIds.filter(function(id)  {
	            var widgetInfo = this._getWidgetInfo(id);
	            var widget = this.getWidgetInstance(id);
	            return filterFunc(id, widgetInfo, widget);
	        }.bind(this)).map(this.getWidgetInstance);

	        // We allow the parent of our renderer to intercept our
	        // interwidgets call.
	        var propsInterWidgetResult = this.props.interWidgets(
	            filterCriterion,
	            results // allow our parent to inspect the local
	                    // interwidget results before acting
	        );

	        if (propsInterWidgetResult) {
	            return propsInterWidgetResult;
	        } else {
	            return results;
	        }
	    },

	    getWidgetInstance: function(id) {
	        var ref = this.refs["container:" + id];
	        if (!ref) {
	            return null;
	        }
	        return ref.getWidget();
	    },

	    _onWidgetFocus: function(id, focusPath) {
	        if (focusPath === undefined) {
	            focusPath = [];
	        } else {
	            if (!_.isArray(focusPath)) {
	                throw new Error(
	                    "widget props.onFocus focusPath must be an Array, " +
	                    "but was" + JSON.stringify(focusPath)
	                );
	            }
	        }
	        this._setCurrentFocus([id].concat(focusPath));
	    },

	    _onWidgetBlur: function(id, blurPath) {
	        var blurringFocusPath = this._currentFocus;

	        // Failsafe: abort if ID is different, because focus probably happened
	        // before blur
	        var fullPath = [id].concat(blurPath);
	        if (!_.isEqual(fullPath, blurringFocusPath)) {
	            return;
	        }

	        // Wait until after any new focus events fire this tick before
	        // declaring that nothing is focused.
	        // If a different widget was focused, we'll see an onBlur event
	        // now, but then an onFocus event on a different element before
	        // this callback is executed
	        _.defer(function()  {
	            if (_.isEqual(this._currentFocus, blurringFocusPath)) {
	                this._setCurrentFocus(null);
	            }
	        }.bind(this));
	    },

	    componentWillUpdate: function(nextProps, nextState) {
	        var oldJipt = this.shouldRenderJiptPlaceholder(this.props, this.state);
	        var newJipt = this.shouldRenderJiptPlaceholder(nextProps, nextState);
	        var oldContent = this.getContent(this.props, this.state);
	        var newContent = this.getContent(nextProps, nextState);
	        var oldHighlightedWidgets = this.props.highlightedWidgets;
	        var newHighlightedWidgets = nextProps.highlightedWidgets;

	        this.reuseMarkdown = !oldJipt && !newJipt &&
	            oldContent === newContent &&
	            // yes, this is identity array comparison, but these are passed
	            // in from state in the item-renderer, so they should be
	            // identity equal unless something changed, and it's expensive
	            // to loop through them to look for differences.
	            // Technically, we could reuse the markdown when this changes, but
	            // to do that we'd have to do more expensive checking of whether
	            // a widget should be highlighted in the common case where
	            // this array hasn't changed, so we just redo the whole
	            // render if this changed
	            oldHighlightedWidgets === newHighlightedWidgets;
	    },

	    componentDidUpdate: function(prevProps, prevState) {
	        this.handleRender(prevProps);
	        // We even do this if we did reuse the markdown because
	        // we might need to update the widget props on this render,
	        // even though we have the same widgets.
	        // WidgetContainers don't update their widgets props when
	        // they are re-rendered, so even if they've been
	        // re-rendered we need to call these methods on them.
	        _.each(this.widgetIds, function(id)  {
	            var container = this.refs["container:" + id];
	            container.replaceWidgetProps(
	                this.getWidgetProps(id)
	            );
	        }.bind(this));
	    },

	    getContent: function(props, state) {
	        return state.jiptContent || props.content;
	    },

	    shouldRenderJiptPlaceholder: function(props, state) {
	        return typeof KA !== "undefined" && KA.language === "en-PT" &&
	                    state.jiptContent == null &&
	                    props.content.indexOf('crwdns') !== -1;
	    },

	    render: function() {
	        if (this.reuseMarkdown) {
	            return this.lastRenderedMarkdown;
	        }

	        var content = this.getContent(this.props, this.state);
	        // `this.widgetIds` is appended to in `this.outputMarkdown`:
	        this.widgetIds = [];

	        if (this.shouldRenderJiptPlaceholder(this.props, this.state)) {
	            // Crowdin's JIPT (Just in place translation) uses a fake language
	            // with language tag "en-PT" where the value of the translations
	            // look like: {crwdns2657085:0}{crwdne2657085:0} where it keeps the
	            // {crowdinId:ngettext variant}. We detect whether the current
	            // content matches this, so we can take over rendering of
	            // the perseus content as the translators interact with jipt.
	            // We search for only part of the tag that crowdin uses to guard
	            // against them changing the format on us. The full tag it looks
	            // for can be found in https://cdn.crowdin.net/jipt/jipt.js
	            // globalPhrase var.

	            // If we haven't already added this component to the registry do so
	            // now. showHints() may cause this component to be rerendered
	            // before jipt has a chance to replace its contents, so this check
	            // will keep us from adding the component to the registry a second
	            // time.
	            if (!this.translationIndex) {
	                this.translationIndex =
	                    window.PerseusTranslationComponents.push(this) - 1;
	            }
	            // We now need to output this tag, as jipt looks for it to be
	            // able to replace it with a translation that it runs an ajax
	            // call to get.  We add a data attribute with the index to the
	            // Persues.TranslationComponent registry so that when jipt
	            // calls its before_dom_insert we can lookup this component by
	            // this attribute and render the text with markdown.
	            return React.createElement("div", {
	                    'data-perseus-component-index': this.translationIndex}, 
	                content
	            );
	        }

	        // Hacks:
	        // We use mutable state here to figure out whether the output
	        // had two columns.
	        // It is updated to true by `this.outputMarkdown` if a
	        // column break is found
	        // TODO(aria): Add a state variable to simple-markdown's output
	        // functions so that we can do this in a less hacky way.
	        this._isTwoColumn = false;

	        var parsedMardown = PerseusMarkdown.parse(content);
	        var markdownContents = this.outputMarkdown(parsedMardown);

	        var className = this._isTwoColumn ?
	            ApiClassNames.RENDERER + " " + ApiClassNames.TWO_COLUMN_RENDERER :
	            ApiClassNames.RENDERER;

	        this.lastRenderedMarkdown = React.createElement("div", {className: className}, 
	            markdownContents
	        );
	        return this.lastRenderedMarkdown;
	    },

	    // wrap top-level elements in a QuestionParagraph, mostly
	    // for appropriate spacing and other css
	    outputMarkdown: function(ast) {
	        if (_.isArray(ast)) {
	            return _.map(ast, function(node)  {return this.outputMarkdown(node);}.bind(this));
	        } else {
	            // !!! WARNING: Mutative hacks! mutates `this._foundTextNodes`:
	            // because i wrote a bad interface to simple-markdown.js' `output`
	            this._foundTextNodes = false;
	            var output = this.outputNested(ast);
	            var className = this._foundTextNodes ?
	                "" :
	                "perseus-paragraph-centered";

	            return React.createElement(QuestionParagraph, {className: className}, 
	                output
	            );
	        }
	    },

	    // output non-top-level nodes or arrays
	    outputNested: function(ast) {
	        if (_.isArray(ast)) {
	            return _.map(ast, this.outputNested);
	        } else {
	            return this.outputNode(ast, this.outputNested);
	        }
	    },

	    // output individual AST nodes [not arrays]
	    outputNode: function(node, nestedOutput) {
	        if (node.type === "widget") {
	            // Widgets can contain text nodes, so we don't center them with
	            // markdown magic here.
	            // Instead, we center them with css magic in articles.less
	            // /cry(aria)
	            this._foundTextNodes = true;

	            if (_.contains(this.widgetIds, node.id)) {
	                // We don't want to render a duplicate widget key/ref,
	                // as this causes problems with react (for obvious
	                // reasons). Instead we just notify the
	                // hopefully-content-creator that they need to change the
	                // widget id.
	                return React.createElement("span", {className: "renderer-widget-error"}, 
	                    "Widget [[", '\u2603', " ", node.id, "]] already exists."
	                );

	            } else {
	                this.widgetIds.push(node.id);
	                return this.renderWidget(node.widgetType, node.id);
	            }

	        } else if (node.type === "math") {
	            // We render math here instead of in perseus-markdown.jsx
	            // because we need to pass it our onRender callback.
	            return React.createElement(TeX, {onRender: this.props.onRender}, 
	                node.content
	            );

	        } else if (node.type === "image") {
	            // We need to add width and height to images from our
	            // props.images mapping.

	            // We do a _.has check here to avoid weird things like
	            // 'toString' or '__proto__' as a url.
	            var extraAttrs = (_.has(this.props.images, node.target)) ?
	                this.props.images[node.target] :
	                null;

	            return React.createElement(SvgImage, React.__spread({
	                src: PerseusMarkdown.sanitizeUrl(node.target), 
	                alt: node.alt, 
	                title: node.title}, 
	                extraAttrs));

	        } else if (node.type === "columns") {
	            // Note that we have two columns. This is so we can put
	            // a className on the outer renderer content for SAT.
	            // TODO(aria): See if there is a better way we can do
	            // things like this
	            this._isTwoColumn = true;
	            // but then render normally:
	            return PerseusMarkdown.ruleOutput(node, nestedOutput);

	        } else if (node.type === "text") {
	            if (rContainsNonWhitespace.test(node.content)) {
	                this._foundTextNodes = true;
	            }
	            return node.content;

	        } else {
	            // If it's a "normal" or "simple" markdown node, just
	            // output it using its output rule.
	            return PerseusMarkdown.ruleOutput(node, nestedOutput);
	        }
	    },

	    handleRender: function(prevProps) {
	        var onRender = this.props.onRender;
	        var oldOnRender = prevProps.onRender;
	        var $images = $(this.getDOMNode()).find("img");

	        // Fire callback on image load...
	        // TODO (jack): make this call happen exactly once through promises!
	        if (oldOnRender) {
	            $images.off("load", oldOnRender);
	        }
	        $images.on("load", onRender);

	        // ...as well as right now (non-image, non-TeX or image from cache)
	        onRender();
	    },

	    componentDidMount: function() {
	        this.handleRender({});
	        this._currentFocus = null;
	    },

	    componentWillUnmount: function() {
	        if (this.translationIndex != null) {
	            window.PerseusTranslationComponents[this.translationIndex] = null;
	        }
	    },

	    // Sets the current focus path
	    // If the new focus path is not a prefix of the old focus path,
	    // we send an onChangeFocus event back to our parent.
	    _setCurrentFocus: function(path) {
	        // We don't do this when the new path is a prefix because
	        // that prefix is already focused (we're just in a more specific
	        // area of it). This makes it safe to call _setCurrentFocus
	        // whenever a widget is interacted with--we won't wipe out
	        // our focus state if we are already focused on a subpart
	        // of that widget (i.e. a transformation NumberInput inside
	        // of a transformer widget).
	        if (!isIdPathPrefix(path, this._currentFocus)) {
	            var prevFocus = this._currentFocus;

	            if (prevFocus) {
	                this.blurPath(prevFocus);
	            }

	            this._currentFocus = path;
	            if (this.props.apiOptions.onFocusChange != null) {
	                this.props.apiOptions.onFocusChange(
	                    this._currentFocus,
	                    prevFocus
	                );
	            }
	        }
	    },

	    focus: function() {
	        var id;
	        var focusResult;
	        for (var i = 0; i < this.widgetIds.length; i++) {
	            var widgetId = this.widgetIds[i];
	            var widget = this.getWidgetInstance(widgetId);
	            var widgetFocusResult = widget && widget.focus && widget.focus();
	            if (widgetFocusResult) {
	                id = widgetId;
	                focusResult = widgetFocusResult;
	                break;
	            }
	        }

	        if (id) {
	            // reconstruct a {path, element} focus object
	            var path;
	            if (_.isObject(focusResult)) {
	                // The result of focus was a {path, id} object itself
	                path = [id].concat(focusResult.path || []);
	            } else {
	                // The result of focus was true or the like; just
	                // construct a root focus object
	                path = [id];
	            }

	            this._setCurrentFocus(path);
	            return true;
	        }
	    },

	    getDOMNodeForPath: function(path) {
	        var widgetId = _.first(path);
	        var interWidgetPath = _.rest(path);

	        // Widget handles parsing of the interWidgetPath. If the path is empty
	        // beyond the widgetID, as a special case we just return the widget's
	        // DOM node.
	        var widget = this.getWidgetInstance(widgetId);
	        var getNode = widget.getDOMNodeForPath;
	        if (getNode) {
	            return getNode(interWidgetPath);
	        } else if (interWidgetPath.length === 0) {
	            return widget.getDOMNode();
	        }
	    },

	    getGrammarTypeForPath: function(path) {
	        var widgetId = _.first(path);
	        var interWidgetPath = _.rest(path);

	        var widget = this.getWidgetInstance(widgetId);
	        return widget.getGrammarTypeForPath(interWidgetPath);
	    },

	    getInputPaths: function() {
	        var inputPaths = [];
	        _.each(this.widgetIds, function(widgetId)  {
	            var widget = this.getWidgetInstance(widgetId);
	            if (widget.getInputPaths) {
	                // Grab all input paths and add widgetID to the front
	                var widgetInputPaths = widget.getInputPaths();
	                // Prefix paths with their widgetID and add to collective
	                // list of paths.
	                _.each(widgetInputPaths, function(inputPath)  {
	                    var relativeInputPath = [widgetId].concat(inputPath);
	                    inputPaths.push(relativeInputPath);
	                });
	            }
	        }.bind(this));

	        return inputPaths;
	    },

	    focusPath: function(path) {
	        // No need to focus if it's already focused
	        if (_.isEqual(this._currentFocus, path)) {
	            return;
	        } else if (this._currentFocus) {
	            // Unfocus old path, if exists
	            this.blurPath(this._currentFocus);
	        }

	        var widgetId = _.first(path);
	        var interWidgetPath = _.rest(path);

	        // Widget handles parsing of the interWidgetPath
	        var focusWidget = this.getWidgetInstance(widgetId).focusInputPath;
	        focusWidget && focusWidget(interWidgetPath);
	    },

	    blurPath: function(path) {
	        // No need to blur if it's not focused
	        if (!_.isEqual(this._currentFocus, path)) {
	            return;
	        }

	        var widgetId = _.first(path);
	        var interWidgetPath = _.rest(path);
	        var widget = this.getWidgetInstance(widgetId);
	        // We might be in the editor and blurring a widget that no
	        // longer exists, so only blur if we actually found the widget
	        if (widget) {
	            var blurWidget = this.getWidgetInstance(widgetId).blurInputPath;
	            // Widget handles parsing of the interWidgetPath
	            blurWidget && blurWidget(interWidgetPath);
	        }
	    },

	    blur: function() {
	        if (this._currentFocus) {
	            this.blurPath(this._currentFocus);
	        }
	    },

	    getSaveWarnings: function() {
	        return _(this.state.widgetInfo)
	            .chain()
	            .map(function(info, id)  {
	                var widget = this.getWidgetInstance(id);
	                var issuesFunc = widget.getSaveWarnings;
	                return issuesFunc ? issuesFunc() : [];
	            }.bind(this))
	            .flatten(true)
	            .value();
	    },

	    serialize: function() {
	        var state = {};
	        _.each(this.state.widgetInfo, function(info, id) {
	            var widget = this.getWidgetInstance(id);
	            var s = widget.serialize();
	            if (!_.isEmpty(s)) {
	                state[id] = s;
	            }
	        }, this);
	        return state;
	    },

	    emptyWidgets: function () {
	        return _.filter(this.widgetIds, function(id)  {
	            var widgetInfo = this._getWidgetInfo(id);
	            var score = this.getWidgetInstance(id).simpleValidate(
	                widgetInfo.options,
	                null
	            );
	            return Util.scoreIsEmpty(score);
	        }.bind(this));
	    },

	    _setWidgetProps: function(id, newProps, cb) {
	        var widgetProps = _.clone(this.state.widgetProps);
	        widgetProps[id] = _.extend({}, widgetProps[id], newProps);
	        this.setState({widgetProps: widgetProps}, function()  {
	            var cbResult = cb && cb();
	            this.props.onInteractWithWidget(id);
	            if (cbResult !== false) {
	                // TODO(jack): For some reason, some widgets don't always end
	                // up in refs here, which is repro-able if you make an
	                // [[ orderer 1 ]] and copy-paste this, then change it to be
	                // an [[ orderer 2 ]]. The resulting Renderer ends up with
	                // an "orderer 2" ref but not an "orderer 1" ref. @_@??
	                // TODO(jack): Figure out why this is happening and fix it
	                // As far as I can tell, this is only an issue in the
	                // editor-page, so doing this shouldn't break clients hopefully
	                this._setCurrentFocus([id]);
	            }
	        }.bind(this));
	    },

	    setInputValue: function(path, newValue, focus) {
	        var widgetId = _.first(path);
	        var interWidgetPath = _.rest(path);
	        var widget = this.getWidgetInstance(widgetId);

	        // Widget handles parsing of the interWidgetPath.
	        widget.setInputValue(interWidgetPath, newValue, focus);
	    },

	    /**
	     * Returns an array of the widget `.getUserInput()` results
	     */
	    getUserInput: function() {
	        return _.map(this.widgetIds, function(id)  {
	            return this.getWidgetInstance(id).getUserInput();
	        }.bind(this));
	    },

	    /**
	     * Returns an array of all widget IDs in the order they occur in
	     * the content.
	     */
	    getWidgetIds: function() {
	        return this.widgetIds;
	    },

	    /**
	     * WARNING: This is an experimental/temporary API and should not be relied
	     *     upon in production code. This function may change its behavior or
	     *     disappear without notice.
	     *
	     * Returns a treelike structure containing all widget IDs (this will
	     * descend into group widgets as well).
	     *
	     * An example of what the structure looks like:
	     *
	     * [
	     *    {id: "radio 1", children: []},
	     *    {
	     *        id: "group 1",
	     *        children: [
	     *            {id: "radio 1", children: []}
	     *            {id: "radio 2", children: []}
	     *        ]
	     *    }
	     * ]
	     *
	     * Widgets will be listed in the order that they appear in their renderer.
	     */
	    getAllWidgetIds: function() {
	        // Recursively builds our result
	        return _.map(this.getWidgetIds(), function(id)  {
	            var groupPrefix = "group";
	            if (id.substring(0, groupPrefix.length) === groupPrefix) {
	                return {
	                    id: id,
	                    children:
	                        this.getWidgetInstance(id)
	                            .getRenderer()
	                            .getAllWidgetIds(),
	                };
	            }

	            // This is our base case
	            return {id: id, children: []};
	        }.bind(this));
	    },

	    /**
	     * Returns the result of `.getUserInput()` for each widget, in
	     * a map from widgetId to userInput.
	     */
	    getUserInputForWidgets: function() {
	        return mapObjectFromArray(this.widgetIds, function(id)  {
	            return this.getWidgetInstance(id).getUserInput();
	        }.bind(this));
	    },

	    /**
	     * Returns an object mapping from widget ID to perseus-style score.
	     * The keys of this object are the values of the array returned
	     * from `getWidgetIds`.
	     */
	    scoreWidgets: function() {
	        var widgetProps = this.state.widgetInfo;
	        var onInputError = this.props.apiOptions.onInputError ||
	                function() { };

	        var gradedWidgetIds = _.filter(this.widgetIds, function(id)  {
	            var props = widgetProps[id];
	            // props.graded is unset or true
	            return props.graded == null || props.graded;
	        });

	        var widgetScores = mapObjectFromArray(gradedWidgetIds, function(id)  {
	            var props = widgetProps[id];
	            var widget = this.getWidgetInstance(id);
	            return widget.simpleValidate(props.options, onInputError);
	        }.bind(this));

	        return widgetScores;
	    },

	    /**
	     * Grades the content.
	     *
	     * Returns a perseus-style score of {
	     *     type: "invalid"|"points",
	     *     message: string,
	     *     earned: undefined|number,
	     *     total: undefined|number
	     * }
	     */
	    score: function() {
	        return _.reduce(this.scoreWidgets(), Util.combineScores, Util.noScore);
	    },

	    guessAndScore: function() {
	        var totalGuess = this.getUserInput();
	        var totalScore = this.score();

	        return [totalGuess, totalScore];
	    },

	    examples: function() {
	        var widgets = this.widgetIds;
	        var examples = _.compact(_.map(widgets, function(widget) {
	            return widget.examples ? widget.examples() : null;
	        }));

	        // no widgets with examples
	        if (!examples.length) {
	            return null;
	        }

	        var allEqual = _.all(examples, function(example) {
	            return _.isEqual(examples[0], example);
	        });

	        // some widgets have different examples
	        // TODO(alex): handle this better
	        if (!allEqual) {
	            return null;
	        }

	        return examples[0];
	    },
	});

	module.exports = Renderer;


/***/ },
/* 11 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);
	var _ = __webpack_require__(60);

	var EditorPage = __webpack_require__(8);

	/* Renders an EditorPage (or an ArticleEditor) as a non-controlled component.
	 *
	 * Normally the parent of EditorPage must pass it an onChange callback and then
	 * respond to any changes by modifying the EditorPage props to reflect those
	 * changes. With StatefulEditorPage changes are stored in state so you can
	 * query them with serialize.
	 */
	var StatefulEditorPage = React.createClass({displayName: 'StatefulEditorPage',

	    propTypes: {
	        componentClass: React.PropTypes.func
	    },

	    getDefaultProps: function() {
	        return {
	            componentClass: EditorPage
	        };
	    },

	    render: function() {
	        return React.createElement(this.props.componentClass, React.__spread({},  this.state));
	    },

	    getInitialState: function() {
	        return _({}).extend(_.omit(this.props, 'componentClass'), {
	            onChange: this.handleChange,
	            ref: "editor"
	        });
	    },

	    // getInitialState isn't called if the react component is re-rendered
	    // in-place on the dom, in which case this is called instead, so we
	    // need to update the state here.
	    // (This component is currently re-rendered by the "Add image" button.)
	    componentWillReceiveProps: function(nextProps) {
	        // be careful not to overwrite our onChange and ref
	        this.setState(_(nextProps).omit("onChange", "ref"));
	    },

	    getSaveWarnings: function() {
	        return this.refs.editor.getSaveWarnings();
	    },

	    serialize: function() {
	        return this.refs.editor.serialize();
	    },

	    handleChange: function(newState, cb) {
	        if (this.isMounted()) {
	            this.setState(newState, cb);
	        }
	    },

	    scorePreview: function() {
	        return this.refs.editor.scorePreview();
	    }
	});

	module.exports = StatefulEditorPage;


/***/ },
/* 12 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * [Most of] the Perseus client API.
	 *
	 * If making a change to this file, or otherwise to the perseus
	 * API, you should increment:
	 *  * the perseus api major version if it is a breaking change
	 *  * the perseus api minor version if it is an additive-only change
	 *  * nothing if it is purely a bug fix.
	 *
	 * Callbacks passed to Renderer/ItemRenderer:
	 *  * onInputError:
	 *    Called when there is an error grading a widget
	 *  * onFocusChange: (newFocus, oldFocus)
	 *    Called when the user focus changes. Each parameter is an object
	 *    containing two fields, `path` and `element`.
	 *    `path` is an array uniquely identifying the input to perseus
	 *    `element` is a DOM element representing the area covered by
	 *    the input (but is not necessarily an `<input>` element).
	 *    When focus changes to or from nothing being selected, `path`
	 *    will be null.
	 *
	 * Stable CSS ClassNames:
	 * These are css class names that will continue to preserve their
	 * semantic meaning across the same perseus api major version.
	 */

	var StubTagEditor = __webpack_require__(59);

	module.exports = {
	    Options: {
	        propTypes: React.PropTypes.shape({
	            fancyDropdowns: React.PropTypes.bool.isRequired,
	            onInputError: React.PropTypes.func.isRequired,
	            onFocusChange: React.PropTypes.func.isRequired,
	            staticRender: React.PropTypes.bool.isRequired,
	            GroupMetadataEditor: React.PropTypes.func.isRequired,
	            // Enable old answer types in test.html
	            // TODO(aria) Remove when Alex kills the answer area
	            enableOldAnswerTypes: React.PropTypes.bool.isRequired,
	            readOnly: React.PropTypes.bool.isRequired,

	            // A function that takes in the relative problem number (starts at
	            // 0 and is incremented for each group widget), and the ID of the
	            // group widget, then returns a react component that will be added
	            // immediately above the renderer in the group widget. If the
	            // function returns null, no annotation will be added.
	            groupAnnotator: React.PropTypes.func.isRequired,
	        }).isRequired,

	        defaults: {
	            fancyDropdowns: false,
	            onInputError: function() { },
	            onFocusChange: function() { },
	            staticRender: false,
	            GroupMetadataEditor: StubTagEditor,
	            enableOldAnswerTypes: false,
	            readOnly: false,
	            groupAnnotator: function() { return null; },
	        }
	    },
	    ClassNames: {
	        RENDERER: "perseus-renderer",
	        TWO_COLUMN_RENDERER: "perseus-renderer-two-columns",
	        INPUT: "perseus-input",
	        FOCUSED: "perseus-focused",
	        RADIO: {
	            OPTION: "perseus-radio-option",
	            SELECTED: "perseus-radio-selected",
	            OPTION_CONTENT: "perseus-radio-option-content"
	        },
	        INTERACTIVE: "perseus-interactive",
	        CORRECT: "perseus-correct",
	        INCORRECT: "perseus-incorrect",
	        UNANSWERED: "perseus-unanswered",
	    }
	};



/***/ },
/* 13 */
/***/ function(module, exports, __webpack_require__) {

	// Responsible for combining the text diffs from text-diff and the widget
	// diffs from widget-differ.

	var TextDiff = __webpack_require__(55);
	var WidgetDiff = __webpack_require__(56);

	// Deeply look up a property in an object,
	// -> getPath(obj, ["a", "b", "c"]) === obj["a"]["b"]["c"]
	var getPath = function(obj, path, defaultValue) {
	    var returningDefault = false;
	    var result = _(path).reduce(function(obj, key) {
	        if (returningDefault || !obj.hasOwnProperty(key)) {
	            returningDefault = true;
	            return defaultValue;
	        }
	        return obj[key];
	    }, obj);
	    return result;
	};

	var widgetsIn = function(item) {
	    var question = item.question || {};
	    var widgets = question.widgets || {};
	    return _.keys(widgets);
	};

	var isWidget = function(obj)  {return _.isObject(obj) && !("content" in obj);};

	var RevisionDiff = React.createClass({displayName: 'RevisionDiff',
	    propTypes: {
	        beforeItem: React.PropTypes.object.isRequired,
	        afterItem: React.PropTypes.object.isRequired
	    },

	    render: function() {
	        var before = this.props.beforeItem;
	        var after = this.props.afterItem;
	        // Not going to handle inserting hints in the middle so well, but
	        // that's pretty complicated to handle nicely.
	        // This will do for now.
	        var hintCount = 0;
	        if (_(before).has("hints") && _(after).has("hints")) {
	            hintCount = Math.max(before.hints.length, after.hints.length);
	        }

	        var widgets = _.union(widgetsIn(before), widgetsIn(after));
	        var sections = [
	            {
	                title: "Question Area",
	                path: ["question"]
	            },
	            {
	                title: "Answer Area",
	                path: ["answerArea", "options"]
	            }
	        ].concat(
	            _.times(hintCount, function(n) {
	                return {
	                    title: "Hint #" + (n + 1),
	                    path: ["hints", n]
	                };
	            })
	        ).concat(
	            _.map(widgets, function(widget) {
	                return {
	                    title: widget,
	                    path: ["question", "widgets", widget, "options"]
	                };
	            })
	        );

	        var result = [];

	        _(sections).each(function(section) {
	            var path = section.path;
	            var beforeValue = getPath(before, path, "");
	            var afterValue = getPath(after, path, "");
	            var displayedDiff;
	            if (isWidget(beforeValue) || isWidget(afterValue)) {
	                if (!isWidget(beforeValue)) {
	                    beforeValue = {};
	                }
	                if (!isWidget(afterValue)) {
	                    afterValue = {};
	                }
	                displayedDiff = React.createElement(WidgetDiff, {
	                    key: section.title, 
	                    title: section.title, 
	                    before: beforeValue, 
	                    after: afterValue});
	            } else {
	                displayedDiff = React.createElement(TextDiff, {
	                    key: section.title, 
	                    title: section.title, 
	                    before: beforeValue.content, 
	                    after: afterValue.content});
	            }
	            result.push(React.createElement("div", null, 
	                React.createElement("div", {className: "diff-header"}, section.title), 
	                React.createElement("div", {className: "diff-header"}, section.title), 
	                React.createElement("div", {className: "diff-body ui-helper-clearfix"}, 
	                    displayedDiff
	                )
	            ));
	        });

	        return React.createElement("div", null, result);
	    }
	});

	module.exports = RevisionDiff;


/***/ },
/* 14 */
/***/ function(module, exports, __webpack_require__) {

	var _ = __webpack_require__(60);

	var widgets = {};

	var Widgets = {
	    // Widgets must be registered to avoid circular dependencies with the
	    // core Editor and Renderer components.
	    register: function(name, data) {
	        widgets[name] = data;
	    },

	    getWidget: function(name, enabledFeatures) {
	        // TODO(alex): Consider referring to these as renderers to avoid
	        // overloading "widget"
	        if (!_.has(widgets, name)) {
	            return null;
	        }

	        // Allow widgets to specify a widget directly or via a function
	        if (widgets[name].getWidget) {
	            return widgets[name].getWidget(enabledFeatures);
	        } else {
	            return widgets[name].widget;
	        }
	    },

	    getEditor: function(name) {
	        return _.has(widgets, name) ? widgets[name].editor : null;
	    },

	    getTransform: function(name) {
	        return _.has(widgets, name) ?
	            widgets[name].transform || _.identity :
	            null;
	    },

	    getVersion: function(name) {
	        return widgets[name].version || {major: 0, minor: 0};
	    },

	    getVersionVector: function() {
	        var version = {};
	        _.each(_.keys(widgets), function(name) {
	            version[name] = Widgets.getVersion(name);
	        });
	        return version;
	    },

	    getPublicWidgets: function() {
	        // TODO(alex): Update underscore.js so that _.pick can take a function.
	        return _.pick(widgets, _.reject(_.keys(widgets), function(name) {
	            return widgets[name].hidden;
	        }));
	    },

	    getAllWidgetTypes: function() {
	        return _.keys(widgets);
	    },

	    upgradeWidgetInfoToLatestVersion: function(oldWidgetInfo) {
	        var type = oldWidgetInfo.type;
	        if (!_.isString(type)) {
	            throw new Error("widget type must be a string, but was: " + type);
	        }
	        var widgetExports = widgets[type];

	        if (widgetExports == null) {
	            // If we have a widget that isn't registered, we can't upgrade it
	            // TODO(jack): Figure out what the best thing to do here would be
	            return oldWidgetInfo;
	        }

	        // Unversioned widgets (pre-July 2014) are all implicitly 0.0
	        var initialVersion = oldWidgetInfo.version || {major: 0, minor: 0};
	        var latestVersion = widgetExports.version || {major: 0, minor: 0};

	        // We do a clone here so that it's safe to mutate the input parameter
	        // in propUpgrades functions (which I will probably accidentally do at
	        // some point, and we would like to not break when that happens).
	        var newEditorProps = _.clone(oldWidgetInfo.options) || {};

	        var upgradePropsMap = widgetExports.propUpgrades || {};

	        // Empty props usually mean a newly created widget by the editor,
	        // and are always considerered up-to-date.
	        // Mostly, we'd rather not run upgrade functions on props that are
	        // not complete.
	        if (_.keys(newEditorProps).length !== 0) {

	            // We loop through all the versions after the current version of
	            // the loaded widget, up to and including the latest version of the
	            // loaded widget, and run the upgrade function to bring our loaded
	            // widget's props up to that version.
	            // There is a little subtlety here in that we call
	            // upgradePropsMap[1] to upgrade *to* version 1,
	            // (not from version 1).
	            for (var nextVersion = initialVersion.major + 1;
	                    nextVersion <= latestVersion.major;
	                    nextVersion++) {

	                if (upgradePropsMap[nextVersion]) {
	                    newEditorProps = upgradePropsMap[nextVersion](
	                        newEditorProps
	                    );

	                } else if ((typeof console !== 'undefined') && console.warn) {
	                    // This is a warning because it is unlikely to be hit in
	                    // local testing, and a warning is slightly less scary in
	                    // prod than a `throw new Error`
	                    console.warn(
	                        "No upgrade found for widget `" + type + "` from " +
	                        "major version `" + (nextVersion - 1) + "` to " +
	                        "major version `" + nextVersion + "` found. This " +
	                        "is necessary to render this `" + type + "` correctly."
	                    );
	                    // But try to keep going anyways (yolo!)
	                    // (Throwing an error here would just break the page
	                    // silently anyways, so that doesn't seem much better
	                    // than a halfhearted attempt to continue, however
	                    // shallow...)
	                }
	            }
	        }

	        return _.extend({}, oldWidgetInfo, {  // maintain other info, like type
	            version: latestVersion,
	            // Default graded to true (so null/undefined becomes true):
	            graded: (
	                (oldWidgetInfo.graded != null) ? oldWidgetInfo.graded : true
	            ),
	            options: newEditorProps
	        });
	    },

	    getRendererPropsForWidgetInfo: function(widgetInfo, problemNum) {
	        var type = widgetInfo.type;
	        var widgetExports = widgets[type];
	        if (widgetExports == null) {
	            // The widget is not a registered widget
	            // It shouldn't matter what we return here, but for consistency
	            // we return the untransformed options, as if the widget did
	            // not have a transform defined.
	            return widgetInfo.options;
	        }
	        var transform = widgetExports.transform || _.identity;
	        // widgetInfo.options are the widgetEditor's props:
	        return transform(widgetInfo.options, problemNum);
	    }
	};

	module.exports = Widgets;


/***/ },
/* 15 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);
	var Changeable = __webpack_require__(66);
	var EditorJsonify = __webpack_require__(67);
	var WidgetJsonifyDeprecated = __webpack_require__(68);
	var _ = __webpack_require__(60);

	var InfoTip = __webpack_require__(69);
	var PropCheckBox = __webpack_require__(58);
	var Renderer = __webpack_require__(10);
	var TextListEditor = __webpack_require__(70);
	var Util = __webpack_require__(3);

	var Categorizer = React.createClass({displayName: 'Categorizer',
	    mixins: [WidgetJsonifyDeprecated, Changeable],

	    propTypes: {
	        // List of items that are being categorized (along the left side)
	        items: React.PropTypes.arrayOf(React.PropTypes.string),
	        // List of categories (across the top)
	        categories: React.PropTypes.arrayOf(React.PropTypes.string),
	        // Ordered list of correct answers, mapping items to categories thusly:
	        //   values[<items_index>] == <categories_index>
	        values: React.PropTypes.arrayOf(React.PropTypes.number)
	    },

	    getDefaultProps: function() {
	        return {
	            items: [],
	            categories: [],
	            values: []
	        };
	    },

	    getInitialState: function() {
	        return {
	            uniqueId: _.uniqueId("perseus_radio_")
	        };
	    },

	    render: function() {
	        var self = this;

	        var indexedItems = _.map(this.props.items, function(item, n)  {return [item, n];});
	        if (this.props.randomizeItems) {
	            indexedItems = Util.shuffle(indexedItems, this.props.problemNum);
	        }

	        return React.createElement("div", {className: "categorizer-container clearfix"}, React.createElement("table", null, 
	            React.createElement("thead", null, React.createElement("tr", null, 
	                React.createElement("th", null, " "), 
	                _.map(this.props.categories, function(category)  {
	                    return React.createElement("th", {className: "category"}, 
	                        React.createElement(Renderer, {content: category})
	                    );
	                })
	            )), 
	            React.createElement("tbody", null, _.map(indexedItems, function(indexedItem)  {
	                var item = indexedItem[0];
	                var itemNum = indexedItem[1];
	                var uniqueId = self.state.uniqueId + "_" + itemNum;
	                return React.createElement("tr", null, 
	                    React.createElement("td", null, React.createElement(Renderer, {content: item})), 
	                    _.range(self.props.categories.length).map(function(catNum)  {
	                        return React.createElement("td", {className: "category"}, 
	                            /* a pseudo-label: toggle the value of the
	                                checkbox when this div or the checkbox is
	                                clicked */
	                            React.createElement("div", {
	                                    onClick: this.onChange.bind(
	                                        this,
	                                        itemNum,
	                                        catNum
	                                    )}, 
	                                React.createElement("input", {
	                                    type: "radio", 
	                                    name: uniqueId, 
	                                    checked: 
	                                        self.props.values[itemNum] === catNum, 
	                                    
	                                    onChange: this.onChange.bind(
	                                        this,
	                                        itemNum,
	                                        catNum
	                                    ), 
	                                    onClick: function(e)  {return e.stopPropagation();}}
	                                    ), 
	                                React.createElement("span", null)
	                            )
	                        );
	                    }.bind(this))
	                );
	            }.bind(this)))
	        ));
	    },

	    onChange: function(itemNum, catNum) {
	        var values = _.clone(this.props.values);
	        values[itemNum] = catNum;
	        this.change("values", values);
	    },

	    simpleValidate: function(rubric) {
	        return Categorizer.validate(this.getUserInput(), rubric);
	    },

	    statics: {
	        displayMode: "block"
	    }
	});


	_.extend(Categorizer, {
	    validate: function(state, rubric) {
	        var completed = true;
	        var allCorrect = true;
	        _.each(rubric.values, function(value, i) {
	            if (state.values[i] == null) {
	                completed = false;
	            }
	            if (state.values[i] !== value) {
	                allCorrect = false;
	            }
	        });
	        if (!completed) {
	            return {
	                type: "invalid",
	                // XXX(joel) - i18n
	                message: "Make sure you select something for every row."
	            };
	        }
	        return {
	            type: "points",
	            earned: allCorrect ? 1 : 0,
	            total: 1,
	            message: null
	        };
	    }
	});


	var CategorizerEditor = React.createClass({displayName: 'CategorizerEditor',
	    mixins: [EditorJsonify, Changeable],

	    propTypes: {
	        items: React.PropTypes.arrayOf(React.PropTypes.string),
	        categories: React.PropTypes.arrayOf(React.PropTypes.string),
	        values: React.PropTypes.arrayOf(React.PropTypes.number),
	        randomizeItems: React.PropTypes.bool
	    },

	    getDefaultProps: function() {
	        return {
	            items: [],
	            categories: [],
	            values: [],
	            randomizeItems: false
	        };
	    },

	    render: function() {
	        return React.createElement("div", null, 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                React.createElement(PropCheckBox, {
	                    label: "Randomize item order", 
	                    labelAlignment: "right", 
	                    randomizeItems: this.props.randomizeItems, 
	                    onChange: this.props.onChange})
	            ), 

	            "Categories:", 
	            React.createElement(TextListEditor, {
	                options: this.props.categories, 
	                onChange: function(cat)  {this.change("categories", cat);}.bind(this), 
	                layout: "horizontal"}), 

	            "Items:", 
	            React.createElement(TextListEditor, {
	                options: this.props.items, 
	                onChange: function(items)  {this.change({
	                        items: items,
	                        // TODO(eater): This truncates props.values so there
	                        // are never more correct answers than items, ensuring
	                        // the widget is possible to answer correctly.
	                        // It doesn't necessarly keep each answer with
	                        // its corresponding item if an item is deleted from
	                        // the middle. Inconvenient, but it's at least possible
	                        // for content creators to catch and fix.
	                        values: _.first(this.props.values, items.length)
	                    });}.bind(this), 
	                layout: "vertical"}), 

	            React.createElement(Categorizer, {
	                items: this.props.items, 
	                categories: this.props.categories, 
	                values: this.props.values, 
	                onChange: function(newProps)  {this.props.onChange(newProps);}.bind(this)}
	                )
	        );
	    },
	});

	module.exports = {
	    name: "categorizer",
	    displayName: "Categorizer",
	    widget: Categorizer,
	    editor: CategorizerEditor,
	    transform: function(editorProps)  {
	        return _.pick(editorProps, "items", "categories", "randomizeItems");
	    }
	};



/***/ },
/* 16 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);
	var InfoTip = __webpack_require__(69);
	var FancySelect = __webpack_require__(71);
	var FancyOption = FancySelect.Option;
	var _ = __webpack_require__(60);

	var EditorJsonify = __webpack_require__(67);
	var ApiClassNames = __webpack_require__(12).ClassNames;
	var ApiOptions = __webpack_require__(12).Options;

	var captureScratchpadTouchStart =
	        __webpack_require__(3).captureScratchpadTouchStart;

	var Dropdown = React.createClass({displayName: 'Dropdown',
	    propTypes: {
	        choices: React.PropTypes.arrayOf(React.PropTypes.string),
	        selected: React.PropTypes.number,
	        placeholder: React.PropTypes.string,
	        apiOptions: ApiOptions.propTypes
	    },

	    getDefaultProps: function() {
	        return {
	            choices: [],
	            selected: 0,
	            placeholder: "",
	            apiOptions: ApiOptions.defaults
	        };
	    },

	    render: function() {
	        var choices = this.props.choices.slice();

	        var selectClasses = React.addons.classSet({
	            "perseus-widget-dropdown": true,
	            "perseus-fancy-dropdown": this.props.apiOptions.fancyDropdowns
	        });

	        if (this.props.apiOptions.fancyDropdowns) {
	            return React.createElement(FancySelect, {
	                    onChange: this._handleChange, 
	                    className: selectClasses + " " + ApiClassNames.INTERACTIVE, 
	                    value: this.props.selected}, 
	                React.createElement(FancyOption, {value: 0, visible: false}, 
	                    React.createElement("span", {className: "placeholder"}, 
	                        this.props.placeholder
	                    )
	                ), 
	                choices.map(function(choice, i)  {
	                    // Always visible so we can animate them with css
	                    return React.createElement(FancyOption, {value: i + 1, visible: true}, 
	                        choice
	                    );
	                })
	            );

	        } else {
	            return React.createElement("select", {
	                    onChange: this._handleChangeEvent, 
	                    onTouchStart: captureScratchpadTouchStart, 
	                    className: selectClasses + " " + ApiClassNames.INTERACTIVE, 
	                    value: this.props.selected}, 
	                React.createElement("option", {value: 0, disabled: true}, 
	                    this.props.placeholder
	                ), 
	                choices.map(function(choice, i)  {
	                    return React.createElement("option", {
	                            key: "" + (i + 1), 
	                            value: i + 1}, 
	                        choice
	                    );
	                })
	            );
	        }
	    },

	    focus: function() {
	        this.getDOMNode().focus();
	        return true;
	    },

	    _handleChangeEvent: function(e) {
	        this._handleChange(parseInt(e.target.value));
	    },

	    _handleChange: function(selected) {
	        this.props.onChange({selected: selected});
	    },

	    getUserInput: function() {
	        return {value: this.props.selected};
	    },

	    simpleValidate: function(rubric) {
	        return Dropdown.validate(this.getUserInput(), rubric);
	    },

	    statics: {
	        displayMode: "inline-block"
	    }
	});

	_.extend(Dropdown, {
	    validate: function(state, rubric) {
	        var selected = state.value;
	        if (selected === 0) {
	            return {
	                type: "invalid",
	                message: null
	            };
	        } else {
	            var correct = rubric.choices[selected - 1].correct;
	            return {
	                type: "points",
	                earned: correct ? 1 : 0,
	                total: 1,
	                message: null
	            };
	        }
	    }
	});

	var DropdownEditor = React.createClass({displayName: 'DropdownEditor',
	    mixins: [EditorJsonify],

	    propTypes: {
	        choices: React.PropTypes.arrayOf(React.PropTypes.shape({
	            content: React.PropTypes.string,
	            correct: React.PropTypes.bool
	        })),
	        placeholder: React.PropTypes.string
	    },

	    getDefaultProps: function() {
	        return {
	            placeholder: "",
	            choices: [{
	                content: "",
	                correct: false
	            }]
	        };
	    },

	    render: function() {
	        var dropdownGroupName = _.uniqueId("perseus_dropdown_");
	        return React.createElement("div", {className: "perseus-widget-dropdown"}, 
	            React.createElement("div", null, "Dropdown", 
	                React.createElement(InfoTip, null, 
	                    React.createElement("p", null, "The drop down is useful for making inequalities in a" + ' ' +
	                    "custom format. We normally use the symbols ", "<", ", ", ">", "," + ' ' +
	                    "≤, ≥ (in that order) which you can copy into the" + ' ' +
	                    "choices. When possible, use the \"multiple choice\" answer" + ' ' +
	                    "type instead.")
	                )
	            ), 
	            React.createElement("input", {
	                type: "text", 
	                placeholder: "Placeholder value", 
	                value: this.props.placeholder, 
	                onChange: this.onPlaceholderChange}), 
	            React.createElement(InfoTip, null, 
	                React.createElement("p", null, "This value will appear as the drop down default. It should" + ' ' +
	                "give the user some indication of the values available in the" + ' ' +
	                "drop down itself, e.g., Yes/No/Maybe.")
	            ), 
	            React.createElement("ul", null, 
	                this.props.choices.map(function(choice, i) {
	                    return React.createElement("li", {key: "" + i}, 
	                        React.createElement("div", null, 
	                            React.createElement("input", {
	                                ref: "radio" + i, 
	                                type: "radio", 
	                                name: dropdownGroupName, 
	                                checked: choice.correct ? "checked" : "", 
	                                onChange: this.onCorrectChange.bind(this, i), 
	                                value: i}), 
	                            React.createElement("input", {
	                                type: "text", 
	                                ref: "editor" + i, 
	                                onChange: this.onContentChange.bind(this, i), 
	                                value: choice.content}), 
	                            React.createElement("a", {href: "#", className: "simple-button orange", 
	                                    onClick: this.removeChoice.bind(this, i)}, 
	                                React.createElement("span", {className: "icon-trash remove-choice"})
	                            )
	                        )
	                    );
	                }, this)
	            ), 

	            React.createElement("div", {className: "add-choice-container"}, 
	                React.createElement("a", {href: "#", className: "simple-button orange", 
	                        onClick: this.addChoice}, 
	                    React.createElement("span", {className: "icon-plus"}), 
	                    ' ', "Add a choice", ' '
	                )
	            )
	        );
	    },

	    onPlaceholderChange: function(e) {
	        var placeholder = e.target.value;
	        this.props.onChange({placeholder: placeholder});
	    },

	    onCorrectChange: function(choiceIndex) {
	        var choices = _.map(this.props.choices, function (choice, i) {
	            return _.extend({}, choice, {
	                correct: i === choiceIndex
	            });
	        });
	        this.props.onChange({choices: choices});
	    },

	    onContentChange: function(choiceIndex, e) {
	        var choices = this.props.choices.slice();
	        var choice = _.clone(choices[choiceIndex]);
	        choice.content = e.target.value;
	        choices[choiceIndex] = choice;
	        this.props.onChange({choices: choices});
	    },

	    addChoice: function(e) {
	        e.preventDefault();

	        var choices = this.props.choices;
	        var blankChoice = {content: "", correct: false};
	        this.props.onChange({
	            choices: choices.concat([blankChoice])
	        }, this.focus.bind(this, choices.length));
	    },

	    removeChoice: function(choiceIndex, e) {
	        e.preventDefault();
	        var choices = _(this.props.choices).clone();
	        choices.splice(choiceIndex, 1);
	        this.props.onChange({
	            choices: choices
	        });
	    },

	    focus: function(i) {
	        this.refs["editor" + i].getDOMNode().focus();
	        return true;
	    }
	});

	var propTransform = function(editorProps)  {
	    return {
	        placeholder: editorProps.placeholder,
	        choices: _.map(editorProps.choices, function(choice)  {return choice.content;})
	    };
	};

	module.exports = {
	    name: "dropdown",
	    displayName: "Drop down",
	    widget: Dropdown,
	    editor: DropdownEditor,
	    transform: propTransform
	};


/***/ },
/* 17 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * This is a simple number-entry widget
	 * It is not as powerful as number-input, but has a simpler, more
	 * representative structure as an example widget, and is easier to
	 * test new ideas on.
	 *
	 * TODO(jack): Add more comments
	 */

	var React = __webpack_require__(61);
	var Changeable = __webpack_require__(66);
	var EditorJsonify = __webpack_require__(67);
	var _ = __webpack_require__(60);

	var TextInput = React.createClass({displayName: 'TextInput',
	    render: function() {
	        return React.createElement("input", {
	            ref: "input", 
	            value: this.props.value || "", 
	            onChange: this.changeValue});
	    },

	    focus: function() {
	        this.refs.input.focus();
	        return true;
	    },

	    changeValue: function(e) {
	        // Translating from the js event e to the value
	        // of the textbox to send to onChange
	        this.props.onChange(e.target.value);
	    }
	});

	/**
	 * This is the widget's renderer. It shows up in the right column
	 * in test.html, and is what is visible to users, and where
	 * users enter their answers.
	 */
	var ExampleWidget = React.createClass({displayName: 'ExampleWidget',
	    propTypes: {
	        value: React.PropTypes.string
	    },

	    getDefaultProps: function() {
	        return {
	            value: ""
	        };
	    },

	    /**
	     * Changeable creates this.change() to tell our parent to update our props
	     */
	    mixins: [Changeable],

	    render: function() {
	        return React.createElement(TextInput, {
	            ref: "input", 
	            value: this.props.value, 
	            onChange: this.change("value")});
	    },

	    getUserInput: function() {
	        return this.props.value;
	    },

	    /**
	     * Widgets that are focusable should add a focus method that returns
	     * true if focusing succeeded. The first such widget found will be
	     * focused on page load.
	     */
	    focus: function() {
	        this.refs.input.focus();
	        return true;
	    },

	    /**
	     * simpleValidate is called for grading. Rubric is the result of calling
	     * getUserInput() on the editor that created this widget.
	     *
	     * Should return an object representing the grading result, such as
	     * {
	     *     type: "points",
	     *     earned: 1,
	     *     total: 1,
	     *     message: null
	     * }
	     */
	    simpleValidate: function(rubric) {
	        return ExampleWidget.validate(this.getUserInput(), rubric);
	    },

	    statics: {
	        /**
	         * Tell the renderer what type of `display:` style we would like
	         * for the component wrapping this one.
	         */
	        displayMode: "inline-block"
	    }
	});


	/**
	 * This is the widget's grading function
	 */
	_.extend(ExampleWidget, {
	    /**
	     * simpleValidate generally defers to this function
	     *
	     * value is usually the result of getUserInput on the widget
	     * rubric is the result of calling serialize() on the editor
	     */
	    validate: function(value, rubric) {
	        if (value === "") {
	            return {
	                type: "invalid",
	                message: "It looks like you haven't answered all of the " +
	                    "question yet."
	            };
	        } else if (value === rubric.correct) {
	            return {
	                type: "points",
	                earned: 1,
	                total: 1,
	                message: null
	            };
	        } else {
	            return {
	                type: "points",
	                earned: 0,
	                total: 1,
	                message: null
	            };
	        }
	    }
	});


	/**
	 * This is the widget's editor. This is what shows up on the left side
	 * of the screen in test.html. Only the question writer sees this.
	 */
	var ExampleWidgetEditor = React.createClass({displayName: 'ExampleWidgetEditor',
	    mixins: [Changeable, EditorJsonify],

	    getDefaultProps: function() {
	        return {
	            correct: ""
	        };
	    },

	    handleAnswerChange: function(event) {
	        this.change({
	            correct: event.target.value
	        });
	    },

	    render: function() {
	        return React.createElement("div", null, 
	            React.createElement("label", null, 
	                "Correct answer:", 
	                React.createElement("input", {
	                    value: this.props.correct, 
	                    onChange: this.handleAnswerChange, 
	                    ref: "input"})
	            )
	        );
	    },

	    focus: function() {
	        this.refs.input.focus();
	        return true;
	    }
	});


	/**
	 * For this widget to work, we must require() this file in src/all-widgets.js
	 */
	module.exports = {
	    name: "example-widget",
	    displayName: "Example Widget",
	    hidden: true,   // Hides this widget from the Perseus.Editor widget select
	    widget: ExampleWidget,
	    editor: ExampleWidgetEditor
	};


/***/ },
/* 18 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * This is an example graphie-using widget
	 *
	 * TODO(jack): Add more comments
	 */

	var React = __webpack_require__(61);
	var _ = __webpack_require__(60);

	var Util = __webpack_require__(3);
	var Changeable = __webpack_require__(66);
	var EditorJsonify = __webpack_require__(67);
	var WidgetJsonifyDeprecated = __webpack_require__(68);

	var Graphie = __webpack_require__(72);
	var MovablePoint = Graphie.MovablePoint;

	var knumber = __webpack_require__(103).number;
	var kpoint = __webpack_require__(103).point;

	/**
	 * This is the widget's renderer. It shows up in the right column
	 * in test.html, and is what is visible to users, and where
	 * users enter their answers.
	 */
	var ExampleGraphieWidget = React.createClass({displayName: 'ExampleGraphieWidget',
	    mixins: [Changeable, WidgetJsonifyDeprecated],

	    propTypes: {
	        graph: React.PropTypes.object.isRequired,
	        coord: React.PropTypes.arrayOf(React.PropTypes.number)
	    },

	    getDefaultProps: function() {
	        return {
	            // We want to allow our coord to be null to test if the
	            // user has interacted with this widget yet when grading it
	            coord: null,
	            graph: {
	                box: [400, 400],
	                labels: ["x", "y"],
	                range: [[-10, 10], [-10, 10]],
	                step: [1, 1],
	                gridStep: [1, 1],
	                valid: true,
	                backgroundImage: null,
	                markings: "grid",
	                showProtractor: false
	            }
	        };
	    },

	    render: function() {
	        return React.createElement(Graphie, {
	                ref: "graphie", 
	                box: this.props.graph.box, 
	                range: this.props.graph.range, 
	                options: this.props.graph, 
	                setup: this.setupGraphie}, 
	            React.createElement(MovablePoint, {
	                    pointSize: 5, 
	                    coord: this.props.coord || [0, 0], 
	                    constraints: [
	                        MovablePoint.constraints.snap(),
	                        MovablePoint.constraints.bound()
	                    ], 
	                    onMove: this.movePoint})
	        );
	    },

	    movePoint: function(newCoord) {
	        this.change({
	            coord: newCoord
	        });
	    },

	    _getGridConfig: function(options) {
	        return _.map(options.step, function(step, i) {
	            return Util.gridDimensionConfig(
	                    step,
	                    options.range[i],
	                    options.box[i],
	                    options.gridStep[i]);
	        });
	    },

	    setupGraphie: function(graphie, options) {
	        var gridConfig = this._getGridConfig(options);
	        graphie.graphInit({
	            range: options.range,
	            scale: _.pluck(gridConfig, "scale"),
	            axisArrows: "<->",
	            labelFormat: function(s) { return "\\small{" + s + "}"; },
	            gridStep: options.gridStep,
	            tickStep: _.pluck(gridConfig, "tickStep"),
	            labelStep: 1,
	            unityLabels: _.pluck(gridConfig, "unityLabel")
	        });
	        graphie.label([0, options.range[1][1]], options.labels[1], "above");
	    },

	    simpleValidate: function(rubric) {
	        return ExampleGraphieWidget.validate(this.getUserInput(), rubric);
	    },

	    statics: {
	        displayMode: "block"
	    }
	});


	/**
	 * This is the widget's grading function
	 */
	_.extend(ExampleGraphieWidget, {
	    validate: function(state, rubric) {
	        if (state.coord == null) {
	            return {
	                type: "invalid",
	                message: null
	            };
	        } else if (kpoint.equal(state.coord, rubric.correct)) {
	            return {
	                type: "points",
	                earned: 1,
	                total: 1,
	                message: null
	            };
	        } else {
	            return {
	                type: "points",
	                earned: 0,
	                total: 1,
	                message: null
	            };
	        }
	    }
	});


	/**
	 * This is the widget's editor. This is what shows up on the left side
	 * of the screen in test.html. Only the question writer sees this.
	 */
	var ExampleGraphieWidgetEditor = React.createClass({displayName: 'ExampleGraphieWidgetEditor',
	    mixins: [Changeable, EditorJsonify],

	    getDefaultProps: function() {
	        return {
	            correct: [4, 4],
	            graph: {
	                box: [340, 340],
	                labels: ["x", "y"],
	                range: [[-10, 10], [-10, 10]],
	                step: [1, 1],
	                gridStep: [1, 1],
	                valid: true,
	                backgroundImage: null,
	                markings: "grid",
	                showProtractor: false
	            }
	        };
	    },

	    render: function() {
	        return React.createElement("div", null, 
	            React.createElement(ExampleGraphieWidget, {
	                graph: this.props.graph, 
	                coord: this.props.correct, 
	                onChange: this.handleChange})
	        );
	    },

	    handleChange: function(newProps) {
	        if (newProps.coord) {
	            this.change({
	                correct: newProps.coord
	            });
	        }
	    }
	});


	/**
	 * For this widget to work, we must export it.
	 * We also must require() this file in src/all-widgets.js
	 */
	module.exports = {
	    name: "example-graphie-widget",
	    displayName: "Example Graphie Widget",
	    hidden: true,   // Hides this widget from the Perseus.Editor widget select
	    widget: ExampleGraphieWidget,
	    editor: ExampleGraphieWidgetEditor
	};


/***/ },
/* 19 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);
	var InfoTip = __webpack_require__(69);
	var SortableArea     = __webpack_require__(73);
	var Tooltip = __webpack_require__(74);
	var _ = __webpack_require__(60);

	var ApiOptions = __webpack_require__(12).Options;
	var Changeable = __webpack_require__(66);
	var EditorJsonify = __webpack_require__(67);
	var WidgetJsonifyDeprecated = __webpack_require__(68);
	var ApiOptions = __webpack_require__(12).Options;
	var ApiClassNames = __webpack_require__(12).ClassNames;

	var EnabledFeatures = __webpack_require__(49);
	var PropCheckBox = __webpack_require__(58);

	var InputWithExamples = __webpack_require__(75);
	var MathInput = __webpack_require__(76);
	var TeX = __webpack_require__(65); // OldExpression only
	var TexButtons = __webpack_require__(77);

	var cx = React.addons.classSet;
	var EnabledFeatures = __webpack_require__(49);

	var lens = __webpack_require__(104);

	var ERROR_MESSAGE = $._("Sorry, I don't understand that!");

	var NO_ANSWERS_WARNING = [
	    "An expression without an answer",
	    "is no expression to me.",
	    "Who can learn from an input",
	    "like the one that I see?",
	    "Put something in there",
	    "won't you please?",
	    "Just a digit will do -",
	    "might I suggest a three?"
	    ].join("\n");

	var NO_CORRECT_ANSWERS_WARNING = "This question is probably going to be too " +
	    "hard because the expression has no correct answer.";
	var SIMPLIFY_WARNING = function(str)  {
	    return ("\"" + str + "\" is required to be simplified but is not considered ") +
	        "simplified by our fancy computer algebra system. This will be " +
	        "graded as incorrect.";
	};
	var PARSE_WARNING = function(str)  {return ("\"" + str + "\" <- you sure that's math?");};
	var NOT_SPECIFIED_WARNING = function(ix)  {
	    return ("mind filling in answer " + ix + "? (the blank one)");
	};

	// The new, MathQuill input expression widget
	var Expression = React.createClass({displayName: 'Expression',
	    mixins: [Changeable],

	    propTypes: {
	        value: React.PropTypes.string,
	        times: React.PropTypes.bool,
	        functions: React.PropTypes.arrayOf(React.PropTypes.string),
	        buttonSets: TexButtons.buttonSetsType,
	        buttonsVisible: React.PropTypes.oneOf(['always', 'never', 'focused']),
	        enabledFeatures: EnabledFeatures.propTypes,
	        apiOptions: ApiOptions.propTypes
	    },

	    getDefaultProps: function() {
	        return {
	            value: "",
	            times: false,
	            functions: [],
	            buttonSets: ["basic", "trig", "prealgebra", "logarithms"],
	            onFocus: function() { },
	            onBlur: function() { },
	            enabledFeatures: EnabledFeatures.defaults,
	            apiOptions: ApiOptions.defaults
	        };
	    },

	    getInitialState: function() {
	        return {
	            showErrorTooltip: false,
	            showErrorText: false
	        };
	    },

	    parse: function(value, props) {
	        // TODO(jack): Disable icu for content creators here, or
	        // make it so that solution answers with ','s or '.'s work
	        var options = _.pick(props || this.props, "functions");
	        if (window.icu && window.icu.getDecimalFormatSymbols) {
	            _.extend(options, window.icu.getDecimalFormatSymbols());
	        }
	        return KAS.parse(value, options);
	    },

	    render: function() {
	        if (this.props.apiOptions.staticRender) {
	            // To make things slightly easier, we just use an InputWithExamples
	            // component to handle the static rendering, which is the same
	            // component used by InputNumber and NumericInput
	            return React.createElement(InputWithExamples, {
	                        ref: "input", 
	                        value: this.props.value, 
	                        type: "tex", 
	                        examples: [], 
	                        shouldShowExamples: false, 
	                        onChange: this.change("value"), 
	                        onFocus: this._handleFocus, 
	                        onBlur: this._handleBlur});
	        } else {
	            // TODO(alex): Style this tooltip to be more consistent with other
	            // tooltips on the site; align to left middle (once possible)
	            var errorTooltip = React.createElement("span", {className: "error-tooltip"}, 
	                React.createElement(Tooltip, {
	                        className: "error-text-container", 
	                        horizontalPosition: "right", 
	                        horizontalAlign: "left", 
	                        verticalPosition: "top", 
	                        arrowSize: 10, 
	                        borderColor: "#fcc335", 
	                        show: this.state.showErrorText}, 
	                    React.createElement("i", {
	                        className: "icon-exclamation-sign error-icon", 
	                        onMouseEnter: function()  {
	                            this.setState({showErrorText: true});
	                        }.bind(this), 
	                        onMouseLeave: function()  {
	                            this.setState({showErrorText: false});
	                        }.bind(this), 
	                        onClick: function()  {
	                            // TODO(alex): Better error feedback for mobile
	                            this.setState({
	                                showErrorText: !this.state.showErrorText
	                            });
	                        }.bind(this)}), 
	                    React.createElement("div", {className: "error-text"}, 
	                        ERROR_MESSAGE
	                    )
	                )
	            );

	            var className = cx({
	                "perseus-widget-expression": true,
	                "show-error-tooltip": this.state.showErrorTooltip
	            });

	            return React.createElement("span", {className: className}, 
	                React.createElement(MathInput, {
	                    ref: "input", 
	                    className: ApiClassNames.INTERACTIVE, 
	                    value: this.props.value, 
	                    onChange: this.change("value"), 
	                    convertDotToTimes: this.props.times, 
	                    buttonsVisible: this.props.buttonsVisible || "focused", 
	                    buttonSets: this.props.buttonSets, 
	                    onFocus: this._handleFocus, 
	                    onBlur: this._handleBlur}), 
	                this.state.showErrorTooltip && errorTooltip
	            );
	        }
	    },

	    _handleFocus: function() {
	        this.props.onFocus([]);
	    },

	    _handleBlur: function() {
	        this.props.onBlur([]);
	    },

	    errorTimeout: null,

	    // Whenever the input value changes, attempt to parse it.
	    //
	    // Clear any errors if this parse succeeds, show an error within a second
	    // if it fails.
	    componentWillReceiveProps: function(nextProps) {
	        if (!_.isEqual(this.props.value, nextProps.value) ||
	            !_.isEqual(this.props.functions, nextProps.functions)) {

	            clearTimeout(this.errorTimeout);

	            if (this.parse(nextProps.value, nextProps).parsed) {
	                this.setState({showErrorTooltip: false});
	            } else {
	                // Store timeout ID so that we can clear it above
	                this.errorTimeout = setTimeout(function()  {
	                    var apiResult = this.props.apiOptions.onInputError(
	                        null, // reserved for some widget identifier
	                        this.props.value,
	                        ERROR_MESSAGE
	                    );
	                    if (apiResult !== false) {
	                        this.setState({showErrorTooltip: true});
	                    }
	                }.bind(this), 2000);
	            }
	        }
	    },

	    componentWillUnmount: function() {
	        clearTimeout(this.errorTimeout);
	    },

	    focus: function() {
	        // The buttons are often on top of text you're trying to read, so don't
	        // focus the editor automatically.
	        return true;
	    },

	    focusInputPath: function(inputPath) {
	        this.refs.input.focus();
	    },

	    blurInputPath: function(inputPath) {
	        this.refs.input.blur();
	    },

	    // HACK(joel)
	    insert: function(text) {
	        if (!this.props.apiOptions.staticRender) {
	            this.refs.input.insert(text);
	        }
	    },

	    getInputPaths: function() {
	        // The widget itself is an input, so we return a single empty list to
	        // indicate this.
	        return [[]];
	    },

	    getGrammarTypeForPath: function(inputPath) {
	        return "expression";
	    },

	    setInputValue: function(path, newValue, cb) {
	        this.props.onChange({
	            value: newValue
	        }, cb);
	    },

	    getAcceptableFormatsForInputPath: function() {
	        // TODO(charlie): What format does the mobile team want this in?
	        return null;
	    },

	    getUserInput: function() {
	        return this.props.value;
	    },

	    simpleValidate: function(rubric, onInputError) {
	        onInputError = onInputError || function() { };
	        return Expression.validate(this.getUserInput(), rubric, onInputError);
	    },

	    statics: {
	        displayMode: "inline-block"
	    }
	});

	/* Content creators input a list of answers which are matched from top to
	 * bottom. The intent is that they can include spcific solutions which should
	 * be graded as correct or incorrect (or ungraded!) first, then get more
	 * general.
	 *
	 * We iterate through each answer, trying to match it with the user's input
	 * using the following angorithm:
	 * - Try to parse the user's input. If it doesn't parse then return "not
	 *   graded".
	 * - For each answer:
	 *   ~ Try to validate the user's input against the answer. The answer is
	 *     expected to parse.
	 *   ~ If the user's input validates (the validator judges it "correct"), we've
	 *     matched and can stop considering answers.
	 * - If there were no matches or the matching answer is considered "ungraded",
	 *   show the user an error. TODO(joel) - what error?
	 * - Otherwise, pass through the resulting points and message.
	 */
	_.extend(Expression, {
	    validate: function(state, rubric, onInputError) {
	        var options = _.clone(rubric);
	        if (window.icu && window.icu.getDecimalFormatSymbols) {
	            _.extend(options, window.icu.getDecimalFormatSymbols());
	        }

	        var createValidator = function(answer)  {
	            return Khan.answerTypes.expression.createValidatorFunctional(
	                // We don't give options to KAS.parse here because that is
	                // parsing the solution answer, not the student answer, and we
	                // don't want a solution to work if the student is using a
	                // different language but not in english.
	                KAS.parse(answer.value, rubric).expr,
	                _({}).extend(options, {
	                    simplify: answer.simplify,
	                    form: answer.form
	                })
	            );
	        };

	        // find the first result to match the user's input
	        var result;
	        var matchingAnswer;
	        var foundMatch = !!_(rubric.answerForms).find(function(answer)  {
	            var validate = createValidator(answer);

	            // save these because they'll be needed if this answer matches
	            result = validate(state);
	            matchingAnswer = answer;

	            // short-circuit as soon as an answer matches
	            return result.correct;
	        });

	        var message = "" || (result && result.message);

	        // now check to see whether it's considered correct, incorrect, or
	        // ungraded
	        //
	        // in the first case we fell through all the possibilities, so the
	        // answer is considered incorrect.
	        if (!foundMatch) {
	            return {
	                type: "points",
	                earned: 0,
	                total: 1
	            };

	        // we matched an ungraded answer - return "invalid"
	        } else if (matchingAnswer.considered === "ungraded") {
	            var apiResult = onInputError(
	                null, // reserved for some widget identifier
	                state,
	                message
	            );
	            return {
	                type: "invalid",
	                message: apiResult === false ? null : message
	            };

	        // The user's input matched one of the answers - is it correct or
	        // incorrect?
	        } else {

	            // TODO(eater): Seems silly to translate result to this
	            // invalid/points thing and immediately translate it back in
	            // ItemRenderer.scoreInput()
	            return {
	                type: "points",
	                earned: matchingAnswer.considered === "correct" ? 1 : 0,
	                total: 1,
	                message: message
	            };
	        }
	    }
	});

	// The old, plain-text input expression widget
	var OldExpression = React.createClass({displayName: 'OldExpression',
	    propTypes: {
	        value: React.PropTypes.string,
	        times: React.PropTypes.bool,
	        functions: React.PropTypes.arrayOf(React.PropTypes.string),
	        enabledFeatures: EnabledFeatures.propTypes
	    },

	    getDefaultProps: function() {
	        return {
	            value: "",
	            times: false,
	            functions: [],
	            onFocus: function() { },
	            onBlur: function() { },
	            enabledFeatures: EnabledFeatures.defaults,
	            apiOptions: ApiOptions.defaults
	        };
	    },

	    getInitialState: function() {
	        return {
	            lastParsedTex: ""
	        };
	    },

	    parse: function(value, props) {
	        // TODO(jack): Disable icu for content creators here, or
	        // make it so that solution answers with ','s or '.'s work
	        var options = _.pick(props || this.props, "functions");
	        if (window.icu && window.icu.getDecimalFormatSymbols) {
	            _.extend(options, window.icu.getDecimalFormatSymbols());
	        }
	        return KAS.parse(value, options);
	    },

	    componentWillMount: function() {
	        this.updateParsedTex(this.props.value);
	    },

	    componentWillReceiveProps: function(nextProps) {
	        this.updateParsedTex(nextProps.value, nextProps);
	    },

	    render: function() {
	        var result = this.parse(this.props.value);
	        var shouldShowExamples = this.props.enabledFeatures.toolTipFormats;

	        return React.createElement("span", {className: "perseus-widget-expression-old"}, 
	            React.createElement("span", {className: "output"}, 
	                React.createElement("span", {className: "tex", 
	                        style: {opacity: result.parsed ? 1.0 : 0.5}}, 
	                    React.createElement(TeX, null, this.state.lastParsedTex)
	                ), 
	                React.createElement("span", {className: "placeholder"}, 
	                    React.createElement("span", {ref: "error", className: "error", 
	                            style: {display: "none"}}, 
	                        React.createElement("span", {className: "buddy"}), 
	                        React.createElement("span", {className: "message"}, React.createElement("span", null, 
	                            ERROR_MESSAGE
	                        ))
	                    )
	                )
	            ), 
	            React.createElement(InputWithExamples, {
	                    ref: "input", 
	                    value: this.props.value, 
	                    onKeyDown: this.handleKeyDown, 
	                    onKeyPress: this.handleKeyPress, 
	                    onChange: this.handleChange, 
	                    examples: this.examples(), 
	                    shouldShowExamples: shouldShowExamples, 
	                    onFocus: this._handleFocus, 
	                    onBlur: this._handleBlur})
	        );
	    },

	    _handleFocus: function() {
	        this.props.onFocus([]);
	    },

	    _handleBlur: function() {
	        this.props.onBlur([]);
	    },

	    errorTimeout: null,

	    componentDidMount: function() {
	        this.componentDidUpdate();
	    },

	    componentDidUpdate: function() {
	        clearTimeout(this.errorTimeout);
	        if (this.parse(this.props.value).parsed) {
	            this.hideError();
	        } else {
	            this.errorTimeout = setTimeout(this.showError, 2000);
	        }
	    },

	    componentWillUnmount: function() {
	        clearTimeout(this.errorTimeout);
	    },

	    showError: function() {
	        var apiResult = this.props.apiOptions.onInputError(
	            null, // reserved for some widget identifier
	            this.props.value,
	            ERROR_MESSAGE
	        );
	        if (apiResult !== false) {
	            var $error = $(this.refs.error.getDOMNode());
	            if (!$error.is(":visible")) {
	                $error.css({ top: 50, opacity: 0.1 }).show()
	                    .animate({ top: 0, opacity: 1.0 }, 300);
	            }
	        } else {
	            this.hideError();
	        }
	    },

	    hideError: function() {
	        var $error = $(this.refs.error.getDOMNode());
	        if ($error.is(":visible")) {
	            $error.animate({ top: 50, opacity: 0.1 }, 300, function() {
	                $(this).hide();
	            });
	        }
	    },

	    /**
	     * The keydown handler handles clearing the error timeout, telling
	     * props.value to update, and intercepting the backspace key when
	     * appropriate...
	     */
	    handleKeyDown: function(event) {
	        var input = this.refs.input.getDOMNode();
	        var text = input.value;

	        var start = input.selectionStart;
	        var end = input.selectionEnd;
	        var supported = start !== undefined;

	        var which = event.nativeEvent.keyCode;

	        if (supported && which === 8 /* backspace */) {
	            if (start === end && text.slice(start - 1, start + 1) === "()") {
	                event.preventDefault();
	                var val = text.slice(0, start - 1) + text.slice(start + 1);

	                // this.props.onChange will update the value for us, but
	                // asynchronously, making it harder to set the selection
	                // usefully, so we just set .value directly here as well.
	                input.value = val;
	                input.selectionStart = start - 1;
	                input.selectionEnd = end - 1;
	                this.props.onChange({value: val});
	            }
	        }
	    },

	    /**
	     * ...whereas the keypress handler handles the parentheses because keyCode
	     * is more useful for actual character insertions (keypress gives 40 for an
	     * open paren '(' instead of keydown which gives 57, the code for '9').
	     */
	    handleKeyPress: function(event) {
	        var input = this.refs.input.getDOMNode();
	        var text = input.value;

	        var start = input.selectionStart;
	        var end = input.selectionEnd;
	        var supported = start !== undefined;

	        var which = event.nativeEvent.charCode;

	        if (supported && which === 40 /* left paren */) {
	            event.preventDefault();

	            var val;
	            if (start === end) {
	                var insertMatched = _.any([" ", ")", ""], function(val) {
	                    return text.charAt(start) === val;
	                });

	                val = text.slice(0, start) +
	                        (insertMatched ? "()" : "(") + text.slice(end);
	            } else {
	                val = text.slice(0, start) +
	                        "(" + text.slice(start, end) + ")" + text.slice(end);
	            }

	            input.value = val;
	            input.selectionStart = start + 1;
	            input.selectionEnd = end + 1;
	            this.props.onChange({value: val});

	        } else if (supported && which === 41 /* right paren */) {
	            if (start === end && text.charAt(start) === ")") {
	                event.preventDefault();
	                input.selectionStart = start + 1;
	                input.selectionEnd = end + 1;
	            }
	        }
	    },

	    handleChange: function(newValue) {
	        this.props.onChange({value: newValue});
	    },

	    focus: function() {
	        this.refs.input.focus();
	        return true;
	    },

	    getInputPaths: function() {
	        // The widget itself is an input, so we return a single empty list to
	        // indicate this.
	        return [[]];
	    },

	    getGrammarTypeForPath: function(inputPath) {
	        return "expression";
	    },

	    getUserInput: function() {
	        return this.props.value;
	    },

	    updateParsedTex: function(value, props) {
	        var result = this.parse(value, props);
	        var options = _.pick(this.props, "times");
	        if (result.parsed) {
	            this.setState({lastParsedTex: result.expr.asTex(options)});
	        }
	    },

	    simpleValidate: function(rubric, onInputError) {
	        onInputError = onInputError || function() { };
	        return Expression.validate(this.getUserInput(), rubric, onInputError);
	    },

	    examples: function() {
	        var mult = $._("For $2\\cdot2$, enter **2*2**");
	        if (this.props.times) {
	            mult = mult.replace(/\\cdot/g, "\\times");
	        }

	        return [
	            $._("**Acceptable Formats**"),
	            mult,
	            $._("For $3y$, enter **3y** or **3*y**"),
	            $._("For $\\dfrac{1}{x}$, enter **1/x**"),
	            $._("For $\\dfrac{1}{xy}$, enter **1/(xy)**"),
	            $._("For $\\dfrac{2}{x + 3}$, enter **2/(x + 3)**"),
	            $._("For $x^{y}$, enter **x^y**"),
	            $._("For $x^{2/3}$, enter **x^(2/3)**"),
	            $._("For $\\sqrt{x}$, enter **sqrt(x)**"),
	            $._("For $\\pi$, enter **pi**"),
	            $._("For $\\sin \\theta$, enter **sin(theta)**"),
	            $._("For $\\le$ or $\\ge$, enter **<=** or **>=**"),
	            $._("For $\\neq$, enter **=/=**")
	        ];
	    },

	    statics: {
	        displayMode: "block"
	    }
	});

	// An answer can be considered correct, wrong, or ungraded.
	var CONSIDERED = ["correct", "wrong", "ungraded"];

	var answerFormType = React.PropTypes.shape({
	    considered: React.PropTypes.oneOf(CONSIDERED).isRequired,
	    value: React.PropTypes.string.isRequired,
	    form: React.PropTypes.bool.isRequired,
	    simplify: React.PropTypes.bool.isRequired
	});

	var ExpressionEditor = React.createClass({displayName: 'ExpressionEditor',
	    mixins: [Changeable],

	    propTypes: {
	        answerForms: React.PropTypes.arrayOf(answerFormType),
	        times: React.PropTypes.bool,
	        buttonSets: TexButtons.buttonSetsType,
	        functions: React.PropTypes.arrayOf(React.PropTypes.string)
	    },

	    getDefaultProps: function() {
	        return {
	            answerForms: [],
	            times: false,
	            buttonSets: ["basic"],
	            functions: ["f", "g", "h"]
	        };
	    },

	    getInitialState: function() {
	        // Is the format of `value` TeX or plain text?
	        // TODO(alex): Remove after backfilling everything to TeX
	        // TODO(joel) - sucks if you edit some expression without
	        // backslashes or curly braces, then come back to the question and
	        // it's surprisingly not TeX anymore.

	        var isTex;
	        // default to TeX if new;
	        if (this.props.answerForms.length === 0) {
	            isTex = true;
	        } else {
	            isTex = _(this.props.answerForms).any(function(form)  {
	                var $__0=    form,value=$__0.value;
	                // only TeX has backslashes and curly braces
	                return _.indexOf(value, "\\") !== -1 ||
	                       _.indexOf(value, "{")  !== -1;
	            });
	        }

	        return { isTex:isTex };
	    },

	    render: function() {

	        var expression = this.state.isTex ? Expression : OldExpression;

	        var answerOptions = this.props.answerForms
	            .map(function(obj, ix)  {
	                var expressionProps = {
	                        // note we're using
	                        // *this.props*.{times,functions,buttonSets} since each
	                        // answer area has the same settings for those
	                        times: this.props.times,
	                        functions: this.props.functions,
	                        buttonSets: this.props.buttonSets,

	                        buttonsVisible: "focused",
	                        form: obj.form,
	                        simplify: obj.simplify,
	                        value: obj.value,

	                        onChange: function(props)  {return this.updateForm(ix, props);}.bind(this),
	                };

	                return lens(obj)
	                    .merge([], {
	                        draggable: true,
	                        onChange: function(props)  {return this.updateForm(ix, props);}.bind(this),
	                        onDelete: function()  {return this.handleRemoveForm(ix);}.bind(this),
	                        expressionProps: expressionProps
	                    })
	                    .freeze();
	            }.bind(this))
	            .map(function(obj)  {return React.createElement(AnswerOption, React.__spread({},  obj));});

	        var sortable = React.createElement(SortableArea, {components: answerOptions, 
	                                     onReorder: this.handleReorder, 
	                                     className: "answer-options-list"});

	        var Expr = this.state.isTex ? Expression : OldExpression;

	        // checkboxes to choose which sets of input buttons are shown
	        var buttonSetChoices = _(TexButtons.buttonSets).map(function(set, name)  {
	            // The first one gets special cased to always be checked, disabled,
	            // and float left.
	            var isFirst = name === "basic";
	            var checked = _.contains(this.props.buttonSets, name) || isFirst;
	            var className = isFirst ?
	                "button-set-label-float" :
	                "button-set-label";
	            return React.createElement("label", {className: className}, 
	                React.createElement("input", {type: "checkbox", 
	                       checked: checked, 
	                       disabled: isFirst, 
	                       onChange: function()  {return this.handleButtonSet(name);}.bind(this)}), 
	                name
	            );
	        }.bind(this));

	        buttonSetChoices.splice(1, 1, React.createElement("label", null, 
	            React.createElement("input", {type: "checkbox", 
	                   onChange: this.handleToggleDiv}), 
	            React.createElement("span", {className: "show-div-button"}, 
	                "show ", React.createElement(TeX, null, "\\div"), " button"
	            )
	        ));

	        return React.createElement("div", {className: "perseus-widget-expression-editor"}, 
	            React.createElement("h3", {className: "expression-editor-h3"}, "Global Options"), 

	            React.createElement("div", null, 
	                React.createElement(PropCheckBox, {
	                    times: this.props.times, 
	                    onChange: this.props.onChange, 
	                    labelAlignment: "right", 
	                    label: "Use × for rendering multiplication instead of a" + ' ' +
	                        "center dot."}), 
	                React.createElement(InfoTip, null, 
	                    React.createElement("p", null, "For pre-algebra problems this option displays" + ' ' +
	                    "multiplication as \\times instead of \\cdot in both the" + ' ' +
	                    "rendered output and the acceptable formats examples.")
	                )
	            ), 

	            React.createElement("div", null, 
	                React.createElement("label", null, 
	                "Function variables: ", 
	                React.createElement("input", {type: "text", 
	                    defaultValue: this.props.functions.join(" "), 
	                    onChange: this.handleFunctions})
	                ), 
	                React.createElement(InfoTip, null, React.createElement("p", null, 
	                    "Single-letter variables listed here will be" + ' ' +
	                    "interpreted as functions. This let us know that f(x) means" + ' ' +
	                    "\"f of x\" and not \"f times x\"."
	                ))
	            ), 

	            React.createElement("div", null, 
	                React.createElement("div", null, "Button sets:"), 
	                buttonSetChoices
	            ), 

	            this.state.isTex && React.createElement(TexButtons, {
	                className: "math-input-buttons", 
	                sets: this.props.buttonSets, 
	                convertDotToTimes: this.props.times, 
	                onInsert: this.handleTexInsert}), 

	            React.createElement("h3", {className: "expression-editor-h3"}, "Answers"), 

	            React.createElement("p", {style: {margin: "4px 0"}}, 
	                "student responses area matched against these from top to bottom"
	            ), 

	            sortable, 

	            React.createElement("div", null, 
	                React.createElement("button", {className: "simple-button orange", 
	                        style: {fontSize: 13}, 
	                        onClick: this.newAnswer, 
	                        type: "button"}, 
	                    "Add new answer"
	                )
	            )

	        );
	    },

	    serialize: function() {
	        var formSerializables = ["value", "form", "simplify", "considered",
	            // it's a little weird to serialize the react key, but saves some
	            // effort reconstructing them when this item is loaded later.
	            "key"];
	        var serializables = ["answerForms", "buttonSets", "functions",
	            "times"];

	        var answerForms = this.props.answerForms.map(function(form)  {
	            return _(form).pick(formSerializables);
	        });

	        return lens(this.props)
	            .set(["answerForms"], answerForms)
	            .mod([], function(props)  {return _(props).pick(serializables);})
	            .freeze();
	    },

	    getSaveWarnings: function() {
	        var issues = [];

	        if (this.props.answerForms.length === 0) {
	            issues.push(NO_ANSWERS_WARNING);
	        } else {

	            var hasCorrect = !!_(this.props.answerForms).find(function(form)  {
	                return form.considered === "correct";
	            });
	            if (!hasCorrect) {
	                issues.push(NO_CORRECT_ANSWERS_WARNING);
	            }

	            _(this.props.answerForms).each(function(form, ix)  {
	                if (this.props.value === "") {
	                    issues.push(NOT_SPECIFIED_WARNING(ix+1));
	                } else {
	                    // note we're not using icu for content creators
	                    var expression = KAS.parse(form.value);
	                    if (!expression.parsed) {
	                        issues.push(PARSE_WARNING(form.value));
	                    } else if (form.simplify &&
	                               !expression.expr.isSimplified()) {
	                        issues.push(SIMPLIFY_WARNING(form.value));
	                    }
	                }
	            }.bind(this));

	            // TODO(joel) - warn about:
	            //   - unreachable answers (how??)
	            //   - specific answers following unspecific answers
	            //   - incorrect answers as the final form
	        }

	        return issues;
	    },

	    _newEmptyAnswerForm: function() {
	        return {
	            considered: 'correct',
	            form: false,

	            // note: the key means "n-th form created" - not "form in
	            // position n" and will stay the same for the life of this form
	            key: this.props.answerForms.length,

	            simplify: false,
	            value: "",
	        };
	    },

	    newAnswer: function() {
	        var answerForms = this.props.answerForms.slice();
	        answerForms.push(this._newEmptyAnswerForm());
	        this.change({ answerForms:answerForms });
	    },

	    handleRemoveForm: function(i) {
	        var answerForms = this.props.answerForms.slice(0, -1);
	        this.change({ answerForms:answerForms });
	    },

	    // called when the options (including the expression itself) to an answer
	    // form change
	    updateForm: function(i, props) {
	        var answerForms = lens(this.props.answerForms)
	            .merge([i], props)
	            .freeze();

	        this.change({ answerForms:answerForms });
	    },

	    handleReorder: function(components) {
	        var answerForms = _(components).map(function(component)  {
	            var form = _(component.props)
	                .pick("considered", "form", "simplify", "value");
	            form.key = component.key;
	            return form;
	        });

	        this.change({ answerForms:answerForms });
	    },

	    // called when the selected buttonset changes
	    handleButtonSet: function(changingName) {
	        var buttonSetNames = _(TexButtons.buttonSets).keys();

	        // Filter to preserve order - using .union and .difference would always
	        // move the last added button set to the end.
	        var buttonSets = _(buttonSetNames).filter(function(set)  {
	            return _(this.props.buttonSets).contains(set) !==
	                   (set === changingName);
	        }.bind(this));

	        this.props.onChange({ buttonSets:buttonSets });
	    },

	    handleToggleDiv: function() {
	        // We always want buttonSets to contain exactly one of "basic" and
	        // "basic+div". Toggle between the two of them.
	        // If someone can think of a more elegant formulation of this (there
	        // must be one!) feel free to change it.
	        var keep, remove;
	        if (_(this.props.buttonSets).contains("basic+div")) {
	            keep = "basic";
	            remove = "basic+div";
	        } else {
	            keep = "basic+div";
	            remove = "basic";
	        }

	        var buttonSets = _(this.props.buttonSets)
	            .reject(function(set)  {return set === remove;})
	            .concat(keep);

	        this.change("buttonSets", buttonSets);
	    },

	    // called when the correct answer changes
	    handleTexInsert: function(str) {
	        this.refs.expression.insert(str);
	    },

	    // called when the function variables change
	    handleFunctions: function(e) {
	        var newProps = {};
	        newProps.functions = _.compact(e.target.value.split(/[ ,]+/));
	        this.props.onChange(newProps);
	    }
	});

	// Find the next element in arr after val, wrapping around to the first.
	var findNextIn = function(arr, val) {
	    var ix = _(arr).indexOf(val);
	    ix = (ix + 1) % arr.length;
	    return arr[ix];
	};

	var AnswerOption = React.createClass({displayName: 'AnswerOption',
	    mixins: [Changeable],

	    propTypes: {
	        considered: React.PropTypes.oneOf(CONSIDERED).isRequired,
	        expressionProps: React.PropTypes.object.isRequired,

	        // Must the answer have the same form as this answer.
	        form: React.PropTypes.bool.isRequired,

	        // Must the answer be simplified.
	        simplify: React.PropTypes.bool.isRequired,

	        onChange: React.PropTypes.func.isRequired,
	        onDelete: React.PropTypes.func.isRequired
	    },

	    getInitialState: function() {
	        return { deleteFocused: false };
	    },

	    handleDeleteBlur: function() {
	        this.setState({ deleteFocused: false });
	    },

	    render: function() {
	        var removeButton = null;
	        if (this.state.deleteFocused) {
	            removeButton = React.createElement("button", {type: "button", 
	                                   className: "simple-button orange", 
	                                   onClick: this.handleImSure, 
	                                   onBlur: this.handleDeleteBlur}, 
	                                "I'm sure!"
	                           );
	        } else {
	            removeButton = React.createElement("button", {type: "button", 
	                                   className: "simple-button orange", 
	                                   onClick: this.handleDelete}, 
	                                "Delete"
	                           );
	        }

	        return React.createElement("div", {className: "expression-answer-option"}, 

	            React.createElement("div", {className: "answer-handle"}), 

	            React.createElement("div", {className: "answer-body"}, 

	                React.createElement("div", {className: "answer-considered"}, 
	                    React.createElement("div", {onClick: this.toggleConsidered, 
	                         className: "answer-status " + this.props.considered}, 
	                        this.props.considered
	                    ), 

	                    React.createElement("div", {className: "answer-expression"}, 
	                        React.createElement(Expression, React.__spread({},  this.props.expressionProps))
	                    )
	                ), 

	                React.createElement("div", {className: "answer-option"}, 
	                    React.createElement(PropCheckBox, {
	                        form: this.props.form, 
	                        onChange: this.props.onChange, 
	                        labelAlignment: "right", 
	                        label: "Answer expression must have the same form."}), 
	                    React.createElement(InfoTip, null, 
	                        React.createElement("p", null, 
	                            "The student's answer must be in the same form." + ' ' +
	                            "Commutativity and excess negative signs are" + ' ' +
	                            "ignored."
	                        )
	                    )
	                ), 

	                React.createElement("div", {className: "answer-option"}, 
	                    React.createElement(PropCheckBox, {
	                        simplify: this.props.simplify, 
	                        onChange: this.props.onChange, 
	                        labelAlignment: "right", 
	                        label: "Answer expression must be fully expanded and" + ' ' +
	                            "simplified."}), 
	                    React.createElement(InfoTip, null, 
	                        React.createElement("p", null, 
	                            "The student's answer must be fully expanded and" + ' ' +
	                            "simplified. Answering this equation (x^2+2x+1) with" + ' ' +
	                            "this factored equation (x+1)^2 will render this" + ' ' +
	                            "response \"Your answer is not fully expanded and" + ' ' +
	                            "simplified.\""
	                        )
	                    )
	                ), 

	                React.createElement("div", {className: "remove-container"}, removeButton)

	            )

	        );
	    },

	    handleImSure: function() {
	        this.props.onDelete();
	    },

	    handleDelete: function() {
	        this.setState({ deleteFocused: true });
	    },

	    toggleConsidered: function() {
	        var newVal = findNextIn(CONSIDERED, this.props.considered);
	        this.change({ considered: newVal });
	    },
	});

	/*
	 * v0 props follow this schema:
	 *
	 *     times: bool
	 *     buttonSets: [string]
	 *     functions: [string]
	 *     buttonsVisible: "always" | "focused" | "never"
	 *
	 *     value: string
	 *     form: bool
	 *     simplify: bool
	 *
	 * v1 props follow this schema:
	 *
	 *     times: bool
	 *     buttonSets: [string]
	 *     functions: [string]
	 *     buttonsVisible: "always" | "focused" | "never"
	 *
	 *     answerForms: [{
	 *         considered: "correct" | "ungraded" | "incorrect"
	 *         form: bool
	 *         simplify: bool
	 *         value: string
	 *     }]
	 */
	propUpgrades = {
	    1: function(v0props)  {return {
	        times: v0props.times,
	        buttonSets: v0props.buttonSets,
	        functions: v0props.functions,
	        buttonsVisible: v0props.buttonsVisible,

	        answerForms: [{
	            considered: "correct",
	            form: v0props.form,
	            simplify: v0props.simplify,
	            value: v0props.value,
	            key: 0,
	        }]
	    };}
	};

	module.exports = {
	    name: "expression",
	    displayName: "Expression / Equation",
	    getWidget: function(enabledFeatures)  {
	        // Allow toggling between the two versions of the widget
	        return enabledFeatures.useMathQuill ? Expression : OldExpression;
	    },
	    editor: ExpressionEditor,
	    transform: function(editorProps)  {
	        return _.pick(editorProps, "times", "functions", "buttonSets",
	                      "buttonsVisible", "answerForms");
	    },
	    version: { major: 1, minor: 0 },
	    propUpgrades:propUpgrades
	};


/***/ },
/* 20 */
/***/ function(module, exports, __webpack_require__) {

	var _ = __webpack_require__(60);

	var ButtonGroup      = __webpack_require__(78);
	var GraphSettings    = __webpack_require__(79);
	var InfoTip          = __webpack_require__(69);
	var Interactive2     = __webpack_require__(80);
	var MultiButtonGroup = __webpack_require__(81);
	var SvgImage         = __webpack_require__(57);
	var Util             = __webpack_require__(3);

	/* Graphie and relevant components. */
	var Graphie      = __webpack_require__(72);
	var MovablePoint = Graphie.MovablePoint;
	var Plot         = Graphie.Plot;
	var MovableLine  = Graphie.MovableLine;

	var knumber = __webpack_require__(103).number;
	var kvector = __webpack_require__(103).vector;
	var kpoint = __webpack_require__(103).point;

	/* Mixins. */
	var Changeable   = __webpack_require__(66);

	/* Utility objects and functions. */
	var defaultBoxSize = 400;
	var defaultEditorBoxSize = 340;
	var defaultBackgroundImage = {
	    url: null,
	    scale: 1,
	    bottom: 0,
	    left: 0
	};
	var defaultType = "linear";

	function typeToButton(type) {
	    var capitalized = type.charAt(0).toUpperCase() + type.substring(1);
	    return {
	        value: type,
	        title: capitalized,
	        content: React.createElement("img", {src: functionForType(type).url, alt: capitalized})
	    };
	}

	function isFlipped(newCoord, oldCoord, line) {
	    var CCW = function(a, b, c)  {
	        return (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1]);
	    };
	    return (CCW(line[0], line[1], oldCoord) > 0) !==
	        (CCW(line[0], line[1], newCoord) > 0);
	}

	// TODO(charlie): These really need to go into a utility file as they're being
	// used by both interactive-graph and now grapher.
	function canonicalSineCoefficients(coeffs) {
	    // For a curve of the form f(x) = a * Sin(b * x - c) + d,
	    // this function ensures that a, b > 0, and c is its
	    // smallest possible positive value.
	    var amplitude = coeffs[0];
	    var angularFrequency = coeffs[1];
	    var phase = coeffs[2];
	    var verticalOffset = coeffs[3];

	    // Guarantee a > 0
	    if (amplitude < 0) {
	        amplitude *= -1;
	        angularFrequency *= -1;
	        phase *= -1;
	    }

	    var period = 2 * Math.PI;
	    // Guarantee b > 0
	    if (angularFrequency < 0) {
	        angularFrequency *= -1;
	        phase *= -1;
	        phase += period / 2;
	    }

	    // Guarantee c is smallest possible positive value
	    while (phase > 0) {
	        phase -= period;
	    }
	    while (phase < 0) {
	        phase += period;
	    }

	    return [amplitude, angularFrequency, phase, verticalOffset];
	}

	function canonicalTangentCoefficients(coeffs) {
	    // For a curve of the form f(x) = a * Tan(b * x - c) + d,
	    // this function ensures that a, b > 0, and c is its
	    // smallest possible positive value.
	    var amplitude = coeffs[0];
	    var angularFrequency = coeffs[1];
	    var phase = coeffs[2];
	    var verticalOffset = coeffs[3];

	    // Guarantee a > 0
	    if (amplitude < 0) {
	        amplitude *= -1;
	        angularFrequency *= -1;
	        phase *= -1;
	    }

	    var period = Math.PI;
	    // Guarantee b > 0
	    if (angularFrequency < 0) {
	        angularFrequency *= -1;
	        phase *= -1;
	        phase += period / 2;
	    }

	    // Guarantee c is smallest possible positive value
	    while (phase > 0) {
	        phase -= period;
	    }
	    while (phase < 0) {
	        phase += period;
	    }

	    return [amplitude, angularFrequency, phase, verticalOffset];
	}

	/* Styles */
	var typeSelectorStyle = {
	    padding: "5px 5px"
	};

	/* Graphing interface. */
	var FunctionGrapher = React.createClass({displayName: 'FunctionGrapher',
	    mixins: [Changeable],

	    _coords: function(props) {
	        // Coords are usually based on props, but should fall back to the
	        // model's default whenever they're not provided (if there's a model)
	        props = props || this.props;
	        var defaultModelCoords = props.model && props.model.defaultCoords &&
	            Grapher.pointsFromNormalized(props.graph.range, props.graph.step,
	                props.graph.snapStep, props.model.defaultCoords);
	        return props.coords || defaultModelCoords || null;
	    },

	    _asymptote: function(props) {
	        // Coords are usually based on props, but should fall back to the
	        // model's default whenever they're not provided (if there's a model)
	        props = props || this.props;
	        var defaultModelAsymptote = props.model &&
	            props.model.defaultAsymptote &&
	            Grapher.pointsFromNormalized(props.graph.range, props.graph.step,
	                props.graph.snapStep, props.model.defaultAsymptote);
	        return props.asymptote || defaultModelAsymptote || null;
	    },

	    getDefaultProps: function() {
	        return {
	            graph: {
	                range: [[-10, 10], [-10, 10]],
	                step: [1, 1]
	            },
	            coords: null,
	            asymptote: null
	        };
	    },

	    render: function() {
	        var pointForCoord = function(coord, i)  {
	            return React.createElement(MovablePoint, {
	                key: i, 
	                coord: coord, 
	                constraints: [
	                    Interactive2.MovablePoint.constraints.bound(),
	                    Interactive2.MovablePoint.constraints.snap(),
	                    function(coord)  {
	                        // Always enforce that this is a function
	                        var isFunction = _.all(this._coords(),
	                            function(otherCoord, j)  {
	                                return i === j  || !otherCoord ||
	                                    !knumber.equal(coord[0], otherCoord[0]);
	                            });

	                        // Evaluate this criteria before per-point constraints
	                        if (!isFunction) {
	                            return false;
	                        }

	                        // Specific functions have extra per-point constraints
	                        if (this.props.model &&
	                                this.props.model.extraCoordConstraint) {
	                            var extraConstraint =
	                                this.props.model.extraCoordConstraint;
	                            // Calculat resulting coords and verify that
	                            // they're valid for this graph
	                            var proposedCoords = _.clone(this._coords());
	                            var oldCoord = _.clone(proposedCoords[i]);
	                            proposedCoords[i] = coord;
	                            return extraConstraint(coord, oldCoord,
	                                proposedCoords, this._asymptote(),
	                                this.props.graph);
	                        }

	                        return isFunction;
	                    }.bind(this)
	                ], 
	                onMove: function(newCoord, oldCoord)  {
	                    var coords;
	                    // Reflect over asymptote, if allowed
	                    var asymptote = this._asymptote();
	                    if (asymptote &&
	                            this.props.model.allowReflectOverAsymptote &&
	                            isFlipped(newCoord, oldCoord, asymptote)) {
	                        coords = _.map(this._coords(), function(coord)  {
	                            return kpoint.reflectOverLine(coord, asymptote);
	                        });
	                    } else {
	                        coords = _.clone(this._coords());
	                    }
	                    coords[i] = newCoord;
	                    this.props.onChange({
	                        coords: coords
	                    });
	                }.bind(this)});
	        }.bind(this);
	        var points = _.map(this._coords(), pointForCoord);
	        var box = this.props.graph.box;

	        var imageDescription = this.props.graph.backgroundImage;
	        var image = null;
	        if (imageDescription.url) {
	            var preScale = box[0] / defaultBoxSize;
	            var scale = imageDescription.scale * preScale;
	            image = React.createElement(SvgImage, {src: imageDescription.url, 
	                              left: preScale * imageDescription.left, 
	                              bottom: preScale * imageDescription.bottom, 
	                              width: imageDescription.width, 
	                              height: imageDescription.height, 
	                              scale: scale});
	        }

	        return React.createElement("div", {
	                    className: "perseus-widget " + "perseus-widget-grapher", 
	                    style: {
	                        width: box[0],
	                        height: this.props.flexibleType ? "auto" : box[1]
	                    }}, 
	                React.createElement("div", {
	                    className: "graphie-container above-scratchpad", 
	                    style: {
	                        width: box[0],
	                        height: box[1]
	                    }}, 
	                image, 
	                React.createElement(Graphie, React.__spread({},  this.props.graph), 
	                    this.props.model && this.renderPlot(), 
	                    this.props.model && this.renderAsymptote(), 
	                    this.props.model && points
	                )
	            )
	        );
	    },

	    renderPlot: function() {
	        var model = this.props.model;
	        var xRange = this.props.graph.range[0];
	        var style = { stroke: KhanUtil.DYNAMIC };

	        var coeffs = model.getCoefficients(this._coords(), this._asymptote());
	        if (!coeffs) {
	            return;
	        }

	        var functionProps = model.getPropsForCoeffs(coeffs, xRange);
	        return React.createElement(model.Movable, React.__spread({}, 
	                    functionProps, 
	                    {key: this.props.model.url, 
	                    range: xRange, 
	                    style: style}));
	    },

	    renderAsymptote: function() {
	        var model = this.props.model;
	        var graph = this.props.graph;
	        var asymptote = this._asymptote();
	        var dashed = {
	            strokeDasharray: "- "
	        };
	        return asymptote &&
	            React.createElement(MovableLine, {onMove: function(newCoord, oldCoord)  {
	                // Calculate and apply displacement
	                var delta = kvector.subtract(newCoord, oldCoord);
	                var newAsymptote = _.map(this._asymptote(), function(coord) 
	                    {return kvector.add(coord, delta);});
	                this.props.onChange({
	                    asymptote: newAsymptote
	                });
	            }.bind(this), constraints: [
	                Interactive2.MovableLine.constraints.bound(),
	                Interactive2.MovableLine.constraints.snap(),
	                function(newCoord, oldCoord)  {
	                    // Calculate and apply proposed displacement
	                    var delta = kvector.subtract(newCoord, oldCoord);
	                    var proposedAsymptote = _.map(this._asymptote(), function(coord) 
	                        {return kvector.add(coord, delta);});
	                    // Verify that resulting asymptote is valid for graph
	                    if (model.extraAsymptoteConstraint) {
	                        return model.extraAsymptoteConstraint(newCoord,
	                            oldCoord, this._coords(), proposedAsymptote,
	                            graph);
	                    }
	                    return true;
	            }.bind(this)], normalStyle: dashed, 
	                highlightStyle: dashed}, 
	                _.map(asymptote, function(coord) 
	                    {return React.createElement(MovablePoint, {coord: coord, 
	                        static: true, 
	                        draw: null, 
	                        extendLine: true});}
	                )
	        );
	    }
	});

	var PlotDefaults = {
	    areEqual: function(coeffs1, coeffs2) {
	        return Util.deepEq(coeffs1, coeffs2);
	    },

	    Movable: Plot,

	    getPropsForCoeffs: function(coeffs) {
	        return {
	            fn: _.partial(this.getFunctionForCoeffs, coeffs)
	        };
	    }
	};

	var Linear = _.extend({}, PlotDefaults, {
	    url: "https://ka-perseus-graphie.s3.amazonaws.com/67aaf581e6d9ef9038c10558a1f70ac21c11c9f8.png",

	    defaultCoords: [[0.25, 0.75], [0.75, 0.75]],

	    getCoefficients: function(coords) {
	        var p1 = coords[0];
	        var p2 = coords[1];

	        var denom = p2[0] - p1[0];
	        var num = p2[1] - p1[1];

	        if (denom === 0) {
	            return;
	        }

	        var m = num / denom;
	        var b = p2[1] - m * p2[0];
	        return [m, b];
	    },

	    getFunctionForCoeffs: function(coeffs, x) {
	        var m = coeffs[0], b = coeffs[1];
	        return m * x + b;
	    },

	    getEquationString: function(coords) {
	        var coeffs = this.getCoefficients(coords);
	        var m = coeffs[0], b = coeffs[1];
	        return "y = " + m.toFixed(3) + "x + " + b.toFixed(3);
	    }
	});

	var Quadratic = _.extend({}, PlotDefaults, {
	    url: "https://ka-perseus-graphie.s3.amazonaws.com/e23d36e6fc29ee37174e92c9daba2a66677128ab.png",

	    defaultCoords: [[0.5, 0.5], [0.75, 0.75]],

	    Movable: Graphie.Parabola,

	    getCoefficients: function(coords) {
	        var p1 = coords[0];
	        var p2 = coords[1];

	        // Parabola with vertex (h, k) has form: y = a * (h - k)^2 + k
	        var h = p1[0];
	        var k = p1[1];

	        // Use these to calculate familiar a, b, c
	        var a = (p2[1] - k) / ((p2[0] - h) * (p2[0] - h));
	        var b = - 2 * h * a;
	        var c = a * h * h + k;

	        return [a, b, c];
	    },

	    getFunctionForCoeffs: function(coeffs, x) {
	        var a = coeffs[0], b = coeffs[1], c = coeffs[2];
	        return (a * x + b) * x + c;
	    },

	    getPropsForCoeffs: function(coeffs) {
	        return {
	            a: coeffs[0],
	            b: coeffs[1],
	            c: coeffs[2]
	        };
	    },

	    getEquationString: function(coords) {
	        var coeffs = this.getCoefficients(coords);
	        var a = coeffs[0], b = coeffs[1], c = coeffs[2];
	        return "y = " + a.toFixed(3) + "x^2 + " + b.toFixed(3) +
	               "x + " + c.toFixed(3);
	    }
	});

	var Sinusoid = _.extend({}, PlotDefaults, {
	    url: "https://ka-perseus-graphie.s3.amazonaws.com/3d68e7718498475f53b206c2ab285626baf8857e.png",

	    defaultCoords: [[0.5, 0.5], [0.6, 0.6]],

	    Movable: Graphie.Sinusoid,

	    getCoefficients: function(coords) {
	        var p1 = coords[0];
	        var p2 = coords[1];

	        var a = (p2[1] - p1[1]);
	        var b = Math.PI / (2 * (p2[0] - p1[0]));
	        var c = p1[0] * b;
	        var d = p1[1];

	        return [a, b, c, d];
	    },

	    getFunctionForCoeffs: function(coeffs, x) {
	        var a = coeffs[0], b = coeffs[1], c = coeffs[2], d = coeffs[3];
	        return a * Math.sin(b * x - c) + d;
	    },

	    getPropsForCoeffs: function(coeffs) {
	        return {
	            a: coeffs[0],
	            b: coeffs[1],
	            c: coeffs[2],
	            d: coeffs[3]
	        };
	    },

	    getEquationString: function(coords) {
	        var coeffs = this.getCoefficients(coords);
	        var a = coeffs[0], b = coeffs[1], c = coeffs[2], d = coeffs[3];
	        return "y = " + a.toFixed(3) + " sin(" + b.toFixed(3) +
	               "x - " + c.toFixed(3) + ") + " + d.toFixed(3);
	    },

	    areEqual: function(coeffs1, coeffs2) {
	        return Util.deepEq(canonicalSineCoefficients(coeffs1),
	                canonicalSineCoefficients(coeffs2));
	    }
	});

	var Tangent = _.extend({}, PlotDefaults, {
	    url: "https://ka-perseus-graphie.s3.amazonaws.com/7db80d23c35214f98659fe1cf0765811c1bbfbba.png",

	    defaultCoords: [[0.5, 0.5], [0.75, 0.75]],

	    getCoefficients: function(coords) {
	        var p1 = coords[0];
	        var p2 = coords[1];

	        var a = (p2[1] - p1[1]);
	        var b = Math.PI / (4 * (p2[0] - p1[0]));
	        var c = p1[0] * b;
	        var d = p1[1];

	        return [a, b, c, d];
	    },

	    getFunctionForCoeffs: function(coeffs, x) {
	        var a = coeffs[0], b = coeffs[1], c = coeffs[2], d = coeffs[3];
	        return a * Math.tan(b * x - c) + d;
	    },

	    getEquationString: function(coords) {
	        var coeffs = this.getCoefficients(coords);
	        var a = coeffs[0], b = coeffs[1], c = coeffs[2], d = coeffs[3];
	        return "y = " + a.toFixed(3) + " sin(" + b.toFixed(3) +
	               "x - " + c.toFixed(3) + ") + " + d.toFixed(3);
	    },

	    areEqual: function(coeffs1, coeffs2) {
	        return Util.deepEq(canonicalTangentCoefficients(coeffs1),
	                canonicalTangentCoefficients(coeffs2));
	    }
	});

	var Exponential = _.extend({}, PlotDefaults, {
	    url: "https://ka-perseus-graphie.s3.amazonaws.com/9cbfad55525e3ce755a31a631b074670a5dad611.png",

	    defaultCoords: [[0.5, 0.55], [0.75, 0.75]],

	    defaultAsymptote: [[0, 0.5], [1.0, 0.5]],

	    /**
	     * Add extra constraints for movement of the points or asymptote (below):
	     *   newCoord: [x, y]
	     *     The end position of the point or asymptote endpoint
	     *   oldCoord: [x, y]
	     *     The old position of the point or asymptote endpoint
	     *   coords:
	     *     An array of coordinates representing the proposed end configuration
	     *     of the plot coordinates.
	     *   asymptote:
	     *     An array of coordinates representing the proposed end configuration
	     *     of the asymptote.
	     *
	     * Return: either a coordinate (to be used as the resulting coordinate of
	     * the move) or a boolean, where `true` uses newCoord as the resulting
	     * coordinate, and `false` uses oldCoord as the resulting coordinate.
	     */
	    extraCoordConstraint: function(newCoord, oldCoord, coords, asymptote,
	            graph) {
	        var y = _.head(asymptote)[1];
	        return _.all(coords, function(coord)  {return coord[1] !== y;});
	    },

	    extraAsymptoteConstraint: function(newCoord, oldCoord, coords, asymptote,
	            graph) {
	        var y = newCoord[1];
	        var isValid = _.all(coords, function(coord)  {return coord[1] > y;}) ||
	            _.all(coords, function(coord)  {return coord[1] < y;});

	        if (isValid) {
	            return [oldCoord[0], y];
	        } else {
	            // Snap the asymptote as close as possible, i.e., if the user moves
	            // the mouse really quickly into an invalid region
	            var oldY = oldCoord[1];
	            var wasBelow = _.all(coords, function(coord)  {return coord[1] > oldY;});
	            if (wasBelow) {
	                var bottomMost = _.min(_.map(coords, function(coord)  {return coord[1];}));
	                return [oldCoord[0], bottomMost - graph.snapStep[1]];
	            } else {
	                var topMost = _.max(_.map(coords, function(coord)  {return coord[1];}));
	                return [oldCoord[0], topMost + graph.snapStep[1]];
	            }
	        }
	    },

	    allowReflectOverAsymptote: true,

	    getCoefficients: function(coords, asymptote) {
	        var p1 = coords[0];
	        var p2 = coords[1];

	        var c = _.head(asymptote)[1];
	        var b = Math.log((p1[1] - c) / (p2[1] - c)) / (p1[0] - p2[0]);
	        var a = (p1[1] - c) / Math.exp(b * p1[0]);
	        return [a, b, c];
	    },

	    getFunctionForCoeffs: function(coeffs, x) {
	        var a = coeffs[0], b = coeffs[1], c = coeffs[2];
	        return a * Math.exp(b * x) + c;
	    },

	    getEquationString: function(coords, asymptote) {
	        if (!asymptote) {
	            return null;
	        }
	        var coeffs = this.getCoefficients(coords, asymptote);
	        var a = coeffs[0], b = coeffs[1], c = coeffs[2];
	        return "y = " + a.toFixed(3) + "e^(" + b.toFixed(3) + "x) + " +
	            c.toFixed(3);
	    }
	});

	var Logarithm = _.extend({}, PlotDefaults, {
	    url: "https://ka-perseus-graphie.s3.amazonaws.com/f6491e99d34af34d924bfe0231728ad912068dc3.png",

	    defaultCoords: [[0.55, 0.5], [0.75, 0.75]],

	    defaultAsymptote: [[0.5, 0], [0.5, 1.0]],

	    extraCoordConstraint: function(newCoord, oldCoord, coords, asymptote,
	            graph) {
	        var x = _.head(asymptote)[0];
	        return _.all(coords, function(coord)  {return coord[0] !== x;}) &&
	            coords[0][1] !== coords[1][1];
	    },

	    extraAsymptoteConstraint: function(newCoord, oldCoord, coords, asymptote,
	            graph) {
	        var x = newCoord[0];
	        var isValid = _.all(coords, function(coord)  {return coord[0] > x;}) ||
	            _.all(coords, function(coord)  {return coord[0] < x;});

	        if (isValid) {
	            return [x, oldCoord[1]];
	        } else {
	            // Snap the asymptote as close as possible, i.e., if the user moves
	            // the mouse really quickly into an invalid region
	            var oldX = oldCoord[0];
	            var wasLeft = _.all(coords, function(coord)  {return coord[0] > oldX;});
	            if (wasLeft) {
	                var leftMost = _.min(_.map(coords, function(coord)  {return coord[0];}));
	                return [leftMost - graph.snapStep[0], oldCoord[1]];
	            } else {
	                var rightMost = _.max(_.map(coords, function(coord)  {return coord[0];}));
	                return [rightMost + graph.snapStep[0], oldCoord[1]];
	            }
	        }
	    },

	    allowReflectOverAsymptote: true,

	    getCoefficients: function(coords, asymptote) {
	        // It's easiest to calculate the logarithm's coefficients by thinking
	        // about it as the inverse of the exponential, so we flip x and y and
	        // perform some algebra on the coefficients. This also unifies the
	        // logic between the two 'models'.
	        var flip = function(coord)  {return [coord[1], coord[0]];};
	        var inverseCoeffs = Exponential.getCoefficients(_.map(coords, flip),
	            _.map(asymptote, flip));
	        var c = - inverseCoeffs[2] / inverseCoeffs[0];
	        var b = 1 / inverseCoeffs[0];
	        var a = 1 / inverseCoeffs[1];
	        return [a, b, c];
	    },

	    getFunctionForCoeffs: function(coeffs, x, asymptote) {
	        var a = coeffs[0], b = coeffs[1], c = coeffs[2];
	        return a * Math.log(b * x + c);
	    },

	    getEquationString: function(coords, asymptote) {
	        if (!asymptote) {
	            return null;
	        }
	        var coeffs = this.getCoefficients(coords, asymptote);
	        var a = coeffs[0], b = coeffs[1], c = coeffs[2];
	        return "y = ln(" + a.toFixed(3) + "x + " + b.toFixed(3) + ") + " +
	            c.toFixed(3);
	    }
	});

	var AbsoluteValue = _.extend({}, PlotDefaults, {
	    url: "https://ka-perseus-graphie.s3.amazonaws.com/8256a630175a0cb1d11de223d6de0266daf98721.png",

	    defaultCoords: [[0.5, 0.5], [0.75, 0.75]],

	    getCoefficients: function(coords) {
	        var p1 = coords[0];
	        var p2 = coords[1];

	        var denom = p2[0] - p1[0];
	        var num = p2[1] - p1[1];

	        if (denom === 0) {
	            return;
	        }

	        var m = Math.abs(num / denom);
	        if (p2[1] < p1[1]) {
	            m *= -1;
	        }
	        var horizontalOffset = p1[0];
	        var verticalOffset = p1[1];

	        return [m, horizontalOffset, verticalOffset];
	    },

	    getFunctionForCoeffs: function(coeffs, x) {
	        var m = coeffs[0],
	            horizontalOffset = coeffs[1],
	            verticalOffset = coeffs[2];
	        return m * Math.abs(x - horizontalOffset) + verticalOffset;
	    },

	    getEquationString: function(coords) {
	        var coeffs = this.getCoefficients(coords);
	        var m = coeffs[0],
	            horizontalOffset = coeffs[1],
	            verticalOffset = coeffs[2];
	        return "y = " + m.toFixed(3) + "| x - " +
	            horizontalOffset.toFixed(3) + "| + " +
	            verticalOffset.toFixed(3);
	    }
	});

	/* Utility functions for dealing with graphing interfaces. */
	var functionTypeMapping = {
	    "linear": Linear,
	    "quadratic": Quadratic,
	    "sinusoid": Sinusoid,
	    "tangent": Tangent,
	    "exponential": Exponential,
	    "logarithm": Logarithm,
	    "absolute_value": AbsoluteValue
	};

	var allTypes = _.keys(functionTypeMapping);

	function functionForType(type) {
	    return functionTypeMapping[type];
	}

	/* Widget and editor. */
	var Grapher = React.createClass({displayName: 'Grapher',
	    getDefaultProps: function() {
	        return {
	            plot: {
	                type: defaultType,
	                coords: null,
	                asymptote: null
	            },
	            graph: {
	                box: [defaultBoxSize, defaultBoxSize],
	                labels: ["x", "y"],
	                range: [[-10, 10], [-10, 10]],
	                step: [1, 1],
	                backgroundImage: defaultBackgroundImage,
	                markings: "graph",
	                rulerLabel: "",
	                rulerTicks: 10,
	                valid: true
	            },
	            availableTypes: [defaultType],
	        };
	    },

	    render: function() {
	        var type = this.props.plot.type;
	        var coords = this.props.plot.coords;
	        var asymptote = this.props.plot.asymptote;

	        var typeSelector = React.createElement("div", {style: typeSelectorStyle, 
	                className: "above-scratchpad"}, 
	            React.createElement(ButtonGroup, {
	                value: type, 
	                allowEmpty: true, 
	                buttons: _.map(this.props.availableTypes, typeToButton), 
	                onChange: this.handleActiveTypeChange})
	        );

	        var box = this.props.graph.box;

	        // Calculate additional graph properties so that the same values are
	        // passed in to both FunctionGrapher and Graphie.
	        var options = _.extend({}, this.props.graph,
	            this._getGridAndSnapSteps(this.props.graph));
	        _.extend(options, {
	            gridConfig: this._getGridConfig(options)
	        });

	        // The `graph` prop will eventually be passed to the <Graphie>
	        // component. In fact, if model is `null`, this is functionalliy
	        // identical to a <Graphie>. Otherwise, some points and a plot will be
	        // overlayed.
	        var grapherProps = {
	            graph: {
	                box: box,
	                range: options.range,
	                step: options.step,
	                snapStep: options.snapStep,
	                backgroundImage: options.backgroundImage,
	                options: options,
	                setup: this._setupGraphie
	            },
	            onChange: this.handlePlotChanges,
	            model: type && functionForType(type),
	            coords: coords,
	            asymptote: asymptote
	        };

	        return React.createElement("div", null, 
	            React.createElement(FunctionGrapher, React.__spread({},  grapherProps)), 
	            this.props.availableTypes.length > 1 && typeSelector
	        );
	    },

	    handlePlotChanges: function(newPlot) {
	        var plot = _.extend({}, this.props.plot, newPlot);
	        this.props.onChange({
	            plot: plot
	        });
	    },

	    handleActiveTypeChange: function(newType) {
	        var plot = _.extend({}, this.props.plot, {
	            type: newType,
	            coords: null,
	            asymptote: null
	        });
	        this.props.onChange({
	            plot: plot
	        });
	    },

	    _getGridAndSnapSteps: function(options) {
	        var gridStep = options.gridStep ||
	                Util.getGridStep(options.range, options.step, defaultBoxSize);
	        var snapStep = options.snapStep ||
	                Util.snapStepFromGridStep(gridStep);
	        return {
	            gridStep: gridStep,
	            snapStep: snapStep
	        };
	    },

	    _getGridConfig: function(options) {
	        return _.map(options.step, function(step, i) {
	            return Util.gridDimensionConfig(
	                    step,
	                    options.range[i],
	                    options.box[i],
	                    options.gridStep[i]);
	        });
	    },

	    _setupGraphie: function(graphie, options) {
	        if (options.markings === "graph") {
	            graphie.graphInit({
	                range: options.range,
	                scale: _.pluck(options.gridConfig, "scale"),
	                axisArrows: "<->",
	                labelFormat: function(s) { return "\\small{" + s + "}"; },
	                gridStep: options.gridStep,
	                snapStep: options.snapStep,
	                tickStep: _.pluck(options.gridConfig, "tickStep"),
	                labelStep: 1,
	                unityLabels: _.pluck(options.gridConfig, "unityLabel")
	            });
	            graphie.label([0, options.range[1][1]], options.labels[1],
	                "above");
	            graphie.label([options.range[0][1], 0], options.labels[0],
	                "right");
	        } else if (options.markings === "grid") {
	            graphie.graphInit({
	                range: options.range,
	                scale: _.pluck(options.gridConfig, "scale"),
	                gridStep: options.gridStep,
	                axes: false,
	                ticks: false,
	                labels: false
	            });
	        } else if (options.markings === "none") {
	            graphie.init({
	                range: options.range,
	                scale: _.pluck(options.gridConfig, "scale")
	            });
	        }
	    },

	    simpleValidate: function(rubric) {
	        return Grapher.validate(this.getUserInput(), rubric);
	    },

	    getUserInput: function() {
	        return this.props.plot;
	    },

	    focus: $.noop,

	    statics: {
	        displayMode: "block"
	    }
	});

	_.extend(Grapher, {
	    validate: function(state, rubric) {
	        if (state.type !== rubric.correct.type) {
	            return {
	                type: "points",
	                earned: 0,
	                total: 1,
	                message: null
	            };
	        }

	        // We haven't moved the coords
	        if (state.coords == null) {
	            return {
	                type: "invalid",
	                message: null
	            };
	        }

	        // Get new function handler for grading
	        var grader = functionForType(state.type);
	        var guessCoeffs = grader.getCoefficients(state.coords,
	            state.asymptote);
	        var correctCoeffs = grader.getCoefficients(rubric.correct.coords,
	            rubric.correct.asymptote);

	        if (guessCoeffs == null || correctCoeffs == null) {
	            return {
	                type: "invalid",
	                message: null
	            };
	        }
	        else if (grader.areEqual(guessCoeffs, correctCoeffs)) {
	            return {
	                type: "points",
	                earned: 1,
	                total: 1,
	                message: null
	            };
	        } else {
	            return {
	                type: "points",
	                earned: 0,
	                total: 1,
	                message: null
	            };
	        }
	    },

	    getEquationString: function(props) {
	        var plot = props.plot;
	        if (plot.type && plot.coords) {
	            var handler = functionForType(plot.type);
	            var result =
	                handler.getEquationString(plot.coords, plot.asymptote);
	            return result || "";
	        } else {
	            return "";
	        }
	    },

	    pointsFromNormalized: function(range, step, snapStep, coordsList) {
	        var numSteps = function(range, step) {
	            return Math.floor((range[1] - range[0]) / step);
	        };

	        return _.map(coordsList, function(coords) {
	            var unsnappedPoint = _.map(coords, function(coord, i) {
	                var currRange = range[i];
	                var currStep = step[i];
	                var nSteps = numSteps(currRange, currStep);
	                var tick = Math.round(coord * nSteps);
	                return currRange[0] + currStep * tick;
	            });
	            // In some graphing widgets, e.g. interactive-graph, you can rely
	            // on the Graphie to handle snapping. Here, we need the points
	            // returned to already be snapped so that the plot that goes
	            // through them is correct.
	            return kpoint.roundTo(unsnappedPoint, snapStep);
	        });
	    }
	});

	var GrapherEditor = React.createClass({displayName: 'GrapherEditor',
	    mixins: [Changeable],

	    getDefaultProps: function() {
	        return {
	            correct: {
	                type: defaultType,
	                coords: null,
	                asymptote: null
	            },
	            graph: {
	                labels: ["x", "y"],
	                range: [[-10, 10], [-10, 10]],
	                step: [1, 1],
	                backgroundImage: defaultBackgroundImage,
	                markings: "graph",
	                rulerLabel: "",
	                rulerTicks: 10,
	                valid: true
	            },
	            availableTypes: [defaultType]
	        };
	    },

	    render: function() {
	        var graph;
	        var equationString;
	        var graph = _.extend(this.props.graph, {
	            box: [defaultEditorBoxSize, defaultEditorBoxSize]
	        });

	        if (this.props.graph.valid === true) {
	            var graphProps = {
	                graph: this.props.graph,
	                plot: this.props.correct,
	                availableTypes: this.props.availableTypes,
	                onChange: function(newProps, cb)  {
	                    var correct = this.props.correct;
	                    if (correct.type === newProps.plot.type) {
	                        correct = _.extend({}, correct, newProps.plot);
	                    } else {
	                        // Clear options from previous graph
	                        correct = newProps.plot;
	                    }
	                    this.props.onChange({correct: correct}, cb);
	                }.bind(this)
	            };

	            graph = React.createElement(Grapher, React.__spread({},  graphProps));
	            equationString = Grapher.getEquationString(graphProps);
	        } else {
	            graph = React.createElement("div", {className: "perseus-error"}, 
	                        this.props.graph.valid
	                    );
	        }

	        return React.createElement("div", null, 
	            React.createElement("div", null, "Correct answer", ' ', 
	                React.createElement(InfoTip, null, 
	                    React.createElement("p", null, "Graph the correct answer in the graph below and ensure" + ' ' +
	                    "the equation or point coordinates displayed represent the" + ' ' +
	                    "correct answer.")
	                ), 
	                ' ', ": ", equationString), 

	            React.createElement(GraphSettings, {
	                editableSettings: ["graph", "snap", "image"], 
	                box: this.props.graph.box, 
	                range: this.props.graph.range, 
	                labels: this.props.graph.labels, 
	                step: this.props.graph.step, 
	                gridStep: this.props.graph.gridStep, 
	                snapStep: this.props.graph.snapStep, 
	                valid: this.props.graph.valid, 
	                backgroundImage: this.props.graph.backgroundImage, 
	                markings: this.props.graph.markings, 
	                rulerLabel: this.props.graph.rulerLabel, 
	                rulerTicks: this.props.graph.rulerTicks, 
	                onChange: this.change("graph")}), 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                React.createElement("label", null, "Available functions:", ' ', " "), 
	                React.createElement(MultiButtonGroup, {
	                    allowEmpty: false, 
	                    values: this.props.availableTypes, 
	                    buttons: _.map(allTypes, typeToButton), 
	                    onChange: this.handleAvailableTypesChange})
	            ), 
	            graph
	        );
	    },

	    handleAvailableTypesChange: function(newAvailableTypes) {
	        var correct = this.props.correct;

	        // If the currently 'correct' type is removed from the list of types,
	        // we need to change it to avoid impossible questions.
	        if (!_.contains(newAvailableTypes, this.props.correct.type)) {
	            var correct = {
	                type: _.first(newAvailableTypes),
	                coords: null,
	                asymptote: null
	            };
	        }
	        this.props.onChange({
	            availableTypes: newAvailableTypes,
	            correct: correct
	        });
	    },

	    serialize: function() {
	        return _.chain(this.props)
	                .pick("correct", "availableTypes")
	                .extend({ graph: _.omit(this.props.graph, "box") })
	                .value();
	    }
	});

	var propTransform = function(editorProps)  {
	    var widgetProps = _.pick(editorProps, "availableTypes");
	    widgetProps.graph = _.extend(editorProps.graph, {
	        box: [defaultBoxSize, defaultBoxSize]
	    });

	    // If there's only one type, the graph type is deterministic
	    if (widgetProps.availableTypes.length === 1) {
	        var plot = {
	            type: _.first(widgetProps.availableTypes),
	            coords: null,
	            asymptote: null
	        };
	        widgetProps.plot = plot;
	    }

	    return widgetProps;
	};

	module.exports = {
	    name: "grapher",
	    displayName: "Grapher",
	    widget: Grapher,
	    editor: GrapherEditor,
	    transform: propTransform
	};


/***/ },
/* 21 */
/***/ function(module, exports, __webpack_require__) {

	var ApiOptions = __webpack_require__(12).Options;
	var Changeable   = __webpack_require__(66);
	var Editor = __webpack_require__(7);
	var Renderer = __webpack_require__(10);

	var Group = React.createClass({displayName: 'Group',
	    mixins: [Changeable],

	    propTypes: {
	        content: React.PropTypes.string,
	        widgets: React.PropTypes.object,
	        images: React.PropTypes.object,
	        icon: React.PropTypes.object,
	        reviewModeRubric: React.PropTypes.object,
	    },

	    getDefaultProps: function() {
	        return {
	            content: "",
	            widgets: {},
	            images: {},
	            icon: null
	        };
	    },

	    render: function() {
	        var apiOptions = _.extend(
	            {},
	            ApiOptions.defaults,
	            this.props.apiOptions,
	            {
	                // Api Rewriting to support correct onFocus/onBlur
	                // events for the mobile API
	                onFocusChange: function(newFocus, oldFocus)  {
	                    if (oldFocus) {
	                        this.props.onBlur(oldFocus);
	                    }
	                    if (newFocus) {
	                        this.props.onFocus(newFocus);
	                    }
	                }.bind(this)
	            }
	        );

	        // Allow a problem number annotation to be added.
	        var number = _.indexOf(this.props.interWidgets("group"), this);
	        var problemNumComponent = this.props.apiOptions.groupAnnotator(
	            number, this.props.widgetId);

	        // This is a little strange because the id of the widget that actually
	        // changed is going to be lost in favor of the group widget's id. The
	        // widgets prop also wasn't actually changed, and this only serves to
	        // alert our renderer (our parent) of the fact that some interaction
	        // has occurred.
	        var onInteractWithWidget = function(id)  {
	            if (this.refs.renderer) {
	                this.change("widgets", this.refs.renderer.props.widgets);
	            }
	        }.bind(this);

	        return React.createElement("div", {className: "perseus-group"}, 
	            problemNumComponent, 
	            React.createElement(Renderer, React.__spread({}, 
	                this.props, 
	                {ref: "renderer", 
	                apiOptions: apiOptions, 
	                interWidgets: this._interWidgets, 
	                reviewMode: !!this.props.reviewModeRubric, 
	                onInteractWithWidget: onInteractWithWidget})), 
	            this.props.icon && React.createElement("div", {className: "group-icon"}, 
	                this.props.icon
	            )
	        );
	    },

	    _interWidgets: function(filterCriterion, localResults) {
	        if (localResults.length) {
	            return localResults;
	        } else {
	            return this.props.interWidgets(filterCriterion);
	        }
	    },

	    getUserInput: function() {
	        return this.refs.renderer.getUserInput();
	    },

	    getSerializedState: function() {
	        return this.refs.renderer.getSerializedState();
	    },

	    restoreSerializedState: function(state, callback) {
	        this.refs.renderer.restoreSerializedState(state, callback);
	        // Tell our renderer that we have no props to change
	        // (all our changes were in state):
	        return null;
	    },

	    simpleValidate: function(rubric) {
	        return this.refs.renderer.score();
	    },

	    // Mobile API:
	    getInputPaths: function() {
	        return this.refs.renderer.getInputPaths();
	    },

	    setInputValue: function(path, newValue, cb) {
	        return this.refs.renderer.setInputValue(path, newValue, cb);
	    },

	    getAcceptableFormatsForInputPath: function(path) {
	        return this.refs.renderer.getAcceptableFormatsForInputPath(path);
	    },

	    /**
	     * WARNING: This is an experimental/temporary API and should not be relied
	     *     upon in production code. This function may change its behavior or
	     *     disappear without notice.
	     *
	     * This function was created to allow Renderer.getAllWidgetIds to descend
	     * into our renderer.
	     */
	    getRenderer: function() {
	        return this.refs.renderer;
	    },

	    focus: function(path) {
	        if (path == null) {
	            return this.refs.renderer.focus();
	        } else {
	            this.refs.renderer.focusPath(path);
	        }
	    },

	    blur: function(path) {
	        this.refs.renderer.blurPath(path);
	    },

	    statics: {
	        displayMode: "block"
	    }
	});

	var GroupEditor = React.createClass({displayName: 'GroupEditor',
	    mixins: [Changeable],

	    propTypes: {
	        content: React.PropTypes.string,
	        widgets: React.PropTypes.object,
	        images: React.PropTypes.object,
	        metadata: React.PropTypes.any,
	        apiOptions: ApiOptions.propTypes,
	    },

	    getDefaultProps: function() {
	        return {
	            content: "",
	            widgets: {},
	            images: {},
	            // `undefined` instead of `null` so that getDefaultProps works for
	            // `the GroupMetadataEditor`
	            metadata: undefined
	        };
	    },

	    render: function() {
	        return React.createElement("div", {className: "perseus-group-editor"}, 
	            React.createElement("div", null, 
	                /* the metadata editor; used for tags on khanacademy.org */
	                this._renderMetadataEditor()
	            ), 
	            React.createElement(Editor, {
	                ref: "editor", 
	                content: this.props.content, 
	                widgets: this.props.widgets, 
	                images: this.props.images, 
	                widgetEnabled: true, 
	                immutableWidgets: false, 
	                onChange: this.props.onChange})
	        );
	    },

	    _renderMetadataEditor: function() {
	        var GroupMetadataEditor = this.props.apiOptions.GroupMetadataEditor;
	        return React.createElement(GroupMetadataEditor, {
	            value: this.props.metadata, 
	            onChange: this.change("metadata")});
	    },

	    serialize: function() {
	        return _.extend({}, this.refs.editor.serialize(), {
	            metadata: this.props.metadata
	        });
	    },
	});

	module.exports = {
	    name: "group",
	    displayName: "Group",
	    widget: Group,
	    editor: GroupEditor,
	    hidden: false
	};



/***/ },
/* 22 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * This is an iframe widget. It is used for rendering an iframe that
	 *  then communicates its state via window.postMessage
	 * This is useful for embedding arbitrary visualizations/simulations with
	 *  completed conditions, such as the mazes and games in Algorithms.
	 * It's particularly well suited for embedding our ProcessingJS programs,
	 *  but could also be used for embedding viz's hosted elsewhere.
	 */

	var React = __webpack_require__(61);
	var _ = __webpack_require__(60);

	var BlurInput    = __webpack_require__(82);
	var Changeable = __webpack_require__(66);
	var EditorJsonify = __webpack_require__(67);
	var WidgetJsonifyDeprecated = __webpack_require__(68);
	var updateQueryString = __webpack_require__(3).updateQueryString;


	/* This renders the iframe and handles validation via window.postMessage */
	var Iframe = React.createClass({displayName: 'Iframe',

	    mixins: [Changeable, WidgetJsonifyDeprecated],

	    propTypes: {
	        width: React.PropTypes.string,
	        height: React.PropTypes.string,
	        url: React.PropTypes.string,
	        settings: React.PropTypes.array,
	        status: React.PropTypes.oneOf(['incomplete', 'incorrect', 'correct']),
	        message: React.PropTypes.string,
	    },

	    getDefaultProps: function() {
	        return {
	            status: "incomplete",
	            // optional message
	            message: null
	        };
	    },
	    handleMessageEvent: function(e) {
	        // We receive data from the iframe that contains {passed: true/false}
	        //  and use that to set the status
	        // It could also contain an optional message
	        var data = {};
	        try {
	            data = JSON.parse(e.originalEvent.data);
	        } catch (err) {
	            return;
	        }

	        if (_.isUndefined(data.testsPassed)) {
	            return;
	        }

	        var status = (data.testsPassed ? "correct" : "incorrect");
	        this.change({
	            status: status,
	            message: data.message
	        });
	    },
	    componentDidMount: function() {
	        $(window).on("message", this.handleMessageEvent);
	    },

	    componentWillUnmount: function() {
	        $(window).off("message", this.handleMessageEvent);
	    },

	    render: function() {
	        var style = {
	            width: this.props.width,
	            height: this.props.height
	        };
	        var url = this.props.url;

	        // If the URL doesnt start with http, it must be a program ID
	        if (url && url.length && url.indexOf("http") !== 0) {
	            url = "https://www.khanacademy.org/computer-programming/program/" + url +
	                    "/embedded?buttons=no&embed=yes&editor=no&author=no";
	            url = updateQueryString(url, "width", this.props.width);
	            url = updateQueryString(url, "height", this.props.height);
	            // Origin is used by output.js in deciding to send messages
	            url = updateQueryString(url, "origin", window.location.origin);
	        }

	        // Turn array of [{name: "", value: ""}] into object
	        if (this.props.settings) {
	            var settings = {};
	            _.each(this.props.settings, function(setting) {
	                if (setting.name && setting.value) {
	                    settings[setting.name] = setting.value;
	                }
	            });
	            // This becomes available to programs as Program.settings()
	            url = updateQueryString(url, "settings", JSON.stringify(settings));
	        }

	        // We sandbox the iframe so that we whitelist only the functionality
	        //  that we need. This makes it a bit safer in case some content
	        //  creator "went wild".
	        // http://www.html5rocks.com/en/tutorials/security/sandboxed-iframes/
	        return React.createElement("iframe", {sandbox: "allow-same-origin allow-scripts", 
	                       style: style, src: url});
	    },

	    simpleValidate: function(rubric) {
	        return Iframe.validate(this.getUserInput(), rubric);
	    },

	    statics: {
	        displayMode: "block"
	    }
	});


	/**
	 * This is the widget's grading function
	 */
	_.extend(Iframe, {
	    validate: function(state, rubric) {
	        // The iframe can tell us whether it's correct or incorrect,
	        //  and pass an optional message
	        if (state.status === "correct") {
	            return {
	                type: "points",
	                earned: 1,
	                total: 1,
	                message: state.message || null
	            };
	        } else if (state.status === "incorrect") {
	            return {
	                type: "points",
	                earned: 0,
	                total: 1,
	                message: state.message || null
	            };
	        } else {
	            return {
	                type: "invalid",
	                message: "Keep going, you're not there yet!"
	            };
	        }
	    }
	});


	/**
	 * This is used for editing a name/value pair.
	 */
	var PairEditor = React.createClass({displayName: 'PairEditor',

	    mixins: [Changeable, EditorJsonify],

	    propTypes: {
	        name: React.PropTypes.string,
	        value: React.PropTypes.string
	    },

	    getDefaultProps: function() {
	        return {
	            name:  "",
	            value: ""
	        };
	    },

	    render: function() {
	        return React.createElement("fieldset", null, 
	                React.createElement("label", null, "Name:", 
	                    React.createElement(BlurInput, {value: this.props.name, 
	                           onChange: this.change("name")})
	                ), 
	                React.createElement("label", null, "Value:", 
	                    React.createElement(BlurInput, {value: this.props.value, 
	                           onChange: this.change("value")})
	                )
	            );
	    }
	});

	/**
	 * This is used for editing a set of name/value pairs.
	 */
	var PairsEditor = React.createClass({displayName: 'PairsEditor',

	    mixins: [Changeable, EditorJsonify],

	    propTypes: {
	        pairs: React.PropTypes.arrayOf(React.PropTypes.shape({
	            name: React.PropTypes.string,
	            value: React.PropTypes.string
	        })).isRequired
	    },

	    render: function() {
	        var editors = _.map(this.props.pairs, function(pair, i)  {
	            return React.createElement(PairEditor, {key: i, name: pair.name, value: pair.value, 
	                     onChange: this.handlePairChange.bind(this, i)});
	        }.bind(this));
	        return React.createElement("div", null, 
	            editors
	            );
	    },

	    handlePairChange: function(pairIndex, pair) {
	        // If they're both non empty, add a new one
	        var pairs = this.props.pairs.slice();
	        pairs[pairIndex] = pair;

	        var lastPair = pairs[pairs.length-1];
	        if (lastPair.name && lastPair.value) {
	            pairs.push({name: "", value: ""});
	        }
	        this.change("pairs", pairs);
	    }
	});

	/**
	 * This is the main editor for this widget, to specify all the options.
	 */
	var IframeEditor = React.createClass({displayName: 'IframeEditor',

	    mixins: [Changeable, EditorJsonify],

	    getDefaultProps: function() {
	        return {
	            url: "",
	            settings: [{name: "", value: ""}],
	            width: 400,
	            height: 400
	        };
	    },

	    render: function() {
	        return React.createElement("div", null, 
	            React.createElement("label", null, "Url or Program ID:", 
	                React.createElement(BlurInput, {name: "url", 
	                           value: this.props.url, 
	                           onChange: this.change("url")})
	            ), 
	            React.createElement("br", null), 
	            React.createElement("label", null, "Settings:", 
	                React.createElement(PairsEditor, {name: "settings", 
	                           pairs: this.props.settings, 
	                           onChange: this.handleSettingsChange})
	            ), 
	            React.createElement("br", null), 
	            React.createElement("label", null, "Width:", 
	                React.createElement(BlurInput, {name: "width", 
	                           value: this.props.width, 
	                           onChange: this.change("width")})
	            ), 
	            React.createElement("label", null, "Height:", 
	                React.createElement(BlurInput, {name: "height", 
	                           value: this.props.height, 
	                           onChange: this.change("height")})
	            )
	        );
	    },

	    handleSettingsChange: function(settings) {
	        this.change({settings: settings.pairs});
	    }
	});


	module.exports = {
	    name: "iframe",
	    displayName: "Iframe",
	    widget: Iframe,
	    // Let's not expose it to all content creators yet
	    hidden: true,
	    editor: IframeEditor
	};


/***/ },
/* 23 */
/***/ function(module, exports, __webpack_require__) {

	var _ = __webpack_require__(60);
	var Util = __webpack_require__(3);

	var BlurInput    = __webpack_require__(82);
	var Editor       = __webpack_require__(7);
	var InfoTip      = __webpack_require__(69);
	var Renderer     = __webpack_require__(10);

	var Changeable   = __webpack_require__(66);
	var EditorJsonify = __webpack_require__(67);
	var WidgetJsonifyDeprecated = __webpack_require__(68);

	var Graphie      = __webpack_require__(72);
	var RangeInput   = __webpack_require__(83);
	var SvgImage     = __webpack_require__(57);

	var defaultBoxSize = 400;
	var defaultRange = [0, 10];
	var defaultBackgroundImage = {
	    url: null,
	    width: 0,
	    height: 0
	};

	/**
	 * Alignment option for captions, relative to specified coordinates.
	 */
	var alignments = [
	    "center",
	    "above",
	    "above right",
	    "right",
	    "below right",
	    "below",
	    "below left",
	    "left",
	    "above left"
	];

	function blankLabel() {
	    return {
	        content: "",
	        coordinates: [0, 0],
	        alignment: "center"
	    };
	}

	var ImageWidget = React.createClass({displayName: 'ImageWidget',
	    mixins: [Changeable, WidgetJsonifyDeprecated],

	    propTypes: {
	        title: React.PropTypes.string,
	        range: React.PropTypes.arrayOf(
	            React.PropTypes.arrayOf(React.PropTypes.number
	            )
	        ),
	        box: React.PropTypes.arrayOf(React.PropTypes.number),
	        backgroundImage: React.PropTypes.shape({
	            url: React.PropTypes.string,
	            width: React.PropTypes.number,
	            height: React.PropTypes.number
	        }),
	        labels: React.PropTypes.arrayOf(
	            React.PropTypes.shape({
	                content: React.PropTypes.string,
	                coordinates: React.PropTypes.arrayOf(React.PropTypes.number),
	                alignment: React.PropTypes.string
	            })
	        ),
	        caption: React.PropTypes.string
	    },

	    getDefaultProps: function() {
	        return {
	            title: "",
	            range: [defaultRange, defaultRange],
	            box: [defaultBoxSize, defaultBoxSize],
	            backgroundImage: defaultBackgroundImage,
	            labels: [],
	            caption: ""
	        };
	    },

	    render: function() {
	        var image;
	        var backgroundImage = this.props.backgroundImage;
	        if (backgroundImage.url) {
	            image = React.createElement(SvgImage, {src: backgroundImage.url, 
	                              width: backgroundImage.width, 
	                              height: backgroundImage.height});
	        }

	        var box = this.props.box;

	        return React.createElement("div", {className: "perseus-image-widget"}, 
	            this.props.title &&
	                React.createElement("div", {className: "perseus-image-title"}, 
	                    React.createElement(Renderer, {content: this.props.title})
	                ), 
	            
	            React.createElement("div", {
	                    className: "graphie-container", 
	                    style: {
	                        width: box[0],
	                        height: box[1]
	                    }}, 
	                image, 
	                React.createElement(Graphie, {
	                    ref: "graphie", 
	                    box: this.props.box, 
	                    range: this.props.range, 
	                    options: _.pick(this.props, "box", "range", "labels"), 
	                    setup: this.setupGraphie}
	                )
	            ), 
	            this.props.caption &&
	                React.createElement("div", {className: "perseus-image-caption"}, 
	                React.createElement(Renderer, {content: this.props.caption})
	                )
	            
	        );
	    },

	    setupGraphie: function(graphie, options) {
	        _.map(options.labels, function(label) {
	            graphie.label(label.coordinates, label.content, label.alignment);
	        });
	    },

	    simpleValidate: function(rubric) {
	        return ImageWidget.validate(this.getUserInput(), rubric);
	    },

	    focus: $.noop,

	    statics: {
	        displayMode: "block"
	    }
	});

	_.extend(ImageWidget, {
	    validate: function(state, rubric) {
	        return {
	            type: "points",
	            earned: 0,
	            total: 0,
	            message: null
	        };
	    }
	});

	var ImageEditor = React.createClass({displayName: 'ImageEditor',
	    mixins: [Changeable, EditorJsonify],

	    componentDidMount: function() {
	        // defer this because it can call a change handler synchronously
	        _.defer(function()  {
	            var url = this.props.backgroundImage.url;
	            this.onUrlChange(url, true);
	        }.bind(this));
	    },

	    getDefaultProps: function() {
	        return {
	            title: "",
	            range: [defaultRange, defaultRange],
	            box: [defaultBoxSize, defaultBoxSize],
	            backgroundImage: defaultBackgroundImage,
	            labels: [],
	            caption: "",
	        };
	    },

	    render: function() {
	        var imageSettings = React.createElement("div", {className: "image-settings"}, 
	            React.createElement("div", null, "Background image:"), 
	            React.createElement("div", null, "Url:", ' ', 
	                React.createElement(BlurInput, {value: this.props.backgroundImage.url || '', 
	                           onChange: function(url)  {return this.onUrlChange(url, false);}.bind(this)}), 
	                React.createElement(InfoTip, null, 
	                    React.createElement("p", null, "Create an image in graphie, or use the \"Add image\"" + ' ' +
	                    "function to create a background.")
	                )
	            ), 
	            this.props.backgroundImage.url && React.createElement("div", null, 
	                React.createElement("div", null, "Graphie X range:", ' ', 
	                    React.createElement(RangeInput, {
	                        value: this.props.range[0], 
	                        onChange: _.partial(this.onRangeChange, 0)})
	                ), 
	                React.createElement("div", null, "Graphie Y range:", ' ', 
	                    React.createElement(RangeInput, {
	                        value: this.props.range[1], 
	                        onChange: _.partial(this.onRangeChange, 1)})
	                )
	            )
	        );

	        var graphSettings = React.createElement("div", {className: "graph-settings"}, 
	                React.createElement("div", {className: "add-label"}, 
	                    React.createElement("button", {onClick: this.addLabel}, 
	                        ' ', "Add a label", ' '
	                    )
	                ), 
	                this.props.labels.length > 0 &&
	                React.createElement("table", {className: "label-settings"}, 
	                    React.createElement("thead", null, 
	                    React.createElement("tr", null, 
	                        React.createElement("th", null, "Coordinates"), 
	                        React.createElement("th", null, "Content"), 
	                        React.createElement("th", null, "Alignment"), 
	                        React.createElement("th", null)
	                    )
	                    ), 
	                    React.createElement("tbody", null, 
	                        this.props.labels.map(this._renderRowForLabel)
	                    )
	                )
	        );

	        return React.createElement("div", {className: "perseus-image-editor"}, 
	            React.createElement("div", null, "Title:"), 
	            React.createElement(Editor, {
	                content: this.props.title, 
	                onChange: function(props)  {
	                    if (props.content != null) {
	                        this.change("title", props.content);
	                    }
	                }.bind(this), 
	                widgetEnabled: false}), 
	            imageSettings, 
	            graphSettings, 
	            React.createElement("div", null, "Caption:"), 
	            React.createElement(Editor, {
	                content: this.props.caption, 
	                onChange: function(props)  {
	                    if (props.content != null) {
	                        this.change("caption", props.content);
	                    }
	                }.bind(this), 
	                widgetEnabled: false})
	        );
	    },

	    _renderRowForLabel: function(label, i) {
	        return React.createElement("tr", {key: i}, 
	            React.createElement("td", null, 
	                React.createElement(RangeInput, {
	                    value: label.coordinates, 
	                    onChange: this.onCoordinateChange.bind(this, i)})
	            ), 
	            React.createElement("td", {style: {verticalAlign: "bottom", width: "5px"}}, 
	                React.createElement("input", {
	                    type: "text", 
	                    className: "graph-settings-axis-label", 
	                    value: label.content, 
	                    onChange: this.onContentChange.bind(this, i)})
	            ), 
	            React.createElement("td", null, 
	                React.createElement("select", {
	                    className: "perseus-widget-dropdown", 
	                    value: label.alignment, 
	                    onChange: this.onAlignmentChange.bind(this, i)}, 
	                    alignments.map(function(alignment, i) {
	                        return React.createElement("option", {key: "" + i, value: alignment}, 
	                            alignment
	                        );
	                    }, this)
	                )
	            ), 
	            React.createElement("td", null, 
	                React.createElement("a", {
	                    href: "#", 
	                    className: "simple-button orange delete-label", 
	                    title: "Remove this label", 
	                    onClick: this.removeLabel.bind(this, i)}, 
	                    React.createElement("span", {className: "icon-trash"})
	                )
	            )
	        );
	    },

	    addLabel: function(e) {
	        e.preventDefault();
	        var labels = this.props.labels.slice();
	        var label = blankLabel();
	        labels.push(label);
	        this.props.onChange({
	            labels: labels,
	        });
	    },

	    removeLabel: function(labelIndex, e) {
	        e.preventDefault();
	        var labels = _(this.props.labels).clone();
	        labels.splice(labelIndex, 1);
	        this.props.onChange({labels: labels});
	    },

	    onCoordinateChange: function(labelIndex, newCoordinates) {
	        var labels = this.props.labels.slice();
	        labels[labelIndex] = _.extend({}, labels[labelIndex], {
	            coordinates: newCoordinates
	        });
	        this.props.onChange({labels: labels});
	    },

	    onContentChange: function(labelIndex, e) {
	        var newContent = e.target.value;
	        var labels = this.props.labels.slice();
	        labels[labelIndex] = _.extend({}, labels[labelIndex], {
	            content: newContent
	        });
	        this.props.onChange({labels: labels});
	    },

	    onAlignmentChange: function(labelIndex, e) {
	        var newAlignment = e.target.value;
	        var labels = this.props.labels.slice();
	        labels[labelIndex] = _.extend({}, labels[labelIndex], {
	            alignment: newAlignment
	        });
	        this.props.onChange({labels: labels});
	    },

	    setUrl: function(url, width, height, silent) {
	        // Because this calls into WidgetEditor._handleWidgetChange, which
	        // checks for this widget's ref to serialize it.
	        //
	        // Errors if you switch items before the `Image` from `onUrlChange`
	        // loads.
	        if (!this.isMounted()) {
	            return;
	        }

	        var image = _.clone(this.props.backgroundImage);
	        image.url = url;
	        image.width = width;
	        image.height = height;
	        var box = [image.width, image.height];
	        this.props.onChange({
	                backgroundImage: image,
	                box: box,
	            },
	            null,
	            silent
	        );
	    },

	    // silently update the url when the component mounts
	    // silently update sizes when the image loads
	    // noisily update the url in response to the author changing it
	    onUrlChange: function(url, silent) {
	        // Immediately set the url, then set the image width and height
	        // (silently) later when the image loads.

	        var width = 0;
	        var height = 0;

	        if (url) {
	            Util.getImageSize(url, function(width, height)  {
	                this.setUrl(url, width, height, true);
	            }.bind(this));
	        }

	        this.setUrl(url, width, height, silent);
	    },

	    onRangeChange: function(type, newRange) {
	        var range = this.props.range.slice();
	        range[type] = newRange;
	        this.props.onChange({range: range});
	    },
	});

	module.exports = {
	    name: "image",
	    displayName: "Image",
	    widget: ImageWidget,
	    editor: ImageEditor
	};



/***/ },
/* 24 */
/***/ function(module, exports, __webpack_require__) {

	var React             = __webpack_require__(61);
	var _ = __webpack_require__(60);

	var BlurInput         = __webpack_require__(82);
	var InfoTip           = __webpack_require__(69);
	var InputWithExamples = __webpack_require__(75);
	var ParseTex          = __webpack_require__(84).parseTex;

	var ApiClassNames = __webpack_require__(12).ClassNames;
	var ApiOptions = __webpack_require__(12).Options;
	var Util = __webpack_require__(3);
	var EnabledFeatures = __webpack_require__(49);

	var answerTypes = {
	    number: {
	        name: "Numbers",
	        forms: "integer, decimal, proper, improper, mixed"
	    },
	    decimal: {
	        name: "Decimals",
	        forms: "decimal"
	    },
	    integer: {
	        name: "Integers",
	        forms: "integer"
	    },
	    rational: {
	        name: "Fractions and mixed numbers",
	        forms: "integer, proper, improper, mixed"
	    },
	    improper: {
	        name: "Improper numbers (no mixed)",
	        forms: "integer, proper, improper"
	    },
	    mixed: {
	        name: "Mixed numbers (no improper)",
	        forms: "integer, proper, mixed"
	    },
	    percent: {
	        name: "Numbers or percents",
	        forms: "integer, decimal, proper, improper, mixed, percent"
	    },
	    pi: {
	        name: "Numbers with pi", forms: "pi"
	    }
	};

	var formExamples = {
	    "integer": function(options) { return $._("an integer, like $6$"); },
	    "proper": function(options) {
	        if (options.simplify === "optional") {
	            return $._("a *proper* fraction, like $1/2$ or $6/10$");
	        } else {
	            return $._("a *simplified proper* fraction, like $3/5$");
	        }
	    },
	    "improper": function(options) {
	        if (options.simplify === "optional") {
	            return $._("an *improper* fraction, like $10/7$ or $14/8$");
	        } else {
	            return $._("a *simplified improper* fraction, like $7/4$");
	        }
	    },
	    "mixed": function(options) {
	        return $._("a mixed number, like $1\\ 3/4$");
	    },
	    "decimal": function(options) {
	        return $._("an *exact* decimal, like $0.75$");
	    },
	    "percent": function(options) {
	        return $._("a percent, like $12.34\\%$");
	    },
	    "pi": function(options) {
	        return $._("a multiple of pi, like $12\\ \\text{pi}$ or " +
	                "$2/3\\ \\text{pi}$");
	    }
	};

	var InputNumber = React.createClass({displayName: 'InputNumber',
	    propTypes: {
	        currentValue: React.PropTypes.string,
	        enabledFeatures: EnabledFeatures.propTypes,
	        reviewModeRubric: React.PropTypes.object,
	    },

	    getDefaultProps: function() {
	        return {
	            currentValue: "",
	            size: "normal",
	            answerType: "number",
	            enabledFeatures: EnabledFeatures.defaults,
	            apiOptions: ApiOptions.defaults
	        };
	    },

	    shouldShowExamples: function() {
	        return this.props.enabledFeatures.toolTipFormats &&
	                this.props.answerType !== "number" &&
	                !this.props.apiOptions.staticRender;
	    },

	    render: function() {
	        // HACK(johnsullivan): Create a function with shared logic between this
	        // and NumericInput.
	        var rubric = this.props.reviewModeRubric;
	        var correct = null;
	        var answerBlurb = null;
	        if (rubric) {
	            var score = this.simpleValidate(rubric);
	            correct = score.type === "points" &&
	                          score.earned === score.total;

	            if (!correct) {
	                // TODO(johnsullivan): Make this a little more human-friendly.
	                var answerString = String(rubric.value);
	                if (rubric.inexact && rubric.maxError) {
	                    answerString += " \u00B1 " + rubric.maxError;
	                }
	                answerBlurb = React.createElement("span", {className: "perseus-possible-answers"}, 
	                    React.createElement("span", {className: "perseus-possible-answer"}, 
	                        answerString
	                    )
	                );
	            }
	        }

	        var classes = {};
	        classes["perseus-input-size-" + this.props.size] = true;
	        classes[ApiClassNames.CORRECT] =
	            rubric && correct && this.props.currentValue;
	        classes[ApiClassNames.INCORRECT] =
	            rubric && !correct && this.props.currentValue;
	        classes[ApiClassNames.UNANSWERED] = rubric && !this.props.currentValue;

	        var input = React.createElement(InputWithExamples, {
	            ref: "input", 
	            value: this.props.currentValue, 
	            onChange: this.handleChange, 
	            className: React.addons.classSet(classes), 
	            type: this._getInputType(), 
	            examples: this.examples(), 
	            shouldShowExamples: this.shouldShowExamples(), 
	            onFocus: this._handleFocus, 
	            onBlur: this._handleBlur});

	        if (answerBlurb) {
	            return React.createElement("span", {className: "perseus-input-with-answer-blurb"}, 
	                input, 
	                answerBlurb
	            );
	        } else {
	            return input;
	        }
	    },

	    handleChange: function(newValue) {
	        this.props.onChange({ currentValue: newValue });
	    },

	    _getInputType: function() {
	        if (this.props.apiOptions.staticRender) {
	            return "tex";
	        } else {
	            return "text";
	        }
	    },

	    _handleFocus: function() {
	        this.props.onFocus([]);
	    },

	    _handleBlur: function() {
	        this.props.onBlur([]);
	    },

	    focus: function() {
	        this.refs.input.focus();
	        return true;
	    },

	    focusInputPath: function(inputPath) {
	        this.refs.input.focus();
	    },

	    blurInputPath: function(inputPath) {
	        this.refs.input.blur();
	    },

	    getInputPaths: function() {
	        // The widget itself is an input, so we return a single empty list to
	        // indicate this.
	        return [[]];
	    },

	    getGrammarTypeForPath: function(path) {
	        return "number";
	    },

	    setInputValue: function(path, newValue, cb) {
	        this.props.onChange({
	            currentValue: newValue
	        }, cb);
	    },

	    getUserInput: function() {
	        return {
	            currentValue: this.props.currentValue
	        };
	    },

	    simpleValidate: function(rubric, onInputError) {
	        onInputError = onInputError || function() { };
	        return InputNumber.validate(
	            this.getUserInput(),
	            rubric,
	            onInputError
	        );
	    },

	    examples: function() {
	        var type = this.props.answerType;
	        var forms = answerTypes[type].forms.split(/\s*,\s*/);

	        var examples = _.map(forms, function(form) {
	            return formExamples[form](this.props);
	        }, this);

	        return [$._("**Acceptable Formats**")].concat(examples);
	    },

	    statics: {
	        displayMode: "inline-block"
	    }
	});

	_.extend(InputNumber, {
	    validate: function(state, rubric, onInputError) {
	        if (rubric.answerType == null) {
	            rubric.answerType = "number";
	        }
	        var val = Khan.answerTypes.number.createValidatorFunctional(
	            rubric.value, {
	                simplify: rubric.simplify,
	                inexact: rubric.inexact || undefined,
	                maxError: rubric.maxError,
	                forms: answerTypes[rubric.answerType].forms
	            });

	        // We may have received TeX; try to parse it before grading.
	        // If `currentValue` is not TeX, this should be a no-op.
	        var currentValue = ParseTex(state.currentValue);

	        var result = val(currentValue);

	        // TODO(eater): Seems silly to translate result to this invalid/points
	        // thing and immediately translate it back in ItemRenderer.scoreInput()
	        if (result.empty) {
	            var apiResult = onInputError(
	                null, // reserved for some widget identifier
	                state.currentValue,
	                result.message
	            );
	            return {
	                type: "invalid",
	                message: (apiResult === false) ? null : result.message
	            };
	        } else {
	            return {
	                type: "points",
	                earned: result.correct ? 1 : 0,
	                total: 1,
	                message: result.message
	            };
	        }
	    }
	});

	var InputNumberEditor = React.createClass({displayName: 'InputNumberEditor',
	    propTypes: {
	        value: React.PropTypes.number,
	        simplify: React.PropTypes.oneOf(['required', 'optional', 'enforced']),
	        size: React.PropTypes.oneOf(['normal', 'small']),
	        inexact: React.PropTypes.bool,
	        maxError: React.PropTypes.number,
	        answerType: React.PropTypes.string
	    },

	    getDefaultProps: function() {
	        return {
	            value: 0,
	            simplify: "required",
	            size: "normal",
	            inexact: false,
	            maxError: 0.1,
	            answerType: "number"
	        };
	    },

	    handleAnswerChange: function(str) {
	        var value = Util.firstNumericalParse(str) || 0;
	        this.props.onChange({value: value});
	    },

	    render: function() {
	        var answerTypeOptions = _.map(answerTypes, function(v, k) {
	            return React.createElement("option", {value: k, key: k}, v.name);
	        }, this);

	        return React.createElement("div", null, 
	            React.createElement("div", null, React.createElement("label", null, 
	                "Correct answer:", ' ', 
	                React.createElement(BlurInput, {value: "" + this.props.value, 
	                           onChange: this.handleAnswerChange, 
	                           ref: "input"})
	            )), 

	            React.createElement("div", null, 
	                React.createElement("label", null, 
	                    "Unsimplified answers", ' ', 
	                    React.createElement("select", {value: this.props.simplify, 
	                            onChange: function(e)  {
	                                this.props.onChange({simplify:
	                                e.target.value});
	                            }.bind(this)}, 
	                        React.createElement("option", {value: "required"}, "will not be graded"), 
	                        React.createElement("option", {value: "optional"}, "will be accepted"), 
	                        React.createElement("option", {value: "enforced"}, "will be marked wrong")
	                    )
	                ), 
	                React.createElement(InfoTip, null, 
	                    React.createElement("p", null, "Normally select \"will not be graded\". This will give the" + ' ' +
	                    "user a message saying the answer is correct but not" + ' ' +
	                    "simplified. The user will then have to simplify it and" + ' ' +
	                    "re-enter, but will not be penalized. (5th grade and" + ' ' +
	                    "anything after)"), 
	                    React.createElement("p", null, "Select \"will be accepted\" only if the user is not" + ' ' +
	                    "expected to know how to simplify fractions yet. (Anything" + ' ' +
	                    "prior to 5th grade)"), 
	                    React.createElement("p", null, "Select \"will be marked wrong\" only if we are" + ' ' +
	                    "specifically assessing the ability to simplify.")
	                )
	            ), 

	            React.createElement("div", null, React.createElement("label", null, 
	                React.createElement("input", {type: "checkbox", 
	                    checked: this.props.inexact, 
	                    onChange: function(e)  {
	                        this.props.onChange({inexact: e.target.checked});
	                    }.bind(this)}), 
	                ' ', "Allow inexact answers"
	            ), 

	            React.createElement("label", null, 
	            React.createElement("input", {/* TODO(emily): don't use a hidden checkbox for alignment */
	                type: "checkbox", style: {visibility: "hidden"}}), 
	            "Max error:", ' ', 
	            React.createElement("input", {type: "text", disabled: !this.props.inexact, 
	                defaultValue: this.props.maxError, 
	                onBlur: function(e)  {
	                    var ans = "" + (Util.firstNumericalParse(
	                            e.target.value) || 0);
	                    e.target.value = ans;
	                    this.props.onChange({maxError: ans});
	                }.bind(this)})
	            )), 

	            React.createElement("div", null, 
	            "Answer type:", ' ', 
	            React.createElement("select", {
	                value: this.props.answerType, 
	                onChange: function(e)  {
	                    this.props.onChange({answerType: e.target.value});
	                }.bind(this)}, 
	                answerTypeOptions
	            ), 
	            React.createElement(InfoTip, null, 
	                React.createElement("p", null, "Use the default \"Numbers\" unless the answer must be in a" + ' ' +
	                "specific form (e.g., question is about converting decimals to" + ' ' +
	                "fractions).")
	            )
	            ), 

	            React.createElement("div", null, 
	                React.createElement("label", null, 
	                    "Width", ' ', 
	                    React.createElement("select", {value: this.props.size, 
	                            onChange: function(e)  {
	                                this.props.onChange({size: e.target.value});
	                            }.bind(this)}, 
	                        React.createElement("option", {value: "normal"}, "Normal (80px)"), 
	                        React.createElement("option", {value: "small"}, "Small (40px)")
	                    )
	                ), 
	                React.createElement(InfoTip, null, 
	                    React.createElement("p", null, "Use size \"Normal\" for all text boxes, unless there are" + ' ' +
	                    "multiple text boxes in one line and the answer area is too" + ' ' +
	                    "narrow to fit them.")
	                )
	            )
	        );
	    },

	    focus: function() {
	        this.refs.input.getDOMNode().focus();
	        return true;
	    },

	    serialize: function() {
	        return _.pick(this.props,
	                "value", "simplify", "size", "inexact", "maxError",
	                "answerType");
	    }
	});

	var propTransform = function(editorProps)  {
	    return _.pick(editorProps, "simplify", "size", "answerType");
	};

	module.exports = {
	    name: "input-number",
	    displayName: "Number text box (old)",
	    hidden: true,
	    widget: InputNumber,
	    editor: InputNumberEditor,
	    transform: propTransform
	};


/***/ },
/* 25 */
/***/ function(module, exports, __webpack_require__) {

	var _ = __webpack_require__(60);

	var Changeable   = __webpack_require__(66);
	var EditorJsonify = __webpack_require__(67);

	var ArrowPicker = __webpack_require__(96);
	var ColorPicker = __webpack_require__(97);
	var ConstraintEditor = __webpack_require__(98);
	var DashPicker = __webpack_require__(99);
	var ElementContainer = __webpack_require__(100);
	var Graphie = __webpack_require__(72);
	var GraphSettings = __webpack_require__(79);
	var MathInput = __webpack_require__(76);
	var NumberInput = __webpack_require__(85);
	var TeX = __webpack_require__(65);
	var TextInput = __webpack_require__(86);

	var Label = Graphie.Label;
	var Line = Graphie.Line;
	var MovablePoint = Graphie.MovablePoint;
	var MovableLine = Graphie.MovableLine;
	var Plot = Graphie.Plot;
	var PlotParametric = Graphie.PlotParametric;
	var Point = Graphie.Point;
	var Rect = Graphie.Rect;

	var kvector = __webpack_require__(103).vector;

	// Memoize KAS parsing
	var KAShashFunc = function(expr, options)  {
	    options = options || {};
	    var result = expr + "||" + options.decimal_separatpr + "||";
	    var functions = options.functions;
	    var functionsLength = functions ? functions.length : 0;
	    for (var i = 0; i < functionsLength; i++) {
	        result += functions[i] + "|";
	    }
	    return result;
	};

	var _parseCache = Object.create(null);
	var KASparse = function(expr, options)  {
	    var hash = KAShashFunc(expr, options);
	    var cached = _parseCache[hash];
	    if (cached) {
	        return cached;
	    }
	    cached = KAS.parse(expr, options);
	    _parseCache[hash] = cached;
	    return cached;
	};

	_compileCache = Object.create(null);
	var KAScompile = function(expr, options)  {
	    var hash = KAShashFunc(expr, options);
	    var cached = _compileCache[hash];
	    if (cached) {
	        return cached;
	    }
	    var parsed = KAS.parse(expr, options).expr;
	    cached = parsed ? parsed.compile() : function() { return 0; };
	    _compileCache[hash] = cached;
	    return cached;
	};

	var defaultInteractionProps = {
	    graph: {
	        box: [400, 400],
	        labels: ["x", "y"],
	        range: [[-10, 10], [-10, 10]],
	        tickStep: [1, 1],
	        gridStep: [1, 1],
	        markings: "graph",
	    },
	    elements: []
	};

	var Interaction = React.createClass({displayName: 'Interaction',
	    mixins: [Changeable],

	    // TODO(eater): Make more better
	    propTypes: {
	        graph: React.PropTypes.object,
	        elements: React.PropTypes.arrayOf(React.PropTypes.object)
	    },

	    getDefaultProps: function() {
	        return defaultInteractionProps;
	    },

	    getInitialState: function() {
	        return {
	            variables: this._getInitialVariables(this.props.elements),
	            functions: this._getInitialFunctions(this.props.elements)
	        };
	    },

	    _getInitialVariables: function(elements) {
	        var variables = {};
	        // TODO(eater): look at all this copypasta! refactor this!
	        _.each(_.where(elements, {type: "movable-point"}), function(element) {
	            var subscript = element.options.varSubscript;
	            var startXExpr = KASparse(element.options.startX || "0").expr;
	            var startYExpr = KASparse(element.options.startY || "0").expr;
	            var startX = 0;
	            var startY = 0;
	            if (startXExpr) {
	                startX = startXExpr.eval({}) || 0;
	            }
	            if (startYExpr) {
	                startY = startYExpr.eval({}) || 0;
	            }
	            variables["x_" + subscript] = startX;
	            variables["y_" + subscript] = startY;
	        }, this);
	        _.each(_.where(elements, {type: "movable-line"}), function(element) {
	            var startSubscript = element.options.startSubscript;
	            var endSubscript = element.options.endSubscript;
	            var startXExpr = KASparse(element.options.startX || "0").expr;
	            var startYExpr = KASparse(element.options.startY || "0").expr;
	            var endXExpr = KASparse(element.options.endX || "0").expr;
	            var endYExpr = KASparse(element.options.endY || "0").expr;
	            var startX = 0;
	            var startY = 0;
	            var endX = 0;
	            var endY = 0;
	            if (startXExpr) {
	                startX = startXExpr.eval({}) || 0;
	            }
	            if (startYExpr) {
	                startY = startYExpr.eval({}) || 0;
	            }
	            if (endXExpr) {
	                endX = endXExpr.eval({}) || 0;
	            }
	            if (endYExpr) {
	                endY = endYExpr.eval({}) || 0;
	            }
	            variables["x_" + startSubscript] = startX;
	            variables["y_" + startSubscript] = startY;
	            variables["x_" + endSubscript] = endX;
	            variables["y_" + endSubscript] = endY;
	        }, this);
	        _.each(_.where(elements, {type: "function"}), function(element) {
	            variables[element.options.funcName] = element.options.value;
	        });
	        return variables;
	    },

	    _getInitialFunctions: function(elements) {
	        return _.map(_.where(elements, {type: "function"}),
	            function(element)  {return element.options.funcName;});
	    },

	    componentWillReceiveProps: function(nextProps) {
	        this.setState({
	            variables: this._getInitialVariables(nextProps.elements),
	            functions: this._getInitialFunctions(nextProps.elements)
	        });
	    },

	    _setupGraphie: function(graphie, options) {
	        graphie.graphInit(_.extend({}, options, {
	            grid: _.contains(["graph", "grid"], this.props.graph.markings),
	            axes: _.contains(["graph"], this.props.graph.markings),
	            ticks: _.contains(["graph"], this.props.graph.markings),
	            labels: _.contains(["graph"], this.props.graph.markings),
	            labelFormat: function(s) { return "\\small{" + s + "}"; },
	            axisArrows: "<->",
	            unityLabels: false
	        }));
	        if (this.props.graph.markings === "graph") {
	            var labels = this.props.graph.labels;
	            var range = this.props.graph.range;
	            graphie.label([0, range[1][1]], labels[1], "above");
	            graphie.label([range[0][1], 0], labels[0], "right");
	        }

	    },

	    _updatePointLocation: function(subscript, coord) {
	        var variables = _.clone(this.state.variables);
	        variables["x_" + subscript] = coord[0];
	        variables["y_" + subscript] = coord[1];
	        this.setState({variables: variables});
	    },

	    _updateLineLocation: function(options, startCoord) {
	        var xDiff = this._eval("(" + options.endX +
	            ")-(" + options.startX + ")");
	        var yDiff = this._eval("(" + options.endY +
	            ")-(" + options.startY + ")");
	        var endCoord = kvector.add(startCoord, [xDiff, yDiff]);
	        var variables = _.clone(this.state.variables);
	        variables["x_" + options.startSubscript] = startCoord[0];
	        variables["y_" + options.startSubscript] = startCoord[1];
	        variables["x_" + options.endSubscript] = endCoord[0];
	        variables["y_" + options.endSubscript] = endCoord[1];
	        this.setState({variables: variables});
	    },

	    _eval: function(expression, variables) {
	        var func = KAScompile(expression,
	            {functions: this.state.functions});
	        var compiledVars = _.extend({}, this.state.variables, variables);
	        _.each(_.keys(compiledVars), function(name)  {
	            if (_.isString(compiledVars[name])) {
	                var func = KAScompile(compiledVars[name], {
	                    functions: this.state.functions
	                });
	                compiledVars[name] = function(x) {
	                    return func(_.extend({}, compiledVars, {
	                        x: x
	                    }));
	                };
	            }
	        }.bind(this));
	        val = func(compiledVars);
	        // Default to 0 if the expression couldn't be parsed
	        return val || 0;
	    },

	    // Return an array of all the variables in an expression
	    _extractVars: function(expr) {
	        if (expr == null) {
	            return [];
	        }
	        var vars = [];
	        _.each(expr.args(), function(arg) {
	            if (arg && arg.constructor.name === "Expr") {
	                vars = vars.concat(this._extractVars(arg));
	            }
	        }, this);

	        if (expr.name() === "Var") {
	            vars.push(expr.prettyPrint());
	        }
	        return vars;
	    },


	    render: function() {
	        return React.createElement(Graphie, {
	                box: this.props.graph.box, 
	                range: this.props.graph.range, 
	                options: this.props.graph, 
	                setup: this._setupGraphie}, 
	            _.map(this.props.elements, function(element, n) {
	                if (element.type === "point") {
	                    return React.createElement(Point, {
	                        key: element.key, 
	                        coord: [this._eval(element.options.coordX),
	                            this._eval(element.options.coordY)], 
	                        color: element.options.color});
	                } else if (element.type === "line") {
	                    var start = [this._eval(element.options.startX),
	                                 this._eval(element.options.startY)];
	                    var end = [this._eval(element.options.endX),
	                               this._eval(element.options.endY)];
	                    return React.createElement(Line, {
	                        key: element.key, 
	                        start: start, 
	                        end: end, 
	                        style: {
	                            stroke: element.options.color,
	                            strokeWidth: element.options.strokeWidth,
	                            strokeDasharray: element.options.strokeDasharray,
	                            arrows: element.options.arrows
	                        }});
	                } else if (element.type === "movable-point") {
	                    // TODO(eater): Would be nice if the constraint system
	                    // were more flexible.
	                    var constraints = [function(coord)  {
	                        var coordX =
	                            Math.max(this._eval(
	                                element.options.constraintXMin),
	                            Math.min(this._eval(
	                                element.options.constraintXMax),
	                            coord[0]));
	                        var coordY =
	                            Math.max(this._eval(
	                                element.options.constraintYMin),
	                            Math.min(this._eval(
	                                element.options.constraintYMax),
	                            coord[1]));
	                        return [coordX, coordY];
	                    }.bind(this)];
	                    if (element.options.constraint === "snap") {
	                        constraints.push(MovablePoint.constraints.snap(
	                            element.options.snap));
	                    } else if (element.options.constraint === "x") {
	                        constraints.push(function(coord)  {
	                            return [this._eval(
	                                element.options.constraintFn,
	                                {y: coord[1]}), coord[1]];
	                        }.bind(this));
	                    } else if (element.options.constraint === "y") {
	                        constraints.push(function(coord)  {
	                            return [coord[0], this._eval(
	                                element.options.constraintFn, {x: coord[0]})];
	                        }.bind(this));
	                    }

	                    // TODO(eater): foo_[xyz] are hacky non-props to get the
	                    // component to update when constraints change
	                    return React.createElement(MovablePoint, {
	                        key: element.key, 
	                        coord: [
	                            this.state.variables["x_" +
	                            element.options.varSubscript],
	                            this.state.variables["y_" +
	                            element.options.varSubscript]], 
	                        constraints: constraints, 
	                        foo_x: element.options.constraint, 
	                        foo_y: element.options.constraintFn, 
	                        foo_z: element.options.snap, 
	                        onMove: _.partial(this._updatePointLocation,
	                            element.options.varSubscript)}
	                        );
	                } else if (element.type === "movable-line") {
	                    // TODO(eater): Would be nice if the constraint system
	                    // were more flexible.
	                    // TODO(eater): Don't duplicate this code from
	                    // movable-point above
	                    var constraints = [function(coord)  {
	                        var coordX =
	                            Math.max(this._eval(
	                                element.options.constraintXMin),
	                            Math.min(this._eval(
	                                element.options.constraintXMax),
	                            coord[0]));
	                        var coordY =
	                            Math.max(this._eval(
	                                element.options.constraintYMin),
	                            Math.min(this._eval(
	                                element.options.constraintYMax),
	                            coord[1]));
	                        return [coordX, coordY];
	                    }.bind(this)];
	                    if (element.options.constraint === "snap") {
	                        constraints.push(MovablePoint.constraints.snap(
	                            element.options.snap));
	                    } else if (element.options.constraint === "x") {
	                        constraints.push(function(coord)  {
	                            return [this._eval(
	                                element.options.constraintFn,
	                                {y: coord[1]}), coord[1]];
	                        }.bind(this));
	                    } else if (element.options.constraint === "y") {
	                        constraints.push(function(coord)  {
	                            return [coord[0], this._eval(
	                                element.options.constraintFn,
	                                {x: coord[0]})];
	                        }.bind(this));
	                    }
	                    var start = [
	                            this.state.variables["x_" +
	                                element.options.startSubscript],
	                            this.state.variables["y_" +
	                                element.options.startSubscript]
	                        ];
	                    var end = [
	                            this.state.variables["x_" +
	                                element.options.endSubscript],
	                            this.state.variables["y_" +
	                                element.options.endSubscript]
	                        ];
	                    return React.createElement(MovableLine, {
	                        key: element.key, 
	                        constraints: constraints, 
	                        onMove: _.bind(this._updateLineLocation, this,
	                            element.options), 
	                        foo_x: element.options.constraint, 
	                        foo_y: element.options.constraintFn, 
	                        foo_z: element.options.snap}, 
	                            React.createElement(MovablePoint, {coord: start, 
	                                static: true, 
	                                normalStyle: {stroke: "none", fill: "none"}}), 
	                            React.createElement(MovablePoint, {coord: end, 
	                                static: true, 
	                                normalStyle: {stroke: "none", fill: "none"}})
	                        );
	                } else if (element.type === "function") {
	                    var fn = function(x)  {
	                        return this._eval(element.options.value, {x: x});
	                    }.bind(this);
	                    // find all the variables referenced by this function
	                    var vars = _.without(this._extractVars(
	                        KASparse(element.options.value).expr), "x");
	                    // and find their values, so we redraw if any change
	                    var varValues = _.object(vars,
	                        _.map(vars, function(v)  {return this.state.variables[v];}.bind(this)));

	                    var range=[this._eval(element.options.rangeMin,
	                        this.state.variables),
	                        this._eval(element.options.rangeMax,
	                        this.state.variables)];

	                    return React.createElement(Plot, {
	                        key: element.key, 
	                        fn: fn, 
	                        foo_fn: element.options.value, 
	                        foo_varvalues: varValues, 
	                        range: range, 
	                        style: {
	                            stroke: element.options.color,
	                            strokeWidth: element.options.strokeWidth,
	                            strokeDasharray: element.options.strokeDasharray,
	                            plotPoints: 100// TODO(eater): why so slow?
	                        }});
	                } else if (element.type === "parametric") {
	                    var fn = function(t)  {
	                        return [
	                            this._eval(element.options.x, {t: t}),
	                            this._eval(element.options.y, {t: t})];
	                    }.bind(this);
	                    // find all the variables referenced by this function
	                    var vars = _.without(this._extractVars(
	                        KASparse(element.options.x).expr).concat(
	                        this._extractVars(
	                        KASparse(element.options.y).expr)), "t");
	                    // and find their values, so we redraw if any change
	                    var varValues = _.object(vars,
	                        _.map(vars, function(v)  {return this.state.variables[v];}.bind(this)));

	                    var range = [this._eval(element.options.rangeMin,
	                        this.state.variables),
	                        this._eval(element.options.rangeMax,
	                        this.state.variables)];

	                    return React.createElement(PlotParametric, {
	                        key: element.key, 
	                        fn: fn, 
	                        foo_fnx: element.options.x, 
	                        foo_fny: element.options.y, 
	                        foo_varvalues: varValues, 
	                        range: range, 
	                        style: {
	                            stroke: element.options.color,
	                            strokeWidth: element.options.strokeWidth,
	                            strokeDasharray: element.options.strokeDasharray,
	                            plotPoints: 100// TODO(eater): why so slow?
	                        }});
	                } else if (element.type === "label") {
	                    var coord = [this._eval(element.options.coordX),
	                                 this._eval(element.options.coordY)];
	                    return React.createElement(Label, {
	                        key: n + 1, 
	                        coord: coord, 
	                        text: element.options.label, 
	                        style: {
	                            color: element.options.color
	                        }});
	                } else if (element.type === "rectangle") {
	                    return React.createElement(Rect, {
	                        key: n + 1, 
	                        x: this._eval(element.options.coordX), 
	                        y: this._eval(element.options.coordY), 
	                        width: _.max([this._eval(element.options.width), 0]), 
	                        height: _.max([this._eval(element.options.height), 0]), 
	                        style: {
	                            stroke: "none",
	                            fill: element.options.color
	                        }});
	                }
	            }, this)
	        );
	    },

	    getUserInput: function() {
	        // TODO(eater): Perhaps we want to be able to record the state of the
	        // user's interaction. Unfortunately sending all the props will
	        // probably make the attempt payload too large. So for now, don't send
	        // anything.
	        return {};
	    },

	    simpleValidate: function(rubric) {
	        return Interaction.validate(this.getUserInput(), rubric);
	    },

	    statics: {
	        displayMode: "block"
	    }
	});


	_.extend(Interaction, {
	    validate: function(state, rubric) {
	        return {
	            type: "points",
	            earned: 0,
	            total: 0,
	            message: null
	        };
	    }
	});


	//
	// Editor for non-interactive points
	//
	// TODO(eater): Factor this out
	//
	var PointEditor = React.createClass({displayName: 'PointEditor',
	    mixins: [EditorJsonify, Changeable],

	    propTypes: {
	        coordX: React.PropTypes.string,
	        coordY: React.PropTypes.string,
	        color: React.PropTypes.string
	    },

	    getDefaultProps: function() {
	        return {
	            coordX: "0",
	            coordY: "0",
	            color: KhanUtil.BLACK
	        };
	    },

	    render: function() {
	        return React.createElement("div", {className: "graph-settings"}, 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                "Coordinate: ", React.createElement(TeX, null, "\\Large("), React.createElement(MathInput, {
	                    buttonSets: [], 
	                    buttonsVisible: "never", 
	                    value: this.props.coordX, 
	                    onChange: this.change("coordX")}), 
	                React.createElement(TeX, null, ","), " ", React.createElement(MathInput, {
	                    buttonSets: [], 
	                    buttonsVisible: "never", 
	                    value: this.props.coordY, 
	                    onChange: this.change("coordY")}), 
	                React.createElement(TeX, null, "\\Large)")
	            ), 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                React.createElement(ColorPicker, {
	                    value: this.props.color, 
	                    onChange: this.change("color")})
	            )
	        );
	    }
	});


	//
	// Editor for non-interactive line segments
	//
	// TODO(eater): Factor this out
	//
	var LineEditor = React.createClass({displayName: 'LineEditor',
	    mixins: [EditorJsonify, Changeable],

	    propTypes: {
	        startX: React.PropTypes.string,
	        startY: React.PropTypes.string,
	        endX: React.PropTypes.string,
	        endY: React.PropTypes.string,
	        color: React.PropTypes.string,
	        strokeDasharray: React.PropTypes.string,
	        arrows: React.PropTypes.string,
	        strokeWidth: React.PropTypes.number
	    },

	    getDefaultProps: function() {
	        return {
	            startX: "-5",
	            startY: "5",
	            endX: "5",
	            endY: "5",
	            color: KhanUtil.BLACK,
	            strokeDasharray: "",
	            arrows: "",
	            strokeWidth: 2
	        };
	    },

	    render: function() {
	        return React.createElement("div", {className: "graph-settings"}, 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                "Start: ", React.createElement(TeX, null, "\\Large("), React.createElement(MathInput, {
	                    buttonSets: [], 
	                    buttonsVisible: "never", 
	                    value: this.props.startX, 
	                    onChange: this.change("startX")}), 
	                React.createElement(TeX, null, ","), " ", React.createElement(MathInput, {
	                    buttonSets: [], 
	                    buttonsVisible: "never", 
	                    value: this.props.startY, 
	                    onChange: this.change("startY")}), 
	                React.createElement(TeX, null, "\\Large)")
	            ), 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                "End: ", React.createElement(TeX, null, "\\Large("), React.createElement(MathInput, {
	                    buttonSets: [], 
	                    buttonsVisible: "never", 
	                    value: this.props.endX, 
	                    onChange: this.change("endX")}), 
	                React.createElement(TeX, null, ","), " ", React.createElement(MathInput, {
	                    buttonSets: [], 
	                    buttonsVisible: "never", 
	                    value: this.props.endY, 
	                    onChange: this.change("endY")}), 
	                React.createElement(TeX, null, "\\Large)")
	            ), 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                React.createElement(ColorPicker, {
	                    value: this.props.color, 
	                    onChange: this.change("color")})
	            ), 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                React.createElement(DashPicker, {
	                    value: this.props.strokeDasharray, 
	                    onChange: this.change("strokeDasharray")}), 
	                "   ", 
	                React.createElement(ArrowPicker, {
	                    value: this.props.arrows, 
	                    onChange: this.change("arrows")})
	            ), 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                React.createElement("div", {className: "perseus-widget-left-col"}, 
	                    "Width: ", React.createElement(NumberInput, {
	                        value: this.props.strokeWidth, 
	                        placeholder: 2, 
	                        onChange: this.change("strokeWidth")})
	                )
	            )
	        );
	    }
	});


	//
	// Editor for interactive movable points
	//
	// TODO(eater): Factor this out
	// TODO(eater): Rethink how constraints are represented
	//
	var MovablePointEditor = React.createClass({displayName: 'MovablePointEditor',
	    mixins: [EditorJsonify, Changeable],

	    propTypes: {
	        startX: React.PropTypes.string,
	        startY: React.PropTypes.string,
	        constraint: React.PropTypes.string,
	        snap: React.PropTypes.number,
	        constraintFn: React.PropTypes.string
	    },

	    getDefaultProps: function() {
	        return {
	            startX: "0",
	            startY: "0",
	            constraint: "none",
	            snap: 0.5,
	            constraintFn: "0",
	            constraintXMin: "-10",
	            constraintXMax: "10",
	            constraintYMin: "-10",
	            constraintYMax: "10"
	        };
	    },

	    render: function() {
	        return React.createElement("div", {className: "graph-settings"}, 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                "Start: ", React.createElement(TeX, null, "\\Large("), React.createElement(MathInput, {
	                    buttonSets: [], 
	                    buttonsVisible: "never", 
	                    value: this.props.startX, 
	                    onChange: this.change("startX")}), 
	                React.createElement(TeX, null, ","), " ", React.createElement(MathInput, {
	                    buttonSets: [], 
	                    buttonsVisible: "never", 
	                    value: this.props.startY, 
	                    onChange: this.change("startY")}), 
	                React.createElement(TeX, null, "\\Large)")
	            ), 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                "Update ", React.createElement(TeX, null, "(x_n, y_n)"), " for ", React.createElement(TeX, null, "n ="), " ", React.createElement(NumberInput, {
	                    value: this.props.varSubscript, 
	                    placeholder: 0, 
	                    onChange: this.change("varSubscript")})
	            ), 
	            React.createElement(ConstraintEditor, React.__spread({},  this.props))
	        );
	    }
	});


	//
	// Editor for interactive movable line segments
	//
	// TODO(eater): Factor this out
	// TODO(eater): Rethink how constraints are represented
	//
	var MovableLineEditor = React.createClass({displayName: 'MovableLineEditor',
	    mixins: [EditorJsonify, Changeable],

	    propTypes: {
	        startX: React.PropTypes.string,
	        startY: React.PropTypes.string,
	        endX: React.PropTypes.string,
	        endY: React.PropTypes.string,
	        constraint: React.PropTypes.string,
	        snap: React.PropTypes.number,
	        constraintFn: React.PropTypes.string
	    },

	    getDefaultProps: function() {
	        return {
	            startX: "-5",
	            startY: "5",
	            endX: "5",
	            endY: "5",
	            constraint: "none",
	            snap: 0.5,
	            constraintFn: "0",
	            constraintXMin: "-10",
	            constraintXMax: "10",
	            constraintYMin: "-10",
	            constraintYMax: "10"
	        };
	    },

	    render: function() {
	        return React.createElement("div", {className: "graph-settings"}, 
	            "Initial position:", 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                "Start: ", React.createElement(TeX, null, "\\Large("), React.createElement(MathInput, {
	                    buttonSets: [], 
	                    buttonsVisible: "never", 
	                    value: this.props.startX, 
	                    onChange: this.change("startX")}), 
	                React.createElement(TeX, null, ","), " ", React.createElement(MathInput, {
	                    buttonSets: [], 
	                    buttonsVisible: "never", 
	                    value: this.props.startY, 
	                    onChange: this.change("startY")}), 
	                React.createElement(TeX, null, "\\Large)")
	            ), 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                "End: ", React.createElement(TeX, null, "\\Large("), React.createElement(MathInput, {
	                    buttonSets: [], 
	                    buttonsVisible: "never", 
	                    value: this.props.endX, 
	                    onChange: this.change("endX")}), 
	                React.createElement(TeX, null, ","), " ", React.createElement(MathInput, {
	                    buttonSets: [], 
	                    buttonsVisible: "never", 
	                    value: this.props.endY, 
	                    onChange: this.change("endY")}), 
	                React.createElement(TeX, null, "\\Large)")
	            ), 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                "Start updates ", React.createElement(TeX, null, "(x_n, y_n)"), " for ", React.createElement(TeX, null, "n ="), 
	                    React.createElement(NumberInput, {
	                        value: this.props.startSubscript, 
	                        placeholder: 0, 
	                        onChange: this.change("startSubscript")})
	            ), 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                "End updates ", React.createElement(TeX, null, "(x_m, y_m)"), " for ", React.createElement(TeX, null, "m ="), 
	                    React.createElement(NumberInput, {
	                        value: this.props.endSubscript, 
	                        placeholder: 0, 
	                        onChange: this.change("endSubscript")})
	            ), 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                "All constraints are applied to the start point."
	            ), 
	            React.createElement(ConstraintEditor, React.__spread({},  this.props))
	        );
	    }
	});


	//
	// Editor for function plots
	//
	// TODO(eater): Factor this out
	//
	var FunctionEditor = React.createClass({displayName: 'FunctionEditor',
	    mixins: [EditorJsonify, Changeable],

	    propTypes: {
	        value: React.PropTypes.string,
	        rangeMin: React.PropTypes.string,
	        rangeMax: React.PropTypes.string,
	        color: React.PropTypes.string,
	        strokeDashArray: React.PropTypes.string,
	        strokeWidth: React.PropTypes.number
	    },

	    getDefaultProps: function() {
	        return {
	            value: "x",
	            rangeMin: "-10",
	            rangeMax: "10",
	            color: KhanUtil.BLUE,
	            strokeDasharray: "",
	            strokeWidth: 2
	        };
	    },

	    render: function() {
	        return React.createElement("div", {className: "graph-settings"}, 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                React.createElement(TeX, null, this.props.funcName + "(x)="), " ", React.createElement(MathInput, {
	                    buttonSets: [], 
	                    buttonsVisible: "never", 
	                    value: this.props.value, 
	                    onChange: this.change("value")})
	            ), 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                "Range: ", React.createElement(TeX, null, "\\Large("), React.createElement(MathInput, {
	                    buttonSets: [], 
	                    buttonsVisible: "never", 
	                    value: this.props.rangeMin, 
	                    onChange: this.change("rangeMin")}), 
	                React.createElement(TeX, null, ","), " ", React.createElement(MathInput, {
	                    buttonSets: [], 
	                    buttonsVisible: "never", 
	                    value: this.props.rangeMax, 
	                    onChange: this.change("rangeMax")}), 
	                React.createElement(TeX, null, "\\Large)")
	            ), 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                React.createElement(ColorPicker, {
	                    value: this.props.color, 
	                    onChange: this.change("color")})
	            ), 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                React.createElement(DashPicker, {
	                    value: this.props.strokeDasharray, 
	                    onChange: this.change("strokeDasharray")})
	            ), 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                React.createElement("div", {className: "perseus-widget-left-col"}, 
	                    "Width: ", React.createElement(NumberInput, {
	                        value: this.props.strokeWidth, 
	                        placeholder: 2, 
	                        onChange: this.change("strokeWidth")})
	                )
	            )
	        );
	    }
	});


	//
	// Editor for parametric plots
	//
	// TODO(eater): Factor this out
	//
	var ParametricEditor = React.createClass({displayName: 'ParametricEditor',
	    mixins: [EditorJsonify, Changeable],

	    propTypes: {
	        x: React.PropTypes.string,
	        y: React.PropTypes.string,
	        rangeMin: React.PropTypes.string,
	        rangeMax: React.PropTypes.string,
	        color: React.PropTypes.string,
	        strokeDashArray: React.PropTypes.string,
	        strokeWidth: React.PropTypes.number
	    },

	    getDefaultProps: function() {
	        return {
	            x: "cos(t)",
	            y: "sin(t)",
	            rangeMin: "0",
	            rangeMax: "2\\pi",
	            color: KhanUtil.BLUE,
	            strokeDasharray: "",
	            strokeWidth: 2
	        };
	    },

	    render: function() {
	        return React.createElement("div", {className: "graph-settings"}, 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                React.createElement(TeX, null, "X(t) ="), " ", React.createElement(MathInput, {
	                    buttonSets: [], 
	                    buttonsVisible: "never", 
	                    value: this.props.x, 
	                    onChange: this.change("x")})
	            ), 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                React.createElement(TeX, null, "Y(t) ="), " ", React.createElement(MathInput, {
	                    buttonSets: [], 
	                    buttonsVisible: "never", 
	                    value: this.props.y, 
	                    onChange: this.change("y")})
	            ), 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                "Range: ", React.createElement(TeX, null, "\\Large("), React.createElement(MathInput, {
	                    buttonSets: [], 
	                    buttonsVisible: "never", 
	                    value: this.props.rangeMin, 
	                    onChange: this.change("rangeMin")}), 
	                React.createElement(TeX, null, ","), " ", React.createElement(MathInput, {
	                    buttonSets: [], 
	                    buttonsVisible: "never", 
	                    value: this.props.rangeMax, 
	                    onChange: this.change("rangeMax")}), 
	                React.createElement(TeX, null, "\\Large)")
	            ), 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                React.createElement(ColorPicker, {
	                    value: this.props.color, 
	                    onChange: this.change("color")})
	            ), 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                React.createElement(DashPicker, {
	                    value: this.props.strokeDasharray, 
	                    onChange: this.change("strokeDasharray")})
	            ), 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                React.createElement("div", {className: "perseus-widget-left-col"}, 
	                    "Width: ", React.createElement(NumberInput, {
	                        value: this.props.strokeWidth, 
	                        placeholder: 2, 
	                        onChange: this.change("strokeWidth")})
	                )
	            )
	        );
	    }
	});


	//
	// Editor for labels
	//
	// TODO(eater): Factor this out maybe?
	// TODO(eater): Add text direction
	//
	var LabelEditor = React.createClass({displayName: 'LabelEditor',
	    mixins: [EditorJsonify, Changeable],

	    propTypes: {
	    },

	    getDefaultProps: function() {
	        return {
	            coordX: "0",
	            coordY: "0",
	            color: KhanUtil.BLACK,
	            label: "\\phi"
	        };
	    },

	    render: function() {
	        return React.createElement("div", {className: "graph-settings"}, 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                React.createElement(TextInput, {
	                    value: this.props.label, 
	                    onChange: this.change("label"), 
	                    style: {
	                        width: "100%"
	                    }}
	                    )
	            ), 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                "Location: ", React.createElement(TeX, null, "\\Large("), React.createElement(MathInput, {
	                    buttonSets: [], 
	                    buttonsVisible: "never", 
	                    value: this.props.coordX, 
	                    onChange: this.change("coordX")}), 
	                React.createElement(TeX, null, ","), " ", React.createElement(MathInput, {
	                    buttonSets: [], 
	                    buttonsVisible: "never", 
	                    value: this.props.coordY, 
	                    onChange: this.change("coordY")}), 
	                React.createElement(TeX, null, "\\Large)")
	            ), 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                React.createElement(ColorPicker, {
	                    value: this.props.color, 
	                    onChange: this.change("color")})
	            )
	        );
	    }
	});


	//
	// Editor for rectangles
	//
	// TODO(eater): Factor this out maybe?
	//
	var RectangleEditor = React.createClass({displayName: 'RectangleEditor',
	    mixins: [EditorJsonify, Changeable],

	    propTypes: {
	    },

	    getDefaultProps: function() {
	        return {
	            coordX: "-5",
	            coordY: "5",
	            width: "2",
	            height: "3",
	            color: KhanUtil.LIGHT_BLUE
	        };
	    },

	    render: function() {
	        return React.createElement("div", {className: "graph-settings"}, 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                "Top left: ", React.createElement(TeX, null, "\\Large("), React.createElement(MathInput, {
	                    buttonSets: [], 
	                    buttonsVisible: "never", 
	                    value: this.props.coordX, 
	                    onChange: this.change("coordX")}), 
	                React.createElement(TeX, null, ","), " ", React.createElement(MathInput, {
	                    buttonSets: [], 
	                    buttonsVisible: "never", 
	                    value: this.props.coordY, 
	                    onChange: this.change("coordY")}), 
	                React.createElement(TeX, null, "\\Large)")
	            ), 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                "Width: ", React.createElement(MathInput, {
	                    buttonSets: [], 
	                    buttonsVisible: "never", 
	                    value: this.props.width, 
	                    onChange: this.change("width")})
	            ), 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                "Height: ", React.createElement(MathInput, {
	                    buttonSets: [], 
	                    buttonsVisible: "never", 
	                    value: this.props.height, 
	                    onChange: this.change("height")})
	            ), 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                React.createElement(ColorPicker, {
	                    value: this.props.color, 
	                    lightColors: true, 
	                    onChange: this.change("color")})
	            ), 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                "You want a border? Sorry, draw your own."
	            )
	        );
	    }
	});



	var InteractionEditor = React.createClass({displayName: 'InteractionEditor',
	    mixins: [EditorJsonify, Changeable],

	    // TODO(eater): Make more better
	    propTypes: {
	        graph: React.PropTypes.object,
	        elements: React.PropTypes.arrayOf(React.PropTypes.object)
	    },

	    getDefaultProps: function() {
	        return defaultInteractionProps;
	    },

	    getInitialState: function() {
	        return {
	            usedVarSubscripts: this._getAllVarSubscripts(this.props.elements),
	            usedFunctionNames: this._getAllFunctionNames(this.props.elements)
	        };
	    },

	    componentWillReceiveProps: function(nextProps) {
	        this.setState({
	            usedVarSubscripts: this._getAllVarSubscripts(nextProps.elements),
	            usedFunctionNames: this._getAllFunctionNames(nextProps.elements)
	        });
	    },

	    _getAllVarSubscripts: function(elements) {
	        return _.map(_.where(elements, {type: "movable-point"}),
	            function(element)  {return element.options.varSubscript;}).concat(
	            _.map(_.where(elements, {type: "movable-line"}),
	            function(element)  {return element.options.startSubscript;})).concat(
	            _.map(_.where(elements, {type: "movable-line"}),
	            function(element)  {return element.options.endSubscript;}));
	    },

	    _getAllFunctionNames: function(elements) {
	        return _.map(_.where(elements, {type: "function"}),
	            function(element)  {return element.options.funcName;});
	    },

	    _updateGraphProps: function(newProps) {
	        // TODO(eater): GraphSettings should name this tickStep instead
	        // of step. Grr..
	        this.change({
	            graph: _.extend(_.omit(newProps, "step"), {
	                    tickStep: newProps.step
	                })
	        });
	    },

	    _addNewElement: function(e) {
	        var elementType = e.target.value;
	        if (elementType === "") {
	            return;
	        }
	        e.target.value = "";
	        var newElement = {
	            type: elementType,
	            key: elementType + "-" + (Math.random()*0xffffff<<0).toString(16),
	            options: elementType === "point" ?
	                        _.clone(PointEditor.defaultProps) :
	                        elementType === "line" ?
	                        _.clone(LineEditor.defaultProps) :
	                        elementType === "movable-point" ?
	                        _.clone(MovablePointEditor.defaultProps) :
	                        elementType === "movable-line" ?
	                        _.clone(MovableLineEditor.defaultProps) :
	                        elementType === "function" ?
	                        _.clone(FunctionEditor.defaultProps) :
	                        elementType === "parametric" ?
	                        _.clone(ParametricEditor.defaultProps) :
	                        elementType === "label" ?
	                        _.clone(LabelEditor.defaultProps) :
	                        elementType === "rectangle" ?
	                        _.clone(RectangleEditor.defaultProps) : {}
	        };
	        if (elementType === "movable-point") {
	            var nextSubscript =
	                _.max([_.max(this.state.usedVarSubscripts), -1]) + 1;
	            newElement.options.varSubscript = nextSubscript;
	        } else if (elementType === "movable-line") {
	            var nextSubscript =
	                _.max([_.max(this.state.usedVarSubscripts), -1]) + 1;
	            newElement.options.startSubscript = nextSubscript;
	            newElement.options.endSubscript = nextSubscript + 1;
	        } else if (elementType === "function") {
	            // TODO(eater): The 22nd function added will be {(x) since '{'
	            // comes after 'z'
	            var nextLetter = String.fromCharCode(_.max([_.max(_.map(
	                this.state.usedFunctionNames, function(c) {
	                return c.charCodeAt(0); })),
	                "e".charCodeAt(0)]) + 1);
	            newElement.options.funcName = nextLetter;
	        }
	        this.change({
	            elements: this.props.elements.concat(newElement)
	        });
	    },

	    _deleteElement: function(index) {
	        var element = this.props.elements[index];
	        this.change({elements: _.without(this.props.elements, element)});
	    },

	    _moveElementUp: function(index) {
	        var element = this.props.elements[index];
	        var newElements = _.without(this.props.elements, element);
	        newElements.splice(index - 1, 0, element);
	        this.change({elements: newElements});
	    },

	    _moveElementDown: function(index) {
	        var element = this.props.elements[index];
	        var newElements = _.without(this.props.elements, element);
	        newElements.splice(index + 1, 0, element);
	        this.change({elements: newElements});
	    },

	    render: function() {
	        return React.createElement("div", {className: "perseus-widget-interaction-editor"}, 
	            React.createElement(ElementContainer, {
	                    title: "Grid settings"}, 
	                React.createElement(GraphSettings, {
	                    editableSettings: ["canvas", "graph"], 
	                    box: this.props.graph.box, 
	                    labels: this.props.graph.labels, 
	                    range: this.props.graph.range, 
	                    step: this.props.graph.tickStep, /*TODO(eater): grr names*/
	                    gridStep: this.props.graph.gridStep, 
	                    markings: this.props.graph.markings, 
	                    onChange: this._updateGraphProps}), 
	                (this.props.graph.valid === true) || React.createElement("div", null, 
	                    this.props.graph.valid
	                )
	            ), 
	            _.map(this.props.elements, function(element, n) {
	                if (element.type === "movable-point") {
	                    return React.createElement(ElementContainer, {
	                            title: React.createElement("span", null, "Movable point ", React.createElement(TeX, null, 
	                                    "(x_{" + element.options.varSubscript +
	                                    "}, y_{" + element.options.varSubscript +
	                                    "})")
	                                ), 
	                            onUp: n === 0 ?
	                                null : this._moveElementUp.bind(this, n), 
	                            onDown: n === this.props.elements.length - 1 ?
	                                null : this._moveElementDown.bind(this, n), 
	                            onDelete: this._deleteElement.bind(this, n), 
	                            key: element.key}, 
	                        React.createElement(MovablePointEditor, React.__spread({}, 
	                            element.options, 
	                            {onChange: function(newProps)  {
	                                var elements = JSON.parse(JSON.stringify(
	                                    this.props.elements));
	                                _.extend(elements[n].options, newProps);
	                                this.change({elements: elements});
	                            }.bind(this)})
	                        )
	                    );
	                } else if (element.type === "movable-line") {
	                    return React.createElement(ElementContainer, {
	                            title: React.createElement("span", null, "Movable line ", React.createElement(TeX, null, 
	                                    "(x_{" + element.options.startSubscript +
	                                    "}, y_{" + element.options.startSubscript +
	                                    "})"), " to ", React.createElement(TeX, null, 
	                                    "(x_{" + element.options.endSubscript +
	                                    "}, y_{" + element.options.endSubscript +
	                                    "})")
	                                ), 
	                            onUp: n === 0 ?
	                                null : this._moveElementUp.bind(this, n), 
	                            onDown: n === this.props.elements.length - 1 ?
	                                null : this._moveElementDown.bind(this, n), 
	                            onDelete: this._deleteElement.bind(this, n), 
	                            key: element.key}, 
	                        React.createElement(MovableLineEditor, React.__spread({}, 
	                            element.options, 
	                            {onChange: function(newProps)  {
	                                var elements = JSON.parse(JSON.stringify(
	                                    this.props.elements));
	                                _.extend(elements[n].options, newProps);
	                                this.change({elements: elements});
	                            }.bind(this)})
	                        )
	                    );
	                } else if (element.type === "point") {
	                    return React.createElement(ElementContainer, {
	                            title: React.createElement("span", null, "Point ", React.createElement(TeX, null, 
	                                    "(" + element.options.coordX +
	                                    ", " + element.options.coordY +
	                                    ")")
	                                ), 
	                            onUp: n === 0 ?
	                                null : this._moveElementUp.bind(this, n), 
	                            onDown: n === this.props.elements.length - 1 ?
	                                null : this._moveElementDown.bind(this, n), 
	                            onDelete: this._deleteElement.bind(this, n), 
	                            key: element.key}, 
	                        React.createElement(PointEditor, React.__spread({}, 
	                            element.options, 
	                            {onChange: function(newProps)  {
	                                var elements = JSON.parse(JSON.stringify(
	                                    this.props.elements));
	                                _.extend(elements[n].options, newProps);
	                                this.change({elements: elements});
	                            }.bind(this)})
	                        )
	                    );
	                } else if (element.type === "line") {
	                    return React.createElement(ElementContainer, {
	                            title: React.createElement("span", null, "Line ", React.createElement(TeX, null, 
	                                    "(" + element.options.startX +
	                                    ", " + element.options.startY +
	                                    ")"), " to ", React.createElement(TeX, null, 
	                                    "(" + element.options.endX +
	                                    ", " + element.options.endY +
	                                    ")")
	                                ), 
	                            onUp: n === 0 ?
	                                null : this._moveElementUp.bind(this, n), 
	                            onDown: n === this.props.elements.length - 1 ?
	                                null : this._moveElementDown.bind(this, n), 
	                            onDelete: this._deleteElement.bind(this, n), 
	                            key: element.key}, 
	                        React.createElement(LineEditor, React.__spread({}, 
	                            element.options, 
	                            {onChange: function(newProps)  {
	                                var elements = JSON.parse(JSON.stringify(
	                                    this.props.elements));
	                                _.extend(elements[n].options, newProps);
	                                this.change({elements: elements});
	                            }.bind(this)})
	                        )
	                    );
	                } else if (element.type === "function") {
	                    return React.createElement(ElementContainer, {
	                            title: React.createElement("span", null, "Function ", React.createElement(TeX, null, 
	                                element.options.funcName + "(x) = " +
	                                element.options.value
	                            )), 
	                            onUp: n === 0 ?
	                                null : this._moveElementUp.bind(this, n), 
	                            onDown: n === this.props.elements.length - 1 ?
	                                null : this._moveElementDown.bind(this, n), 
	                            onDelete: this._deleteElement, 
	                            key: element.key}, 
	                        React.createElement(FunctionEditor, React.__spread({}, 
	                            element.options, 
	                            {onChange: function(newProps)  {
	                                var elements = JSON.parse(JSON.stringify(
	                                    this.props.elements));
	                                _.extend(elements[n].options, newProps);
	                                this.change({elements: elements});
	                            }.bind(this)})
	                        )
	                    );
	                } else if (element.type === "parametric") {
	                    return React.createElement(ElementContainer, {
	                            title: React.createElement("span", null, "Parametric"), 
	                            onUp: n === 0 ?
	                                null : this._moveElementUp.bind(this, n), 
	                            onDown: n === this.props.elements.length - 1 ?
	                                null : this._moveElementDown.bind(this, n), 
	                            onDelete: this._deleteElement, 
	                            key: element.key}, 
	                        React.createElement(ParametricEditor, React.__spread({}, 
	                            element.options, 
	                            {onChange: function(newProps)  {
	                                var elements = JSON.parse(JSON.stringify(
	                                    this.props.elements));
	                                _.extend(elements[n].options, newProps);
	                                this.change({elements: elements});
	                            }.bind(this)})
	                        )
	                    );
	                } else if (element.type === "label") {
	                    return React.createElement(ElementContainer, {
	                            title: React.createElement("span", null, "Label ", React.createElement(TeX, null, 
	                                element.options.label), " "), 
	                            onUp: n === 0 ?
	                                null : this._moveElementUp.bind(this, n), 
	                            onDown: n === this.props.elements.length - 1 ?
	                                null : this._moveElementDown.bind(this, n), 
	                            onDelete: this._deleteElement, 
	                            key: element.key}, 
	                        React.createElement(LabelEditor, React.__spread({}, 
	                            element.options, 
	                            {onChange: function(newProps)  {
	                                var elements = JSON.parse(JSON.stringify(
	                                    this.props.elements));
	                                _.extend(elements[n].options, newProps);
	                                this.change({elements: elements});
	                            }.bind(this)}))
	                    );
	                } else if (element.type === "rectangle") {
	                    return React.createElement(ElementContainer, {
	                            title: React.createElement("span", null, "Rectangle ", React.createElement(TeX, null, "(" +
	                                element.options.coordX + ", " +
	                                element.options.coordY + ")"), 
	                                " — ", 
	                                React.createElement(TeX, null, element.options.width + " \\times " +
	                                element.options.height)
	                                ), 
	                            onUp: n === 0 ?
	                                null : this._moveElementUp.bind(this, n), 
	                            onDown: n === this.props.elements.length - 1 ?
	                                null : this._moveElementDown.bind(this, n), 
	                            onDelete: this._deleteElement, 
	                            key: element.key}, 
	                        React.createElement(RectangleEditor, React.__spread({}, 
	                            element.options, 
	                            {onChange: function(newProps)  {
	                                var elements = JSON.parse(JSON.stringify(
	                                    this.props.elements));
	                                _.extend(elements[n].options, newProps);
	                                this.change({elements: elements});
	                            }.bind(this)}))
	                    );
	                }
	            }, this), 
	            React.createElement("div", {className: "perseus-widget-interaction-editor-select-element"}, 
	                React.createElement("select", {onChange: this._addNewElement}, 
	                    React.createElement("option", {value: ""}, "Add an element", "\u2026"), 
	                    React.createElement("option", {disabled: true}, "--"), 
	                    React.createElement("option", {value: "point"}, "Point"), 
	                    React.createElement("option", {value: "line"}, "Line segment"), 
	                    React.createElement("option", {value: "function"}, "Function plot"), 
	                    React.createElement("option", {value: "parametric"}, "Parametric plot"), 
	                    React.createElement("option", {value: "label"}, "Label"), 
	                    React.createElement("option", {value: "rectangle"}, "Rectangle"), 
	                    React.createElement("option", {value: "movable-point"}, 
	                        "★ Movable point"), 
	                    React.createElement("option", {value: "movable-line"}, 
	                        "★ Movable line segment")
	                )
	            )
	        );
	    }
	});


	module.exports = {
	    name: "interaction",
	    displayName: "Interaction",
	    widget: Interaction,
	    editor: InteractionEditor,
	    transform: _.identity
	};


/***/ },
/* 26 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);
	var _ = __webpack_require__(60);

	var Graph         = __webpack_require__(87);
	var GraphSettings = __webpack_require__(79);
	var InfoTip       = __webpack_require__(69);
	var Interactive2  = __webpack_require__(80);
	var NumberInput   = __webpack_require__(85);
	var Util          = __webpack_require__(3);

	var knumber = __webpack_require__(103).number;
	var kpoint = __webpack_require__(103).point;

	var DeprecationMixin = Util.DeprecationMixin;


	var TRASH_ICON_URI = 'https://ka-perseus-graphie.s3.amazonaws.com/b1452c0d79fd0f7ff4c3af9488474a0a0decb361.png';

	var defaultBoxSize = 400;
	var defaultEditorBoxSize = 340;
	var defaultBackgroundImage = {
	    url: null,
	    scale: 1,
	    bottom: 0,
	    left: 0
	};

	var eq = Util.eq;
	var deepEq = Util.deepEq;

	var UNLIMITED = "unlimited";

	// Sample background image:
	// https://ka-perseus-graphie.s3.amazonaws.com/29c1b0fcd17fe63df0f148fe357044d5d5c7d0bb.png

	function ccw(a, b, c) {
	    return (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1]);
	}

	function collinear(a, b, c) {
	    return eq(ccw(a, b, c), 0);
	}

	function sign(val) {
	    if (eq(val, 0)) {
	        return 0;
	    } else {
	        return val > 0 ? 1 : -1;
	    }
	}

	// default to defaultValue if actual is null or undefined
	function defaultVal(actual, defaultValue) {
	    return (actual == null) ? defaultValue : actual;
	}

	// Given rect bounding points A and B, whether point C is inside the rect
	function pointInRect(a, b, c) {
	    return (c[0] <= Math.max(a[0], b[0]) && c[0] >= Math.min(a[0], b[0]) &&
	            c[1] <= Math.max(a[1], b[1]) && c[1] >= Math.min(a[1], b[1]));
	}

	// Whether line segment AB intersects line segment CD
	// http://www.geeksforgeeks.org/check-if-two-given-line-segments-intersect/
	function intersects(ab, cd) {
	    var triplets = [
	        [ab[0], ab[1], cd[0]],
	        [ab[0], ab[1], cd[1]],
	        [cd[0], cd[1], ab[0]],
	        [cd[0], cd[1], ab[1]]
	    ];

	    var orientations = _.map(triplets, function(triplet) {
	        return sign(ccw.apply(null, triplet));
	    });

	    if (orientations[0] !== orientations[1] &&
	        orientations[2] !== orientations[3]) {
	        return true;
	    }

	    for (var i = 0; i < 4; i++) {
	        if (orientations[i] === 0 && pointInRect.apply(null, triplets[i])) {
	            return true;
	        }
	    }

	    return false;
	}

	function vector(a, b) {
	    return _.map(_.zip(a, b), function(pair) {
	        return pair[0] - pair[1];
	    });
	}

	function magnitude(v) {
	    return Math.sqrt(_.reduce(v, function(memo, el) {
	        return memo + Math.pow(el, 2);
	    }, 0));
	}

	function dotProduct(a, b) {
	    return _.reduce(_.zip(a, b), function(memo, pair) {
	        return memo + pair[0] * pair[1];
	    }, 0);
	}

	function sideLengths(coords) {
	    var segments = _.zip(coords, rotate(coords));
	    return _.map(segments, function(segment) {
	        return magnitude(vector.apply(null, segment));
	    });
	}

	// Based on http://math.stackexchange.com/a/151149
	function angleMeasures(coords) {
	    var triplets = _.zip(rotate(coords, -1), coords, rotate(coords, 1));

	    var offsets = _.map(triplets, function(triplet) {
	        var p = vector(triplet[1], triplet[0]);
	        var q = vector(triplet[2], triplet[1]);
	        var raw = Math.acos(dotProduct(p, q) / (magnitude(p) * magnitude(q)));
	        return sign(ccw.apply(null, triplet)) > 0 ? raw : -raw;
	    });

	    var sum = _.reduce(offsets, function(memo, arg) { return memo + arg; }, 0);

	    return _.map(offsets, function(offset) {
	        return sum > 0 ? Math.PI - offset : Math.PI + offset;
	    });
	}

	// Whether two polygons are similar (or if specified, congruent)
	function similar(coords1, coords2, tolerance) {
	    if (coords1.length !== coords2.length) {
	        return false;
	    }

	    var n = coords1.length;

	    var angles1 = angleMeasures(coords1);
	    var angles2 = angleMeasures(coords2);

	    var sides1 = sideLengths(coords1);
	    var sides2 = sideLengths(coords2);

	    for (var i = 0; i < 2 * n; i++) {
	        var angles = angles2.slice();
	        var sides = sides2.slice();

	        // Reverse angles and sides to allow matching reflected polygons
	        if (i >= n) {
	            angles.reverse();
	            sides.reverse();
	            // Since sides are calculated from two coordinates,
	            // simply reversing results in an off by one error
	            sides = rotate(sides, 1);
	        }

	        angles = rotate(angles, i);
	        sides = rotate(sides, i);

	        if (deepEq(angles1, angles)) {
	            var sidePairs = _.zip(sides1, sides);

	            var factors = _.map(sidePairs, function(pair) {
	                return pair[0] / pair[1];
	            });

	            var same = _.all(factors, function(factor) {
	                return eq(factors[0], factor);
	            });

	            var congruentEnough = _.all(sidePairs, function(pair) {
	                return knumber.equal(pair[0], pair[1], tolerance);
	            });

	            if (same && congruentEnough) {
	                return true;
	            }
	        }
	    }

	    return false;
	}

	// Less than or approximately equal
	function leq(a, b) {
	    return a < b || eq(a, b);
	}

	// Given triangle with sides ABC return angle opposite side C in degrees
	function lawOfCosines(a, b, c) {
	    return Math.acos((a * a + b * b - c * c) / (2 * a * b)) * 180 / Math.PI;
	}

	function canonicalSineCoefficients(coeffs) {
	    // For a curve of the form f(x) = a * Sin(b * x - c) + d,
	    // this function ensures that a, b > 0, and c is its
	    // smallest possible positive value.
	    var amplitude = coeffs[0];
	    var angularFrequency = coeffs[1];
	    var phase = coeffs[2];
	    var verticalOffset = coeffs[3];

	    // Guarantee a > 0
	    if (amplitude < 0) {
	        amplitude *= -1;
	        angularFrequency *= -1;
	        phase *= -1;
	    }

	    var period = 2 * Math.PI;
	    // Guarantee b > 0
	    if (angularFrequency < 0) {
	        angularFrequency *= -1;
	        phase *= -1;
	        phase += period / 2;
	    }

	    // Guarantee c is smallest possible positive value
	    while (phase > 0) {
	        phase -= period;
	    }
	    while (phase < 0) {
	        phase += period;
	    }

	    return [amplitude, angularFrequency, phase, verticalOffset];
	}

	// e.g. rotate([1, 2, 3]) -> [2, 3, 1]
	function rotate(array, n) {
	    n = (typeof n === "undefined") ? 1 : (n % array.length);
	    return array.slice(n).concat(array.slice(0, n));
	}

	function capitalize(str) {
	    return str.replace(/(?:^|-)(.)/g, function(match, letter) {
	        return letter.toUpperCase();
	    });
	}

	function getLineEquation(first, second) {
	    if (eq(first[0], second[0])) {
	        return "x = " + first[0].toFixed(3);
	    } else {
	        var m = (second[1] - first[1]) /
	                (second[0] - first[0]);
	        var b = first[1] - m * first[0];
	        return "y = " + m.toFixed(3) + "x + " + b.toFixed(3);
	    }
	}

	// Stolen from the wikipedia article
	// http://en.wikipedia.org/wiki/Line-line_intersection
	function getLineIntersection(firstPoints, secondPoints) {
	    var x1 = firstPoints[0][0],
	        y1 = firstPoints[0][1],
	        x2 = firstPoints[1][0],
	        y2 = firstPoints[1][1],
	        x3 = secondPoints[0][0],
	        y3 = secondPoints[0][1],
	        x4 = secondPoints[1][0],
	        y4 = secondPoints[1][1];

	    var determinant = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);

	    if (Math.abs(determinant) < 1e-9) {
	        return "Lines are parallel";
	    } else {
	        var x = ((x1 * y2 - y1 * x2) * (x3 - x4) -
	                 (x1 - x2) * (x3 * y4 - y3 * x4)) / determinant;
	        var y = ((x1 * y2 - y1 * x2) * (y3 - y4) -
	                 (y1 - y2) * (x3 * y4 - y3 * x4)) / determinant;
	        return "Intersection: (" + x.toFixed(3) + ", " + y.toFixed(3) + ")";
	    }
	}

	function numSteps(range, step) {
	    return Math.floor((range[1] - range[0]) / step);
	}

	var deprecatedProps = {
	    showGraph: function(props) {
	        return {markings: props.showGraph ? "graph" : "none"};
	    }
	};


	var InteractiveGraph = React.createClass({displayName: 'InteractiveGraph',

	    getInitialState: function() {
	        return {
	            shouldShowInstructions: this._getShouldShowInstructions()
	        };
	    },

	    getDefaultProps: function() {
	        return {
	            labels: ["x", "y"],
	            range: [[-10, 10], [-10, 10]],
	            box: [defaultBoxSize, defaultBoxSize],
	            step: [1, 1],
	            backgroundImage: defaultBackgroundImage,
	            markings: "graph",
	            showProtractor: false,
	            showRuler: false,
	            rulerLabel: "",
	            rulerTicks: 10,
	            graph: {
	                type: "linear"
	            }
	        };
	    },

	    mixins: [DeprecationMixin],
	    deprecatedProps: deprecatedProps,

	    _getShouldShowInstructions: function(props) {
	        props = props || this.props;
	        return this.isClickToAddPoints(props) && (
	            props.graph.coords == null || props.graph.coords.length === 0
	        );
	    },

	    componentDidUpdate: function(prevProps, prevState) {
	        var oldType = prevProps.graph.type;
	        var newType = this.props.graph.type;
	        if (oldType !== newType ||
	                prevProps.graph.allowReflexAngles !==
	                    this.props.graph.allowReflexAngles ||
	                prevProps.graph.angleOffsetDeg !==
	                    this.props.graph.angleOffsetDeg ||
	                prevProps.graph.numPoints !== this.props.graph.numPoints ||
	                prevProps.graph.numSides !== this.props.graph.numSides ||
	                prevProps.graph.numSegments !== this.props.graph.numSegments ||
	                prevProps.graph.showAngles !== this.props.graph.showAngles ||
	                prevProps.graph.showSides !== this.props.graph.showSides ||
	                prevProps.graph.snapTo !== this.props.graph.snapTo ||
	                prevProps.graph.snapDegrees !== this.props.graph.snapDegrees) {
	            this["remove" + capitalize(oldType) + "Controls"]();
	            this["add" + capitalize(newType) + "Controls"]();
	        }
	        if (this.shouldResetGraphie) {
	            this.resetGraphie();
	        }
	    },

	    render: function() {
	        var typeSelect;
	        var extraOptions;
	        if (this.props.flexibleType) {
	            typeSelect = React.createElement("select", {
	                    value: this.props.graph.type, 
	                    onChange: function(e)  {
	                        var type = e.target.value;
	                        this.props.onChange({
	                            graph: {type: type}
	                        });
	                    }.bind(this)}, 
	                React.createElement("option", {value: "linear"}, "Linear function"), 
	                React.createElement("option", {value: "quadratic"}, "Quadratic function"), 
	                React.createElement("option", {value: "sinusoid"}, "Sinusoid function"), 
	                React.createElement("option", {value: "circle"}, "Circle"), 
	                React.createElement("option", {value: "point"}, "Point(s)"), 
	                React.createElement("option", {value: "linear-system"}, "Linear System"), 
	                React.createElement("option", {value: "polygon"}, "Polygon"), 
	                React.createElement("option", {value: "segment"}, "Line Segment(s)"), 
	                React.createElement("option", {value: "ray"}, "Ray"), 
	                React.createElement("option", {value: "angle"}, "Angle")
	            );

	            if (this.props.graph.type === "point") {
	                extraOptions = React.createElement("select", {
	                        key: "point-select", 
	                        value: this.props.graph.numPoints || 1, 
	                        onChange: function(e)  {
	                            // Convert numbers, leave UNLIMITED intact:
	                            var num = +e.target.value || e.target.value;
	                            this.props.onChange({
	                                graph: {
	                                    type: "point",
	                                    numPoints: num,
	                                    coords: null
	                                }
	                            });
	                        }.bind(this)}, 
	                    _.map(_.range(1, 7), function(n) {
	                        return React.createElement("option", {value: n}, 
	                            n, " point", n > 1 && "s"
	                        );
	                    }), 
	                    React.createElement("option", {value: UNLIMITED}, "unlimited")
	                );
	            } else if (this.props.graph.type === "polygon") {
	                extraOptions = React.createElement("div", null, 
	                    React.createElement("div", null, 
	                        React.createElement("select", {
	                            key: "polygon-select", 
	                            value: this.props.graph.numSides || 3, 
	                            onChange: function(e)  {
	                                // Convert numbers, leave UNLIMITED intact:
	                                var num = +e.target.value || e.target.value;
	                                var graph = _.extend({}, this.props.graph, {
	                                    numSides: num,
	                                    coords: null,
	                                    snapTo: "grid" // reset the snap for
	                                                   // UNLIMITED, which only
	                                                   // supports "grid"
	                                });
	                                this.props.onChange({graph: graph});
	                            }.bind(this)}, 
	                            _.map(_.range(3, 13), function(n) {
	                                return React.createElement("option", {value: n}, n, " sides");
	                            }), 
	                            React.createElement("option", {value: UNLIMITED}, "unlimited sides")
	                        )
	                    ), 
	                    React.createElement("div", null, 
	                        React.createElement("label", null, " Snap to", ' ', 
	                            React.createElement("select", {
	                                key: "polygon-snap", 
	                                value: this.props.graph.snapTo, 
	                                onChange: function(e)  {
	                                    var graph = _.extend({},
	                                        this.props.graph,
	                                        {
	                                            snapTo: e.target.value,
	                                            coords: null
	                                        });
	                                    this.props.onChange({graph: graph});
	                                }.bind(this)}, 
	                                React.createElement("option", {value: "grid"}, "grid"), 
	                                (this.props.graph.numSides !== UNLIMITED) && [
	                                    React.createElement("option", {value: "angles"}, 
	                                        ' ', "interior angles", ' '
	                                    ),
	                                    React.createElement("option", {value: "sides"}, 
	                                        ' ', "side measures", ' '
	                                    )
	                                ]
	                            )
	                        ), 
	                        React.createElement(InfoTip, null, 
	                            React.createElement("p", null, "These options affect the movement of the vertex" + ' ' +
	                            "points. The grid option will guide the points to" + ' ' +
	                            "the nearest half step along the grid."), 

	                            React.createElement("p", null, "The interior angle and side measure options" + ' ' +
	                            "guide the points to the nearest whole angle or" + ' ' +
	                            "side"), " measure respectively.", ' '
	                        )
	                    ), 
	                    React.createElement("div", null, 
	                        React.createElement("label", null, "Show angle measures:", ' ', 
	                            React.createElement("input", {type: "checkbox", 
	                                checked: this.props.graph.showAngles, 
	                                onChange: this.toggleShowAngles})
	                        ), 
	                        React.createElement(InfoTip, null, 
	                            React.createElement("p", null, "Displays the interior angle measures.")
	                        )
	                    ), 
	                    React.createElement("div", null, 
	                        React.createElement("label", null, "Show side measures:", ' ', 
	                            React.createElement("input", {type: "checkbox", 
	                                checked: this.props.graph.showSides, 
	                                onChange: this.toggleShowSides})
	                        ), 
	                        React.createElement(InfoTip, null, 
	                            React.createElement("p", null, "Displays the side lengths.")
	                        )
	                    )
	                );
	            } else if (this.props.graph.type === "segment") {
	                extraOptions = React.createElement("select", {
	                        key: "segment-select", 
	                        value: this.props.graph.numSegments || 1, 
	                        onChange: function(e)  {
	                            var num = +e.target.value;
	                            this.props.onChange({
	                                graph: {
	                                    type: "segment",
	                                    numSegments: num,
	                                    coords: null
	                                }
	                            });
	                        }.bind(this)}, 
	                    _.map(_.range(1, 7), function(n) {
	                        return React.createElement("option", {value: n}, 
	                            n, " segment", n > 1 && "s"
	                        );
	                    })
	                );
	            } else if (this.props.graph.type === "angle") {
	                var allowReflexAngles = defaultVal(
	                    this.props.graph.allowReflexAngles,
	                    true
	                );
	                extraOptions = React.createElement("div", null, 
	                    React.createElement("div", null, 
	                        React.createElement("label", null, "Show angle measure:", ' ', 
	                            React.createElement("input", {type: "checkbox", 
	                                checked: this.props.graph.showAngles, 
	                                onChange: this.toggleShowAngles})
	                        )
	                    ), 
	                    React.createElement("div", null, 
	                        React.createElement("label", null, "Allow reflex angles:", ' ', 
	                            React.createElement("input", {type: "checkbox", 
	                                checked: allowReflexAngles, 
	                                onChange: function(newVal)  {
	                                    this.props.onChange({
	                                        graph: _.extend({}, this.props.graph, {
	                                            allowReflexAngles:
	                                                    !allowReflexAngles,
	                                            coords: null
	                                        })
	                                    });
	                                }.bind(this)})
	                        ), 
	                        React.createElement(InfoTip, null, 
	                            React.createElement("p", null, 
	                                "Reflex angles are angles with a measure" + ' ' +
	                                "greater than 180 degrees."
	                            ), 
	                            React.createElement("p", null, 
	                                "By default, these should remain enabled."
	                            )
	                        )
	                    ), 
	                    React.createElement("div", null, 
	                        React.createElement("label", null, "Snap to increments of", ' ', 
	                            React.createElement(NumberInput, {
	                                key: "degree-snap", 
	                                placeholder: 1, 
	                                value: this.props.graph.snapDegrees, 
	                                onChange: function(newVal)  {
	                                    this.props.onChange({
	                                        graph: _.extend({}, this.props.graph, {
	                                            snapDegrees: Math.abs(newVal),
	                                            coords: null
	                                        })
	                                    });
	                                }.bind(this)}), 
	                            ' ', "degrees", ' '
	                        )
	                    ), 
	                    React.createElement("div", null, 
	                        React.createElement("label", null, 
	                            ' ', "With an offset of", ' ', 
	                            React.createElement(NumberInput, {
	                                key: "angle-offset", 
	                                placeholder: 0, 
	                                value: this.props.graph.angleOffsetDeg, 
	                                onChange: function(newVal)  {
	                                    this.props.onChange({
	                                        graph: _.extend({}, this.props.graph, {
	                                            angleOffsetDeg: newVal,
	                                            coords: null
	                                        })
	                                    });
	                                }.bind(this)}), 
	                            ' ', "degrees", ' '
	                        )
	                    )
	                );
	            }
	        }

	        var box = this.props.box;

	        var instructions;
	        if (this.isClickToAddPoints() && this.state.shouldShowInstructions) {
	            if  (this.props.graph.type === "point") {
	                instructions = $._("Click to add points");
	            } else if (this.props.graph.type === "polygon") {
	                instructions = $._("Click to add vertices");
	            }
	        } else {
	            instructions = undefined;
	        }

	        var onMouseDown = this.isClickToAddPoints() ?
	            this.handleAddPointsMouseDown :
	            null;

	        var gridStep = this.props.gridStep || Util.getGridStep(
	                this.props.range,
	                this.props.step,
	                defaultBoxSize
	        );
	        var snapStep = this.props.snapStep || Util.snapStepFromGridStep(
	            gridStep
	        );

	        return React.createElement("div", {className: "perseus-widget " +
	                    "perseus-widget-interactive-graph", 
	                    style: {
	                        width: box[0],
	                        height: this.props.flexibleType ? "auto" : box[1]
	                    }}, 
	            React.createElement(Graph, {
	                instructions: instructions, 
	                ref: "graph", 
	                box: this.props.box, 
	                labels: this.props.labels, 
	                range: this.props.range, 
	                step: this.props.step, 
	                gridStep: gridStep, 
	                snapStep: snapStep, 
	                markings: this.props.markings, 
	                backgroundImage: this.props.backgroundImage, 
	                showProtractor: this.props.showProtractor, 
	                showRuler: this.props.showRuler, 
	                rulerLabel: this.props.rulerLabel, 
	                rulerTicks: this.props.rulerTicks, 
	                onMouseDown: onMouseDown, 
	                onNewGraphie: this.setGraphie}), 
	            typeSelect, extraOptions
	        );
	    },

	    setGraphie: function(newGraphie) {
	        this.graphie = newGraphie;
	        this.setupGraphie();
	    },

	    handleAddPointsMouseDown: function(coord) {
	        // This function should only be called when this.isClickToAddPoints()
	        // is true
	        if (!this.isClickToAddPoints()) {
	            throw new Error("handleAddPointsClick should not be registered" +
	                "when isClickToAddPoints() is false");
	        }
	        if (!this.isCoordInTrash(coord)) {
	            var point;
	            if (this.props.graph.type === "point") {
	                point = this.createPointForPointsType(
	                    coord,
	                    this.points.length
	                );
	                if (!point.constrain()) {
	                    point.remove();
	                    return;
	                }
	                this.points.push(point);

	                // interactive2 allows us to grab the point
	                var idx = this.points.length - 1;
	                this.points[idx].grab(coord);

	                this.updateCoordsFromPoints();
	            } else if (this.props.graph.type === "polygon") {
	                if (this.polygon.closed()) {
	                    return;
	                }
	                point = this.createPointForPolygonType(
	                    coord,
	                    this.points.length
	                );
	                this.points.push(point);

	                var idx = this.points.length - 1;
	                this.points[idx].grab(coord);

	                // We don't call updateCoordsFromPoints for
	                // polygons, since the polygon won't be
	                // closed yet.
	                this.updatePolygon();
	            }

	            this.setState({
	                shouldShowInstructions: false
	            });
	        }
	    },

	    resetGraphie: function() {
	        this.shouldResetGraphie = false;
	        this.refs.graph.reset();
	    },

	    setupGraphie: function() {
	        this.setTrashCanVisibility(0);
	        if (this.isClickToAddPoints()) {
	            this.setTrashCanVisibility(0.5);
	        }

	        var type = this.props.graph.type;
	        this["add" + capitalize(type) + "Controls"]();
	    },

	    setTrashCanVisibility: function(opacity) {
	        var graphie = this.graphie;

	        if (knumber.equal(opacity, 0)) {
	            if (this.trashCan) {
	                this.trashCan.remove();
	                this.trashCan = null;
	            }
	        } else {
	            if (!this.trashCan) {
	                this.trashCan = graphie.raphael.image(TRASH_ICON_URI,
	                    graphie.xpixels - 40,
	                    graphie.ypixels - 40,
	                    40,
	                    40
	                );
	            }
	            this.trashCan.attr({
	                opacity: opacity
	            });
	        }
	    },

	    componentWillReceiveProps: function(nextProps) {
	        if (this.isClickToAddPoints() !== this.isClickToAddPoints(nextProps)) {
	            this.shouldResetGraphie = true;
	            this.setState({
	                shouldShowInstructions:
	                        this._getShouldShowInstructions(nextProps)
	            });
	        }
	    },

	    isClickToAddPoints: function(props) {
	        props = props || this.props;
	        return (props.graph.type === "point" &&
	                props.graph.numPoints === UNLIMITED) ||
	               (props.graph.type === "polygon" &&
	                props.graph.numSides === UNLIMITED);
	    },

	    addLine: function(type) {
	        var self = this;
	        var graphie = self.graphie;
	        var coords = InteractiveGraph.getLineCoords(
	            self.props.graph,
	            self.props
	        );

	        var points = self.points = _.map(coords, function(coord)  {
	            return Interactive2.addMovablePoint(graphie, {
	                coord: coord,
	                constraints: [
	                    Interactive2.MovablePoint.constraints.bound(),
	                    Interactive2.MovablePoint.constraints.snap()
	                ],
	                onMove: function()  {
	                    var graph = _.extend({}, self.props.graph, {
	                        coords: _.invoke(points, "coord")
	                    });
	                    self.props.onChange({graph: graph});
	                },
	                normalStyle: {
	                    stroke: KhanUtil.INTERACTIVE,
	                    fill: KhanUtil.INTERACTIVE
	                }
	            });
	        });

	        var lineConfig = {
	            points: points,
	            static: true
	        };

	        if (type === "line") {
	            lineConfig.extendLine = true;
	        } else if (type === "ray") {
	            lineConfig.extendRay = true;
	        }

	        var line = self.line = Interactive2.addMovableLine(
	            graphie,
	            lineConfig
	        );

	        // A and B can't be in the same place
	        points[0].listen("constraints", "isLine", function(coord)  {
	            return !kpoint.equal(coord, points[1].coord());
	        });
	        points[1].listen("constraints", "isLine", function(coord)  {
	            return !kpoint.equal(coord, points[0].coord());
	        });
	    },

	    removeLine: function() {
	        _.invoke(this.points, "remove");
	        this.line.remove();
	    },

	    addLinearControls: function() {
	        this.addLine("line");
	    },

	    removeLinearControls: function() {
	        this.removeLine();
	    },

	    addQuadraticControls: function() {
	        var graphie = this.graphie;
	        var coords = this.props.graph.coords;
	        if (!coords) {
	            coords = InteractiveGraph.defaultQuadraticCoords(this.props);
	        }

	        var pointA;
	        var pointB;
	        var pointC;
	        var onMoveHandler = function()  {
	            var graph = _.extend({}, this.props.graph, {
	                coords: [pointA.coord(), pointB.coord(), pointC.coord()]
	            });
	            this.props.onChange({graph: graph});
	            this.updateQuadratic();
	        }.bind(this);

	        pointA = this.pointA = Interactive2.addMovablePoint(graphie, {
	            coord: coords[0],
	            constraints: [
	                Interactive2.MovablePoint.constraints.bound(),
	                Interactive2.MovablePoint.constraints.snap(),
	                function(coord)  {
	                    return !pointA || (coord[0] !== pointB.coord()[0] &&
	                                coord[0] !== pointC.coord()[0]);
	                }
	            ],
	            onMove: onMoveHandler
	        });

	        pointB = this.pointB = Interactive2.addMovablePoint(graphie, {
	            coord: coords[1],
	            constraints: [
	                Interactive2.MovablePoint.constraints.bound(),
	                Interactive2.MovablePoint.constraints.snap(),
	                function(coord)  {
	                    return !pointB || (coord[0] !== pointA.coord()[0] &&
	                                coord[0] !== pointC.coord()[0]);
	                }
	            ],
	            onMove: onMoveHandler
	        });

	        pointC = this.pointC = Interactive2.addMovablePoint(graphie, {
	            coord: coords[2],
	            constraints: [
	                Interactive2.MovablePoint.constraints.bound(),
	                Interactive2.MovablePoint.constraints.snap(),
	                function(coord)  {
	                    return !pointC || (coord[0] !== pointA.coord()[0] &&
	                                coord[0] !== pointB.coord()[0]);
	                }
	            ],
	            onMove: onMoveHandler
	        });

	        this.updateQuadratic();
	    },

	    updateQuadratic: function() {
	        var coeffs = InteractiveGraph.getCurrentQuadraticCoefficients(
	                this.props);
	        if (!coeffs) {
	            return;
	        }

	        // Extract coefficients the parabola
	        var a = coeffs[0], b = coeffs[1], c = coeffs[2];

	        // Plot and style
	        if (this.parabola) {
	            var path = this.graphie.svgParabolaPath(a, b, c);
	            this.parabola.attr({ path: path });
	        } else {
	            this.parabola = this.graphie.parabola(a, b, c);
	            this.parabola.attr({ stroke: KhanUtil.DYNAMIC });
	            this.parabola.toBack();
	        }
	    },

	    removeQuadraticControls: function() {
	        this.pointA.remove();
	        this.pointB.remove();
	        this.pointC.remove();
	        if (this.parabola) {
	            this.parabola.remove();
	            this.parabola = null;
	        }
	    },

	    addSinusoidControls: function() {
	        var graphie = this.graphie;
	        var coords = this.props.graph.coords;
	        if (!coords) {
	            coords = InteractiveGraph.defaultSinusoidCoords(this.props);
	        }

	        var pointA;
	        var pointB;
	        var onMoveHandler = function()  {
	            var graph = _.extend({}, this.props.graph, {
	                coords: [pointA.coord(), pointB.coord()]
	            });
	            this.props.onChange({graph: graph});
	            this.updateSinusoid();
	        }.bind(this);

	        pointA = this.pointA = Interactive2.addMovablePoint(graphie, {
	            coord: coords[0],
	            constraints: [
	                Interactive2.MovablePoint.constraints.bound(),
	                Interactive2.MovablePoint.constraints.snap(),
	                function(coord)  {
	                    return !pointA || coord[0] !== pointB.coord()[0];
	                }
	            ],
	            onMove: onMoveHandler
	        });

	        pointB = this.pointB = Interactive2.addMovablePoint(graphie, {
	            coord: coords[1],
	            constraints: [
	                Interactive2.MovablePoint.constraints.bound(),
	                Interactive2.MovablePoint.constraints.snap(),
	                function(coord)  {
	                    return !pointA || coord[0] !== pointA.coord()[0];
	                }
	            ],
	            onMove: onMoveHandler
	        });

	        this.updateSinusoid();
	    },

	    updateSinusoid: function() {
	        var coeffs = InteractiveGraph.getCurrentSinusoidCoefficients(
	                this.props);
	        if (!coeffs) {
	            return;
	        }

	        var a = coeffs[0], b = coeffs[1], c = coeffs[2], d = coeffs[3];

	        // Plot and style
	        if (this.sinusoid) {
	            var path = this.graphie.svgSinusoidPath(a, b, c, d);
	            this.sinusoid.attr({ path: path });
	        } else {
	            this.sinusoid = this.graphie.sinusoid(a, b, c, d);
	            this.sinusoid.attr({ stroke: KhanUtil.DYNAMIC });
	            this.sinusoid.toBack();
	        }
	    },

	    removeSinusoidControls: function() {
	        this.pointA.remove();
	        this.pointB.remove();
	        if (this.sinusoid) {
	            this.sinusoid.remove();
	            this.sinusoid = null;
	        }
	    },

	    addCircleControls: function() {
	        var graphie = this.graphie;
	        var minSnap = _.min(graphie.snap);

	        var circle = this.circle = graphie.addCircleGraph({
	            center: this.props.graph.center || [0, 0],
	            radius: this.props.graph.radius || _.min(this.props.step),
	            snapX: graphie.snap[0],
	            snapY: graphie.snap[1],
	            minRadius: minSnap * 2,
	            snapRadius: minSnap
	        });

	        $(circle).on("move", function()  {
	            var graph = _.extend({}, this.props.graph, {
	                center: circle.center,
	                radius: circle.radius
	            });
	            this.props.onChange({graph: graph});
	        }.bind(this));
	    },

	    removeCircleControls: function() {
	        this.circle.remove();
	    },

	    addLinearSystemControls: function() {
	        var graphie = this.graphie;
	        var coords = InteractiveGraph.getLinearSystemCoords(this.props.graph,
	            this.props);

	        var segmentColors = [KhanUtil.INTERACTIVE, KhanUtil.GREEN];
	        var points = this.points = _.map(coords,
	                function(segmentCoords, segmentIndex)  {
	            var segmentPoints = _.map(segmentCoords, function(coord, i)  {
	                return Interactive2.addMovablePoint(graphie, {
	                    coord: coord,
	                    constraints: [
	                        Interactive2.MovablePoint.constraints.bound(),
	                        Interactive2.MovablePoint.constraints.snap(),
	                        function(coord)  {
	                            if (!segmentPoints) {
	                                // points hasn't been defined yet because
	                                // we're still creating them
	                                return;
	                            }
	                            return !kpoint.equal(
	                                coord,
	                                segmentPoints[1 - i].coord()
	                            );
	                        }
	                    ],
	                    onMove: function()  {
	                        var graph = _.extend({}, this.props.graph, {
	                            coords: _.map(
	                                this.points,
	                                function(segment)  {return _.invoke(segment, "coord");}
	                            )
	                        });
	                        this.props.onChange({graph: graph});
	                    }.bind(this),
	                    normalStyle: {
	                        stroke: segmentColors[segmentIndex],
	                        fill: segmentColors[segmentIndex]
	                    }
	                });
	            }.bind(this));
	            return segmentPoints;
	        }.bind(this));

	        var lines = this.lines = _.map(points,
	                function(segmentPoints, segmentIndex)  {
	            return Interactive2.addMovableLine(graphie, {
	                points: segmentPoints,
	                static: true,
	                extendLine: true,
	                normalStyle: {
	                    stroke: segmentColors[segmentIndex]
	                }
	            });
	        });
	    },

	    removeLinearSystemControls: function() {
	        _.invoke(this.lines, "remove");
	        _.map(this.points, function(segment)  {return _.invoke(segment, "remove");});
	    },

	    isCoordInTrash: function(coord) {
	        var graphie = this.graphie;
	        var screenPoint = graphie.scalePoint(coord);
	        return screenPoint[0] >= graphie.xpixels - 40 &&
	                screenPoint[1] >= graphie.ypixels - 40;
	    },

	    createPointForPointsType: function(coord, i) {
	        var self = this;
	        var graphie = self.graphie;
	        var point = Interactive2.addMovablePoint(graphie, {
	            coord: coord,
	            constraints: [
	                Interactive2.MovablePoint.constraints.bound(),
	                Interactive2.MovablePoint.constraints.snap(),
	                function(coord) {
	                    // TODO(jack): There ought to be a
	                    // MovablePoint.constraints.avoid
	                    // default that lets you do things like this
	                    return _.all(self.points, function(pt) {
	                        return point === pt ||
	                            !kpoint.equal(coord, pt.coord());
	                    });
	                }
	            ],
	            onMoveStart: function() {
	                if (self.isClickToAddPoints()) {
	                    self.setTrashCanVisibility(1);
	                }
	            },
	            onMove: self.updateCoordsFromPoints,
	            onMoveEnd: function(coord) {
	                if (self.isClickToAddPoints()) {
	                    if (self.isCoordInTrash(coord)) {
	                        // remove this point from points
	                        self.points = _.filter(self.points, function(pt) {
	                            return pt !== point;
	                        });
	                        // update the correct answer box
	                        self.updateCoordsFromPoints();

	                        // remove this movablePoint from graphie.
	                        // we wait to do this until we're not inside of
	                        // said point's onMoveEnd method so its state is
	                        // consistent throughout this method call
	                        setTimeout(point.remove.bind(point), 0);
	                    }
	                    // In case we mouseup'd off the graphie and that
	                    // stopped the move (in which case, we might not
	                    // be in isCoordInTrash()
	                    self.setTrashCanVisibility(0.5);
	                }
	            },
	            normalStyle: {
	                stroke: KhanUtil.INTERACTIVE,
	                fill: KhanUtil.INTERACTIVE
	            }
	        });

	        return point;
	    },

	    removePoint: function(point) {
	        var index = null;
	        this.points = _.filter(this.points, function(pt, i) {
	            if (pt === point) {
	                index = i;
	                return false;
	            } else {
	                return true;
	            }
	        });
	        return index;
	    },

	    createPointForPolygonType: function(coord, i) {
	        var graphie = this.graphie;

	        // TODO(alex): check against "grid" instead, use constants
	        var snapToGrid = !_.contains(["angles", "sides"],
	            this.props.graph.snapTo);

	        // Index relative to current point -> absolute index
	        // NOTE: This does not work when isClickToAddPoints() == true,
	        // as `i` can be changed by dragging a point to the trash
	        // Currently this function is only called when !isClickToAddPoints()
	        var rel = function(j)  {
	            return (i + j + this.points.length) % this.points.length;
	        }.bind(this);

	        var onMoveEndHandler = function(coord)  {
	            if (this.isClickToAddPoints()) {
	                if (this.isCoordInTrash(coord)) {
	                    // remove this point from points
	                    var index = this.removePoint(point);
	                    if (this.polygon.closed()) {
	                        this.points = rotate(this.points, index);
	                        this.polygon.update({closed: false});
	                    }
	                    this.updatePolygon();
	                    // the polygon is now unclosed, so we need to
	                    // remove any points props
	                    this.clearCoords();

	                    // remove this movablePoint from graphie.
	                    // wait to do this until we're not inside of
	                    // said point's onMoveEnd method so state is
	                    // consistent throughout the method call
	                    setTimeout(point.remove.bind(point), 0);
	                } else if (this.points.length > 1 && ((
	                            point === this.points[0] &&
	                            kpoint.equal(
	                                coord,
	                                _.last(this.points).coord()
	                            )
	                        ) || (
	                            point === _.last(this.points) &&
	                            kpoint.equal(
	                                coord,
	                                this.points[0].coord()
	                            )
	                        ))) {
	                    // If the user clicked and dragged a point over endpoint,
	                    // join the them
	                    var pointToRemove = this.points.pop();
	                    if (this.points.length > 2) {
	                        this.polygon.update({closed: true});
	                        this.updateCoordsFromPoints();
	                    } else {
	                        this.polygon.update({closed: false});
	                        this.clearCoords();
	                    }
	                    this.updatePolygon();
	                    // remove this movablePoint from graphie.
	                    // wait to do this until we're not inside of
	                    // said point's onMoveEnd method so state is
	                    // consistent throughout the method call
	                    setTimeout(pointToRemove.remove.bind(
	                        pointToRemove), 0);
	                } else {
	                    // If the user clicked and dragged a point over any other
	                    // existing point, fix shape
	                    var shouldRemove = _.any(this.points, function(pt) {
	                        return pt !== point && kpoint.equal(
	                            pt.coord(), coord);
	                    });
	                    if (shouldRemove) {
	                        this.removePoint(point);

	                        if (this.points.length < 3) {
	                            this.polygon.update({closed: false});
	                            this.clearCoords();
	                        } else if (this.polygon.closed()) {
	                            this.updateCoordsFromPoints();
	                        }
	                        this.updatePolygon();
	                        // remove this movablePoint from graphie.
	                        // wait to do this until we're not inside
	                        // said point's onMoveEnd method so state
	                        // is consistent throughout the method call
	                        setTimeout(point.remove.bind(point), 0);
	                    } else {
	                        // If this was
	                        //  * not a deletion
	                        //  * and a click on the first or last point
	                        //  * and not a drag,
	                        //  * and not a creation of a new point
	                        //    (see !point.state.isInitialMove, below),
	                        //  * and our polygon is not closed,
	                        //  * and we can close it (we need at least 3 points),
	                        // then close it
	                        if ((point === this.points[0] ||
	                                point === _.last(this.points)) &&
	                                !point.hasMoved() &&
	                                !point.state.isInitialMove &&
	                                !this.polygon.closed() &&
	                                this.points.length > 2) {

	                            this.polygon.update({closed: true});
	                            this.updatePolygon();

	                            // We finally have a closed polygon, so save our
	                            // points to props
	                            this.updateCoordsFromPoints();
	                        }
	                    }
	                }

	                // In case we mouseup'd off the graphie and that
	                // stopped the move
	                this.setTrashCanVisibility(0.5);
	            }

	            point.state.isInitialMove = false;
	        }.bind(this);

	        var graphConstraint = function(coord)  {
	            // These constraints are all relative to the other points, so if
	            // we're creating the initial points and haven't added any others
	            // to the graph, we can't enforce them.
	            if (this.points == null || this.points.length === 0) {
	                return true;
	            }

	            var coords = _.invoke(this.points, "coord");
	            coords[i] = coord;

	            // Check for invalid positioning, but only if we aren't adding
	            // points one click at a time, since those added points could
	            // have already violated these constraints
	            if (!this.isClickToAddPoints()) {
	                // Polygons can't have consecutive collinear points
	                if (collinear(coords[rel(-2)], coords[rel(-1)], coords[i]) ||
	                    collinear(coords[rel(-1)], coords[i],  coords[rel(1)]) ||
	                    collinear(coords[i],  coords[rel(1)],  coords[rel(2)])) {
	                    return false;
	                }

	                var segments = _.zip(coords, rotate(coords));

	                if (this.points.length > 3) {
	                    // Constrain to simple (non self-intersecting) polygon by
	                    // testing whether adjacent segments intersect any others
	                    for (var j = -1; j <= 0; j++) {
	                        var segment = segments[rel(j)];
	                        var others = _.without(segments,
	                            segment, segments[rel(j-1)], segments[rel(j+1)]);

	                        for (var k = 0; k < others.length; k++) {
	                            var other = others[k];
	                            if (intersects(segment, other)) {
	                                return false;
	                            }
	                        }
	                    }
	                }
	            }

	            if (this.props.graph.snapTo === "angles" &&
	                    this.points.length > 2) {
	                // Snap to whole degree interior angles

	                var angles = _.map(angleMeasures(coords), function(rad) {
	                    return rad * 180 / Math.PI;
	                });

	                _.each([-1, 1], function(j) {
	                    angles[rel(j)] = Math.round(angles[rel(j)]);
	                });

	                var getAngle = function(a, vertex, b) {
	                    var angle = KhanUtil.findAngle(
	                        coords[rel(a)], coords[rel(b)], coords[rel(vertex)]
	                    );
	                    return (angle + 360) % 360;
	                };

	                var innerAngles = [
	                    angles[rel(-1)] - getAngle(-2, -1, 1),
	                    angles[rel(1)] - getAngle(-1, 1, 2)
	                ];
	                innerAngles[2] = 180 - (innerAngles[0] + innerAngles[1]);

	                // Avoid degenerate triangles
	                if (_.any(innerAngles, function(angle) {
	                            return leq(angle, 1);
	                        })) {
	                    return false;
	                }

	                var knownSide = magnitude(vector(coords[rel(-1)],
	                    coords[rel(1)]));

	                var onLeft = sign(ccw(
	                    coords[rel(-1)], coords[rel(1)], coords[i]
	                )) === 1;

	                // Solve for side by using the law of sines
	                var side = Math.sin(innerAngles[1] * Math.PI / 180) /
	                    Math.sin(innerAngles[2] * Math.PI / 180) * knownSide;

	                var outerAngle = KhanUtil.findAngle(coords[rel(1)],
	                    coords[rel(-1)]);

	                var offset = this.graphie.polar(
	                    side,
	                    outerAngle + (onLeft? 1 : -1) * innerAngles[0]
	                );

	                return this.graphie.addPoints(coords[rel(-1)], offset);
	            } else if (this.props.graph.snapTo === "sides" &&
	                    this.points.length > 1) {
	                // Snap to whole unit side measures

	                var sides = _.map([
	                    [coords[rel(-1)], coords[i]],
	                    [coords[i], coords[rel(1)]],
	                    [coords[rel(-1)], coords[rel(1)]]
	                ], function(coords) {
	                    return magnitude(vector.apply(null, coords));
	                });

	                _.each([0, 1], function(j) {
	                    sides[j] = Math.round(sides[j]);
	                });

	                // Avoid degenerate triangles
	                if (leq(sides[1] + sides[2], sides[0]) ||
	                        leq(sides[0] + sides[2], sides[1]) ||
	                        leq(sides[0] + sides[1], sides[2])) {
	                    return false;
	                }

	                // Solve for angle by using the law of cosines
	                var innerAngle = lawOfCosines(sides[0],
	                    sides[2], sides[1]);

	                var outerAngle = KhanUtil.findAngle(coords[rel(1)],
	                    coords[rel(-1)]);

	                var onLeft = sign(ccw(
	                    coords[rel(-1)], coords[rel(1)], coords[i]
	                )) === 1;

	                var offset = this.graphie.polar(
	                    sides[0],
	                    outerAngle + (onLeft ? 1 : -1) * innerAngle
	                );

	                return this.graphie.addPoints(coords[rel(-1)], offset);
	            } else {
	                // Snap to grid (already done)
	                return true;
	            }
	        }.bind(this);

	        var point = Interactive2.addMovablePoint(graphie, {
	            coord: coord,
	            constraints: [
	                Interactive2.MovablePoint.constraints.bound(),
	                snapToGrid ? Interactive2.MovablePoint.constraints.snap() :
	                             null,
	                graphConstraint
	            ],
	            onMoveStart: function()  {
	                if (this.isClickToAddPoints()) {
	                    this.setTrashCanVisibility(1);
	                }
	            }.bind(this),
	            onMove: function()  {
	                if (this.polygon.closed()) {
	                    this.updateCoordsFromPoints();
	                }
	            }.bind(this),
	            onMoveEnd: onMoveEndHandler,
	            normalStyle: {
	                stroke: KhanUtil.INTERACTIVE,
	                fill: KhanUtil.INTERACTIVE
	            }
	        });
	        point.state.isInitialMove = true;

	        return point;
	    },

	    updateCoordsFromPoints: function() {
	        var graph = _.extend({}, this.props.graph, {
	            // Handle old movable points with .coord, or
	            // Interactive2.MovablePoint's with .coord()
	            coords: _.map(this.points, function(point) {
	                return _.result(point, "coord");
	            })
	        });
	        this.props.onChange({graph: graph});
	    },

	    clearCoords: function() {
	        var graph = _.extend({}, this.props.graph, {
	            coords: null
	        });
	        this.props.onChange({graph: graph});
	    },

	    addPointControls: function() {
	        var coords = InteractiveGraph.getPointCoords(
	            this.props.graph,
	            this.props
	        );
	        // Clear out our old points so that newly added points don't
	        // "collide" with them and reposition when being added
	        // Without this, when added, each point checks whether it is on top
	        // of a point in this.points, which (a) shouldn't matter since
	        // we're clearing out this.points anyways, and (b) can cause problems
	        // if each of this.points is a MovablePoint instead of an
	        // Interactive2.MovablePoint, since one has a .coord and the other
	        // has .coord()
	        // TODO(jack): Figure out a better way to do this
	        this.points = [];
	        this.points = _.map(coords, this.createPointForPointsType, this);
	    },

	    removePointControls: function() {
	        _.invoke(this.points, "remove");
	    },

	    addSegmentControls: function() {
	        var self = this;
	        var graphie = this.graphie;

	        var coords = InteractiveGraph.getSegmentCoords(
	            this.props.graph,
	            this.props
	        );

	        this.points = [];
	        this.lines = _.map(coords, function(segment, i) {
	            var updateCoordProps = function() {
	                var graph = _.extend({}, self.props.graph, {
	                    coords: _.invoke(self.lines, "coords")
	                });
	                self.props.onChange({graph: graph});
	            };

	            var points = _.map(segment, function(coord, i) {
	                return Interactive2.addMovablePoint(graphie, {
	                    coord: coord,
	                    normalStyle: {
	                        stroke: KhanUtil.INTERACTIVE,
	                        fill: KhanUtil.INTERACTIVE
	                    },
	                    constraints: [
	                        Interactive2.MovablePoint.constraints.bound(),
	                        Interactive2.MovablePoint.constraints.snap(),
	                        function(coord)  {
	                            if (!points) {
	                                // points hasn't been defined yet because
	                                // we're still creating them
	                                return;
	                            }
	                            return !kpoint.equal(coord, points[1 - i].coord());
	                        }
	                    ],
	                    onMove: updateCoordProps
	                });
	            });

	            self.points = self.points.concat(points);
	            var line = Interactive2.addMovableLine(graphie, {
	                points: points,
	                static: false,
	                constraints: [
	                    Interactive2.MovableLine.constraints.bound(),
	                    Interactive2.MovableLine.constraints.snap()
	                ],
	                onMove: [
	                    Interactive2.MovableLine.onMove.updatePoints,
	                    updateCoordProps
	                ],
	                normalStyle: {
	                    stroke: KhanUtil.INTERACTIVE
	                },
	                highlightStyle: {
	                    stroke: KhanUtil.INTERACTING
	                }
	            });
	            _.invoke(points, "toFront");

	            return line;
	        }, this);
	    },

	    removeSegmentControls: function() {
	        _.invoke(this.points, "remove");
	        _.invoke(this.lines, "remove");
	    },

	    addRayControls: function() {
	        this.addLine("ray");
	    },

	    removeRayControls: function() {
	        this.removeLine();
	    },

	    addPolygonControls: function() {
	        this.polygon = null;
	        var coords = InteractiveGraph.getPolygonCoords(
	            this.props.graph,
	            this.props
	        );
	        // Clear out our old points so that newly added points don't
	        // "collide", as in `addPointControls`
	        this.points = [];
	        this.points = _.map(coords, this.createPointForPolygonType, this);
	        this.updatePolygon();
	    },

	    updatePolygon: function() {
	        var closed;
	        if (this.polygon) {
	            closed = this.polygon.closed();
	        } else if (this.points.length >= 3) {
	            closed = true;
	        } else {
	            // There will only be fewer than 3 points in click-to-add-vertices
	            // mode, so we don't need to explicitly check for that here.
	            closed = false;
	        }

	        var graphie = this.graphie;
	        var n = this.points.length;

	        // TODO(alex): check against "grid" instead, use constants
	        var snapToGrid = !_.contains(["angles", "sides"],
	            this.props.graph.snapTo);

	        var angleLabels = _.times(n, function(i) {
	            if (!this.props.graph.showAngles ||
	                    (!closed && (i === 0 || i === n - 1))) {
	                return "";
	            } else if (this.props.graph.snapTo === "angles") {
	                return "$deg0";
	            } else {
	                return "$deg1";
	            }
	        }, this);

	        var showRightAngleMarkers = _.times(n, function(i) {
	            return closed || (i !== 0 && i !== n - 1);
	        }, this);

	        var numArcs = _.times(n, function(i) {
	            if (this.props.graph.showAngles &&
	                    (closed || (i !== 0 && i !== n - 1))) {
	                return 1;
	            } else {
	                return 0;
	            }
	        }, this);

	        var sideLabels = _.times(n, function(i) {
	            if (!this.props.graph.showSides ||
	                (!closed && i === n - 1)) {
	                return "";
	            } else if (this.props.graph.snapTo === "sides") {
	                return "$len0";
	            } else {
	                return "$len1";
	            }
	        }, this);

	        if (this.polygon == null) {
	            var self = this;
	            self.polygon = Interactive2.addMovablePolygon(graphie, {
	                constraints: [
	                    Interactive2.MovablePolygon.constraints.bound(),
	                    snapToGrid ? Interactive2.MovablePolygon.constraints.snap()
	                               : null
	                ],
	                closed: closed,
	                points: self.points,
	                angleLabels: angleLabels,
	                showRightAngleMarkers: showRightAngleMarkers,
	                numArcs: numArcs,
	                sideLabels: sideLabels,
	                onMove: [
	                    Interactive2.MovablePolygon.onMove.updatePoints,
	                    function() {
	                        if (this.closed()) {
	                            self.updateCoordsFromPoints();
	                        }
	                    }
	                ]
	            });
	        } else {
	            // We only need to pass in the properties that might've changed
	            this.polygon.update({
	                closed: closed,
	                points: this.points,
	                angleLabels: angleLabels,
	                showRightAngleMarkers: showRightAngleMarkers,
	                numArcs: numArcs,
	                sideLabels: sideLabels
	            });
	        }
	    },

	    removePolygonControls: function() {
	        _.invoke(this.points, "remove");
	        this.polygon.remove();
	    },

	    addAngleControls: function() {
	        var graphie = this.graphie;

	        var coords = InteractiveGraph.getAngleCoords(
	            this.props.graph,
	            this.props
	        );

	        // The vertex snaps to the grid, but the rays don't...
	        this.points = _.map(coords, function(coord, i) {
	            return graphie.addMovablePoint(_.extend({
	                coord: coord,
	                normalStyle: {
	                    stroke: KhanUtil.INTERACTIVE,
	                    fill: KhanUtil.INTERACTIVE
	                }
	            }, i === 1 ? {
	                snapX: graphie.snap[0],
	                snapY: graphie.snap[1]
	            } : {}));
	        });

	        // ...they snap to whole-degree angles from the vertex.
	        this.angle = graphie.addMovableAngle({
	            points: this.points,
	            snapDegrees: this.props.graph.snapDegrees || 1,
	            snapOffsetDeg: this.props.graph.angleOffsetDeg || 0,
	            angleLabel: this.props.graph.showAngles ? "$deg0" : "",
	            pushOut: 2,
	            allowReflex: defaultVal(this.props.graph.allowReflexAngles, true)
	        });

	        $(this.angle).on("move", function()  {
	            var graph = _.extend({}, this.props.graph, {
	                coords: this.angle.getClockwiseCoords()
	            });
	            this.props.onChange({graph: graph});
	        }.bind(this));
	    },

	    removeAngleControls: function() {
	        _.invoke(this.points, "remove");
	        this.angle.remove();
	    },

	    toggleShowAngles: function() {
	        var graph = _.extend({}, this.props.graph, {
	            showAngles: !this.props.graph.showAngles
	        });
	        this.props.onChange({graph: graph});
	    },

	    toggleShowSides: function() {
	        var graph = _.extend({}, this.props.graph, {
	            showSides: !this.props.graph.showSides
	        });
	        this.props.onChange({graph: graph});
	    },

	    getUserInput: function() {
	        return this.props.graph;
	    },

	    simpleValidate: function(rubric) {
	        return InteractiveGraph.validate(this.getUserInput(), rubric, this);
	    },

	    focus: $.noop,

	    statics: {
	        displayMode: "block"
	    }
	});


	_.extend(InteractiveGraph, {
	    getQuadraticCoefficients: function(coords) {
	        var p1 = coords[0];
	        var p2 = coords[1];
	        var p3 = coords[2];

	        var denom = (p1[0] - p2[0]) * (p1[0] - p3[0]) * (p2[0] - p3[0]);
	        if (denom === 0) {
	            return;
	        }
	        var a = (p3[0] * (p2[1] - p1[1]) +
	                 p2[0] * (p1[1] - p3[1]) +
	                 p1[0] * (p3[1] - p2[1])) / denom;
	        var b = ((p3[0] * p3[0]) * (p1[1] - p2[1]) +
	                 (p2[0] * p2[0]) * (p3[1] - p1[1]) +
	                 (p1[0] * p1[0]) * (p2[1] - p3[1])) / denom;
	        var c = (p2[0] * p3[0] * (p2[0] - p3[0]) * p1[1] +
	                 p3[0] * p1[0] * (p3[0] - p1[0]) * p2[1] +
	                 p1[0] * p2[0] * (p1[0] - p2[0]) * p3[1]) / denom;
	        return [a, b, c];
	    },

	    getSinusoidCoefficients: function(coords) {
	        // It's assumed that p1 is the root and p2 is the first peak
	        var p1 = coords[0];
	        var p2 = coords[1];

	        // Resulting coefficients are canonical for this sine curve
	        var amplitude = (p2[1] - p1[1]);
	        var angularFrequency = Math.PI / (2 * (p2[0] - p1[0]));
	        var phase = p1[0] * angularFrequency;
	        var verticalOffset = p1[1];

	        return [amplitude, angularFrequency, phase, verticalOffset];
	    },


	    /**
	     * @param {object} graph Like props.graph or props.correct
	     * @param {object} props of an InteractiveGraph instance
	     */
	    getLineCoords: function(graph, props) {
	        return graph.coords || InteractiveGraph.pointsFromNormalized(
	            props,
	            [
	                [0.25, 0.75],
	                [0.75, 0.75]
	            ]
	        );
	    },

	    /**
	     * @param {object} graph Like props.graph or props.correct
	     * @param {object} props of an InteractiveGraph instance
	     */
	    getPointCoords: function(graph, props) {
	        var numPoints = graph.numPoints || 1;
	        var coords = graph.coords;

	        if (coords) {
	            return coords;
	        } else {
	            switch (numPoints) {
	                case 1:
	                    // Back in the day, one point's coords were in graph.coord
	                    coords = [graph.coord || [0, 0]];
	                    break;
	                case 2:
	                    coords = [[-5, 0], [5, 0]];
	                    break;
	                case 3:
	                    coords = [[-5, 0], [0, 0], [5, 0]];
	                    break;
	                case 4:
	                    coords = [[-6, 0], [-2, 0], [2, 0], [6, 0]];
	                    break;
	                case 5:
	                    coords = [[-6, 0], [-3, 0], [0, 0], [3, 0], [6, 0]];
	                    break;
	                case 6:
	                    coords = [[-5, 0], [-3, 0], [-1, 0], [1, 0], [3, 0],
	                              [5, 0]];
	                    break;
	                case UNLIMITED:
	                    coords = [];
	                    break;
	            }
	            // Transform coords from their -10 to 10 space to 0 to 1
	            // because of the old graph.coord, and also it's easier.
	            var range = [[-10, 10], [-10, 10]];
	            coords = InteractiveGraph.normalizeCoords(coords, range);

	            var coords = InteractiveGraph.pointsFromNormalized(props, coords);
	            return coords;
	        }
	    },

	    /**
	     * @param {object} graph Like props.graph or props.correct
	     * @param {object} props of an InteractiveGraph instance
	     */
	    getLinearSystemCoords: function(graph, props) {
	        return graph.coords ||
	            _.map([
	                [[0.25, 0.75], [0.75, 0.75]],
	                [[0.25, 0.25], [0.75, 0.25]]
	            ], function(coords)  {
	                return InteractiveGraph.pointsFromNormalized(props, coords);
	            });
	    },

	    /**
	     * @param {object} graph Like props.graph or props.correct
	     * @param {object} props of an InteractiveGraph instance
	     */
	    getPolygonCoords: function(graph, props) {
	        var coords = graph.coords;
	        if (coords) {
	            return coords;
	        }

	        var n = graph.numSides || 3;

	        if (n === UNLIMITED) {
	            coords = [];
	        } else {
	            var angle = 2 * Math.PI / n;
	            var offset = (1 / n - 1 / 2) * Math.PI;

	            // TODO(alex): Generalize this to more than just triangles so that
	            // all polygons have whole number side lengths if snapping to sides
	            var radius = graph.snapTo === "sides" ? Math.sqrt(3) / 3 * 7: 4;

	            // Generate coords of a regular polygon with n sides
	            coords = _.times(n, function(i) {
	                return [
	                    radius * Math.cos(i * angle + offset),
	                    radius * Math.sin(i * angle + offset)
	                ];
	            });
	        }

	        var range = [[-10, 10], [-10, 10]];
	        coords = InteractiveGraph.normalizeCoords(coords, range);

	        var snapToGrid = !_.contains(["angles", "sides"], graph.snapTo);
	        coords = InteractiveGraph.pointsFromNormalized(props, coords,
	            /* noSnap */ !snapToGrid);

	        return coords;
	    },

	    /**
	     * @param {object} graph Like props.graph or props.correct
	     * @param {object} props of an InteractiveGraph instance
	     */
	    getSegmentCoords: function(graph, props) {
	        var coords = graph.coords;
	        if (coords) {
	            return coords;
	        }

	        var n = graph.numSegments || 1;
	        var ys = {
	            1: [5],
	            2: [5, -5],
	            3: [5, 0, -5],
	            4: [6, 2, -2, -6],
	            5: [6, 3, 0, -3, -6],
	            6: [5, 3, 1, -1, -3, -5]
	        }[n];
	        var range = [[-10, 10], [-10, 10]];

	        return _.map(ys, function(y) {
	            var segment = [[-5, y], [5, y]];
	            segment = InteractiveGraph.normalizeCoords(segment, range);
	            segment = InteractiveGraph.pointsFromNormalized(props, segment);
	            return segment;
	        });
	    },

	    /**
	     * @param {object} graph Like props.graph or props.correct
	     * @param {object} props of an InteractiveGraph instance
	     */
	    getAngleCoords: function(graph, props) {
	        var coords = graph.coords;
	        if (coords) {
	            return coords;
	        }

	        var snap = graph.snapDegrees || 1;
	        var angle = snap;
	        while (angle < 20) {
	            angle += snap;
	        }
	        angle = angle * Math.PI / 180;
	        var offset = (graph.angleOffsetDeg || 0) * Math.PI / 180;

	        coords = InteractiveGraph.pointsFromNormalized(props, [
	            [0.85, 0.50],
	            [0.5, 0.50]
	        ]);

	        var radius = magnitude(vector.apply(null, coords));

	        // Adjust the lower point by angleOffsetDeg degrees
	        coords[0] = [
	            coords[1][0] + radius * Math.cos(offset),
	            coords[1][1] + radius * Math.sin(offset)
	        ];
	        // Position the upper point angle radians from the
	        // lower point
	        coords[2] = [
	            coords[1][0] + radius * Math.cos(angle + offset),
	            coords[1][1] + radius * Math.sin(angle + offset)
	        ];

	        return coords;
	    },

	    normalizeCoords: function(coordsList, range) {
	        return _.map(coordsList, function(coords) {
	            return _.map(coords, function(coord, i) {
	                var extent = range[i][1] - range[i][0];
	                return ((coord + range[i][1]) / extent);
	            });
	        });
	    },

	    getEquationString: function(props) {
	        var type = props.graph.type;
	        var funcName = "get" + capitalize(type) + "EquationString";
	        return InteractiveGraph[funcName](props);
	    },

	    pointsFromNormalized: function(props, coordsList, noSnap) {
	        return _.map(coordsList, function(coords) {
	            return _.map(coords, function(coord, i) {
	                var range = props.range[i];
	                if (noSnap) {
	                    return range[0] + (range[1] - range[0]) * coord;
	                } else {
	                    var step = props.step[i];
	                    var nSteps = numSteps(range, step);
	                    var tick = Math.round(coord * nSteps);
	                    return range[0] + step * tick;
	                }
	            });
	        });
	    },

	    getLinearEquationString: function(props) {
	        var coords = InteractiveGraph.getLineCoords(props.graph, props);
	        if (eq(coords[0][0], coords[1][0])) {
	            return "x = " + coords[0][0].toFixed(3);
	        } else {
	            var m = (coords[1][1] - coords[0][1]) /
	                    (coords[1][0] - coords[0][0]);
	            var b = coords[0][1] - m * coords[0][0];
	            if (eq(m, 0)) {
	                return "y = " + b.toFixed(3);
	            } else {
	                return "y = " + m.toFixed(3) + "x + " + b.toFixed(3);
	            }
	        }
	    },

	    getCurrentQuadraticCoefficients: function(props) {
	        // TODO(alpert): Don't duplicate
	        var coords = props.graph.coords ||
	                InteractiveGraph.defaultQuadraticCoords(props);
	        return InteractiveGraph.getQuadraticCoefficients(coords);
	    },

	    defaultQuadraticCoords: function(props) {
	        var coords = [[0.25, 0.75], [0.5, 0.25], [0.75, 0.75]];
	        return InteractiveGraph.pointsFromNormalized(props, coords);
	    },

	    getQuadraticEquationString: function(props) {
	        var coeffs = InteractiveGraph.getCurrentQuadraticCoefficients(
	                props);
	        return "y = " + coeffs[0].toFixed(3) + "x^2 + " +
	                        coeffs[1].toFixed(3) + "x + " +
	                        coeffs[2].toFixed(3);
	    },

	    getCurrentSinusoidCoefficients: function(props) {
	        var coords = props.graph.coords ||
	                InteractiveGraph.defaultSinusoidCoords(props);
	        return InteractiveGraph.getSinusoidCoefficients(coords);
	    },

	    defaultSinusoidCoords: function(props) {
	        var coords = [[0.5, 0.5], [0.65, 0.60]];
	        return InteractiveGraph.pointsFromNormalized(props, coords);
	    },

	    getSinusoidEquationString: function(props) {
	        var coeffs = InteractiveGraph.getCurrentSinusoidCoefficients(
	                props);
	        return "y = " + coeffs[0].toFixed(3) + "sin(" +
	                        coeffs[1].toFixed(3) + "x - " +
	                        coeffs[2].toFixed(3) + ") + " +
	                        coeffs[3].toFixed(3);
	    },

	    getCircleEquationString: function(props) {
	        var graph = props.graph;
	        // TODO(alpert): Don't duplicate
	        var center = graph.center || [0, 0];
	        var radius = graph.radius || 2;
	        return "center (" + center[0] + ", " + center[1] + "), radius " +
	                radius;
	    },

	    getLinearSystemEquationString: function(props) {
	        var coords = InteractiveGraph.getLinearSystemCoords(
	            props.graph,
	            props
	        );
	        return "\n" +
	            getLineEquation(coords[0][0], coords[0][1]) +
	            "\n" +
	            getLineEquation(coords[1][0], coords[1][1]) +
	            "\n" +
	            getLineIntersection(coords[0], coords[1]);
	    },

	    getPointEquationString: function(props) {
	        var coords = InteractiveGraph.getPointCoords(props.graph, props);
	        return coords.map(function(coord) {
	            return "(" + coord[0] + ", " + coord[1] + ")";
	        }).join(", ");
	    },

	    getSegmentEquationString: function(props) {
	        var segments = InteractiveGraph.getSegmentCoords(props.graph,
	            props);
	        return _.map(segments, function(segment) {
	            return "[" +
	                _.map(segment, function(coord) {
	                    return "(" + coord.join(", ") + ")";
	                }).join(" ") +
	            "]";
	        }).join(" ");
	    },

	    getRayEquationString: function(props) {
	        var coords = InteractiveGraph.getLineCoords(props.graph, props);
	        var a = coords[0];
	        var b = coords[1];
	        var eq = InteractiveGraph.getLinearEquationString(props);

	        if (a[0] > b[0]) {
	            eq += " (for x <= " + a[0].toFixed(3) + ")";
	        } else if (a[0] < b[0]) {
	            eq += " (for x >= " + a[0].toFixed(3) + ")";
	        } else if (a[1] > b[1]) {
	            eq += " (for y <= " + a[1].toFixed(3) + ")";
	        } else {
	            eq += " (for y >= " + a[1].toFixed(3) + ")";
	        }

	        return eq;
	    },

	    getPolygonEquationString: function(props) {
	        var coords = InteractiveGraph.getPolygonCoords(
	            props.graph,
	            props
	        );
	        return _.map(coords, function(coord) {
	            return "(" + coord.join(", ") + ")";
	        }).join(" ");
	    },

	    getAngleEquationString: function(props) {
	        var coords = InteractiveGraph.getAngleCoords(props.graph, props);
	        var angle = KhanUtil.findAngle(coords[2], coords[0], coords[1]);
	        return angle.toFixed(0) + "\u00B0 angle" +
	                " at (" + coords[1].join(", ") + ")";
	    },

	    validate: function(state, rubric, component) {
	        // TODO(alpert): Because this.props.graph doesn't always have coords,
	        // check that .coords exists here, which is always true when something
	        // has moved
	        if (state.type === rubric.correct.type && state.coords) {
	            if (state.type === "linear") {
	                var guess = state.coords;
	                var correct = rubric.correct.coords;
	                // If both of the guess points are on the correct line, it's
	                // correct.
	                if (collinear(correct[0], correct[1], guess[0]) &&
	                        collinear(correct[0], correct[1], guess[1])) {
	                    return {
	                        type: "points",
	                        earned: 1,
	                        total: 1,
	                        message: null
	                    };
	                }
	            } else if (state.type === "linear-system") {
	                var guess = state.coords;
	                var correct = rubric.correct.coords;

	                if ((
	                        collinear(correct[0][0], correct[0][1], guess[0][0]) &&
	                        collinear(correct[0][0], correct[0][1], guess[0][1]) &&
	                        collinear(correct[1][0], correct[1][1], guess[1][0]) &&
	                        collinear(correct[1][0], correct[1][1], guess[1][1])
	                    ) || (
	                        collinear(correct[0][0], correct[0][1], guess[1][0]) &&
	                        collinear(correct[0][0], correct[0][1], guess[1][1]) &&
	                        collinear(correct[1][0], correct[1][1], guess[0][0]) &&
	                        collinear(correct[1][0], correct[1][1], guess[0][1])
	                    )) {
	                    return {
	                        type: "points",
	                        earned: 1,
	                        total: 1,
	                        message: null
	                    };
	                }

	            } else if (state.type === "quadratic") {
	                // If the parabola coefficients match, it's correct.
	                var guessCoeffs = this.getQuadraticCoefficients(state.coords);
	                var correctCoeffs = this.getQuadraticCoefficients(
	                        rubric.correct.coords);
	                if (deepEq(guessCoeffs, correctCoeffs)) {
	                    return {
	                        type: "points",
	                        earned: 1,
	                        total: 1,
	                        message: null
	                    };
	                }
	            } else if (state.type === "sinusoid") {
	                var guessCoeffs = this.getSinusoidCoefficients(
	                    state.coords);
	                var correctCoeffs = this.getSinusoidCoefficients(
	                        rubric.correct.coords);

	                var canonicalGuessCoeffs = canonicalSineCoefficients(
	                                                guessCoeffs);
	                var canonicalCorrectCoeffs = canonicalSineCoefficients(
	                                                correctCoeffs);
	                // If the canonical coefficients match, it's correct.
	                if (deepEq(canonicalGuessCoeffs, canonicalCorrectCoeffs)) {
	                    return {
	                        type: "points",
	                        earned: 1,
	                        total: 1,
	                        message: null
	                    };
	                }
	            } else if (state.type === "circle") {
	                if (deepEq(state.center, rubric.correct.center) &&
	                        eq(state.radius, rubric.correct.radius)) {
	                    return {
	                        type: "points",
	                        earned: 1,
	                        total: 1,
	                        message: null
	                    };
	                }
	            } else if (state.type === "point") {
	                var guess = state.coords;
	                var correct = InteractiveGraph.getPointCoords(
	                        rubric.correct, component);
	                guess = guess.slice();
	                correct = correct.slice();
	                // Everything's already rounded so we shouldn't need to do an
	                // eq() comparison but _.isEqual(0, -0) is false, so we'll use
	                // eq() anyway. The sort should be fine because it'll stringify
	                // it and -0 converted to a string is "0"
	                guess.sort();
	                correct.sort();
	                if (deepEq(guess, correct)) {
	                    return {
	                        type: "points",
	                        earned: 1,
	                        total: 1,
	                        message: null
	                    };
	                }
	            } else if (state.type === "polygon") {
	                var guess = state.coords.slice();
	                var correct = rubric.correct.coords.slice();

	                var match;
	                if (rubric.correct.match === "similar") {
	                    match = similar(guess, correct, Number.POSITIVE_INFINITY);
	                } else if (rubric.correct.match === "congruent") {
	                    match = similar(guess, correct, knumber.DEFAULT_TOLERANCE);
	                } else if (rubric.correct.match === "approx") {
	                    match = similar(guess, correct, 0.1);
	                } else { /* exact */
	                    guess.sort();
	                    correct.sort();
	                    match = deepEq(guess, correct);
	                }

	                if (match) {
	                    return {
	                        type: "points",
	                        earned: 1,
	                        total: 1,
	                        message: null
	                    };
	                }
	            } else if (state.type === "segment") {
	                var guess = state.coords.slice();
	                var correct = rubric.correct.coords.slice();
	                guess = _.invoke(guess, "sort").sort();
	                correct = _.invoke(correct, "sort").sort();
	                if (deepEq(guess, correct)) {
	                    return {
	                        type: "points",
	                        earned: 1,
	                        total: 1,
	                        message: null
	                    };
	                }
	            } else if (state.type === "ray") {
	                var guess = state.coords;
	                var correct = rubric.correct.coords;
	                if (deepEq(guess[0], correct[0]) &&
	                        collinear(correct[0], correct[1], guess[1])) {
	                    return {
	                        type: "points",
	                        earned: 1,
	                        total: 1,
	                        message: null
	                    };
	                }
	            } else if (state.type === "angle") {
	                var guess = state.coords;
	                var correct = rubric.correct.coords;

	                var match;
	                if (rubric.correct.match === "congruent") {
	                    var angles = _.map([guess, correct], function(coords) {
	                        var angle = KhanUtil.findAngle(
	                            coords[2], coords[0], coords[1]);
	                        return (angle + 360) % 360;
	                    });
	                    match = eq.apply(null, angles);
	                } else { /* exact */
	                    match = deepEq(guess[1], correct[1]) &&
	                            collinear(correct[1], correct[0], guess[0]) &&
	                            collinear(correct[1], correct[2], guess[2]);
	                }

	                if (match) {
	                    return {
	                        type: "points",
	                        earned: 1,
	                        total: 1,
	                        message: null
	                    };
	                }
	            }
	        }

	        // The input wasn't correct, so check if it's a blank input or if it's
	        // actually just wrong
	        if (!state.coords || _.isEqual(state, rubric.graph)) {
	            // We're where we started.
	            return {
	                type: "invalid",
	                message: null
	            };
	        } else {
	            return {
	                type: "points",
	                earned: 0,
	                total: 1,
	                message: null
	            };
	        }
	    }
	});

	var InteractiveGraphEditor = React.createClass({displayName: 'InteractiveGraphEditor',
	    className: "perseus-widget-interactive-graph",

	    getDefaultProps: function() {
	        return {
	            box: [defaultEditorBoxSize, defaultEditorBoxSize],
	            labels: ["x", "y"],
	            range: [[-10, 10], [-10, 10]],
	            step: [1, 1],
	            valid: true,
	            backgroundImage: defaultBackgroundImage,
	            markings: "graph",
	            showProtractor: false,
	            showRuler: false,
	            rulerLabel: "",
	            rulerTicks: 10,
	            correct: {
	                type: "linear",
	                coords: null
	            }
	        };
	    },

	    // TODO(jack): Use versioning instead of DeprecationMixin
	    mixins: [DeprecationMixin],
	    deprecatedProps: deprecatedProps,

	    render: function() {
	        var graph;
	        var equationString;

	        var gridStep = this.props.gridStep || Util.getGridStep(
	                this.props.range,
	                this.props.step,
	                defaultBoxSize
	        );
	        var snapStep = this.props.snapStep || Util.snapStepFromGridStep(
	            gridStep
	        );

	        if (this.props.valid === true) {
	            // TODO(aria): send these down all at once
	            var graphProps = {
	                ref: "graph",
	                box: this.props.box,
	                range: this.props.range,
	                labels: this.props.labels,
	                step: this.props.step,
	                gridStep: gridStep,
	                snapStep: snapStep,
	                graph: this.props.correct,
	                backgroundImage: this.props.backgroundImage,
	                markings: this.props.markings,
	                showProtractor: this.props.showProtractor,
	                showRuler: this.props.showRuler,
	                rulerLabel: this.props.rulerLabel,
	                rulerTicks: this.props.rulerTicks,
	                flexibleType: true,
	                onChange: function(newProps)  {
	                    var correct = this.props.correct;
	                    if (correct.type === newProps.graph.type) {
	                        correct = _.extend({}, correct, newProps.graph);
	                    } else {
	                        // Clear options from previous graph
	                        correct = newProps.graph;
	                    }
	                    this.props.onChange({correct: correct});
	                }.bind(this)
	            };
	            graph = React.createElement(InteractiveGraph, React.__spread({},  graphProps));
	            equationString = InteractiveGraph.getEquationString(graphProps);
	        } else {
	            graph = React.createElement("div", {className: "perseus-error"}, this.props.valid);
	        }

	        return React.createElement("div", {className: "perseus-widget-interactive-graph"}, 
	            React.createElement("div", null, "Correct answer", ' ', 
	                React.createElement(InfoTip, null, 
	                    React.createElement("p", null, "Graph the correct answer in the graph below and ensure" + ' ' +
	                    "the equation or point coordinates displayed represent the" + ' ' +
	                    "correct answer.")
	                ), 
	                ' ', ": ", equationString), 


	            React.createElement(GraphSettings, {
	                box: this.props.box, 
	                range: this.props.range, 
	                labels: this.props.labels, 
	                step: this.props.step, 
	                gridStep: gridStep, 
	                snapStep: snapStep, 
	                valid: this.props.valid, 
	                backgroundImage: this.props.backgroundImage, 
	                markings: this.props.markings, 
	                showProtractor: this.props.showProtractor, 
	                showRuler: this.props.showRuler, 
	                rulerLabel: this.props.rulerLabel, 
	                rulerTicks: this.props.rulerTicks, 
	                onChange: this.props.onChange}), 


	            this.props.correct.type === "polygon" &&
	            React.createElement("div", {className: "type-settings"}, 
	                React.createElement("label", null, 
	                    ' ', "Student answer must", ' ', 
	                    React.createElement("select", {
	                            value: this.props.correct.match, 
	                            onChange: this.changeMatchType}, 
	                        React.createElement("option", {value: "exact"}, "match exactly"), 
	                        React.createElement("option", {value: "congruent"}, "be congruent"), 
	                        React.createElement("option", {value: "approx"}, 
	                            "be approximately congruent"), 
	                        React.createElement("option", {value: "similar"}, "be similar")
	                    )
	                ), 
	                React.createElement(InfoTip, null, 
	                    React.createElement("ul", null, 
	                        React.createElement("li", null, 
	                            React.createElement("p", null, React.createElement("b", null, "Match Exactly:"), " Match exactly in size," + ' ' +
	                            "orientation, and location on the grid even if it is" + ' ' +
	                            "not shown in the background.")
	                        ), 
	                        React.createElement("li", null, 
	                            React.createElement("p", null, React.createElement("b", null, "Be Congruent:"), " Be congruent in size and" + ' ' +
	                            "shape, but can be located anywhere on the grid.")
	                        ), 
	                        React.createElement("li", null, 
	                            React.createElement("p", null, 
	                                React.createElement("b", null, "Be Approximately Congruent:"), " Be exactly" + ' ' +
	                                "similar, and congruent in size and shape to" + ' ' +
	                                "within 0.1 units, but can be located anywhere" + ' ' +
	                                "on the grid. ", React.createElement("em", null, "Use this with snapping to" + ' ' +
	                                "angle measure.")
	                            )
	                        ), 
	                        React.createElement("li", null, 
	                            React.createElement("p", null, React.createElement("b", null, "Be Similar:"), " Be similar with matching" + ' ' +
	                            "interior angles, and side measures that are" + ' ' +
	                            "matching or a multiple of the correct side" + ' ' +
	                            "measures. The figure can be located anywhere on the" + ' ' +
	                            "grid.")
	                        )
	                    )
	                )
	            ), 
	            this.props.correct.type === "angle" &&
	            React.createElement("div", {className: "type-settings"}, 
	                React.createElement("div", null, 
	                    React.createElement("label", null, 
	                        ' ', "Student answer must", ' ', 
	                        React.createElement("select", {
	                                value: this.props.correct.match, 
	                                onChange: this.changeMatchType}, 
	                            React.createElement("option", {value: "exact"}, "match exactly"), 
	                            React.createElement("option", {value: "congruent"}, "be congruent")
	                        )
	                    ), 
	                    React.createElement(InfoTip, null, 
	                        React.createElement("p", null, "Congruency requires only that the angle measures are" + ' ' +
	                        "the same. An exact match implies congruency, but also" + ' ' +
	                        "requires that the angles have the same orientation and" + ' ' +
	                        "that the vertices are in the same position.")
	                    )
	                )
	            ), 
	            graph
	        );
	    },

	    changeMatchType: function(e) {
	        var correct = _.extend({}, this.props.correct, {
	            match: e.target.value
	        });
	        this.props.onChange({correct: correct});
	    },

	    serialize: function() {
	        var json = _.pick(this.props, "step", "backgroundImage", "markings",
	            "labels", "showProtractor", "showRuler", "rulerLabel",
	            "rulerTicks", "range", "gridStep", "snapStep");

	        var graph = this.refs.graph;
	        if (graph) {
	            var correct = graph && graph.getUserInput();
	            _.extend(json, {
	                // TODO(alpert): Allow specifying flexibleType (whether the
	                // graph type should be a choice or not)
	                graph: {type: correct.type},
	                correct: correct
	            });

	            _.each(["allowReflexAngles", "angleOffsetDeg", "numPoints",
	                        "numSides", "numSegments", "showAngles", "showSides",
	                        "snapTo", "snapDegrees"],
	                    function(key) {
	                        if (_.has(correct, key)) {
	                            json.graph[key] = correct[key];
	                        }
	                    });
	        }
	        return json;
	    }
	});

	module.exports = {
	    name: "interactive-graph",
	    displayName: "Interactive graph",
	    widget: InteractiveGraph,
	    editor: InteractiveGraphEditor
	};


/***/ },
/* 27 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);
	var Changeable = __webpack_require__(66);
	var EditorJsonify = __webpack_require__(67);
	var WidgetJsonifyDeprecated = __webpack_require__(68);

	var NumberInput = __webpack_require__(85);
	var PropCheckBox = __webpack_require__(58);
	var InfoTip = __webpack_require__(69);

	var MAX_SIZE = 8;

	// Styling
	var CELL_PADDING = 5;

	var TABLE_STYLE = {
	    display: "table",
	    tableLayout: "fixed"
	};

	var ROW_STYLE = {
	    display: "table-row"
	};

	var CELL_STYLE = {
	    display: "table-cell",
	    padding: CELL_PADDING
	};

	var BASE_TILE_STYLE = {
	    borderRadius: 10,
	    cursor: "pointer"
	};

	var MOVE_COUNT_STYLE = {
	    padding: CELL_PADDING,
	    display: "inline-block"
	};

	var RESET_BUTTON_STYLE = {
	    "float": "right",
	    paddingRight: CELL_PADDING
	};

	var MAIN_TILE_SIZE = 50;

	var mapCells = function(cells, func)  {
	    return _.map(cells, function(row, y)  {
	        return _.map(row, function(value, x)  {
	            return func(value, y, x);
	        });
	    });
	};

	var genCells = function(height, width, func)  {
	    return _.times(height, function(y)  {
	        return _.times(width, function(x)  {
	            return func(y, x);
	        });
	    });
	};

	var PATTERNS = {
	    plus: function()  {return [
	        [false, true, false],
	        [true,  true, true ],
	        [false, true, false]
	    ];},
	    x: function()  {return [
	        [true,  false, true ],
	        [false, true,  false],
	        [true,  false, true ]
	    ];},
	    "plus/x": function(iter)  {
	        return (iter % 2) ? PATTERNS.x() : PATTERNS.plus();
	    }
	};


	/**
	 * Clamps value to an integer in the range [min, max]
	 */
	var clampToInt = function(value, min, max) {
	    value = Math.floor(value);
	    value = Math.max(value, min);
	    value = Math.min(value, max);
	    return value;
	};

	// A single glowy cell
	var Tile = React.createClass({displayName: 'Tile',
	    propTypes: {
	        value: React.PropTypes.bool.isRequired,
	        size: React.PropTypes.number.isRequired
	    },

	    render: function() {
	        var color = this.props.value ? "#55dd55" : "#115511";
	        var style = _.extend({}, BASE_TILE_STYLE, {
	            width: this.props.size,
	            height: this.props.size,
	            backgroundColor: color
	        });
	        return React.createElement("div", {
	            style: style, 
	            onClick: this._flip});
	    },

	    _flip: function() {
	        this.props.onChange(!this.props.value);
	    },
	});

	// A grid of glowy cells
	var TileGrid = React.createClass({displayName: 'TileGrid',
	    propTypes: {
	        cells: React.PropTypes.arrayOf(
	            React.PropTypes.arrayOf(React.PropTypes.bool)
	        ).isRequired,
	        size: React.PropTypes.number.isRequired
	    },

	    render: function() {
	        return React.createElement("div", {style: TABLE_STYLE, className: "no-select"}, 
	            _.map(this.props.cells, function(row, y)  {
	                return React.createElement("div", {key: y, style: ROW_STYLE}, 
	                    _.map(row, function(cell, x)  {
	                        return React.createElement("div", {key: x, style: CELL_STYLE}, 
	                            React.createElement(Tile, {
	                                value: cell, 
	                                size: this.props.size, 
	                                onChange: _.partial(this.props.onChange, y, x)}
	                                )
	                        );
	                    }.bind(this))
	                );
	            }.bind(this))
	        );
	    },
	});

	// Returns a copy of the tiles, with tiles flipped according to
	// whether or not their y, x position satisfies the predicate
	var flipTilesPredicate = function(oldCells, predicate)  {
	    return _.map(oldCells, function(row, y)  {
	        return _.map(row, function(cell, x)  {
	            return predicate(y, x) ? !cell : cell;
	        });
	    });
	};

	var flipTilesPattern = function(oldCells, tileY, tileX, pattern)  {
	    return flipTilesPredicate(oldCells, function(y, x)  {
	        var offsetY = y - tileY;
	        var offsetX = x - tileX;
	        if (Math.abs(offsetY) <= 1 && Math.abs(offsetX) <= 1) {
	            return pattern[offsetY + 1][offsetX + 1];
	        } else {
	            return false;
	        }
	    });
	};

	// The lights puzzle widget
	var LightsPuzzle = React.createClass({displayName: 'LightsPuzzle',
	    mixins: [Changeable, WidgetJsonifyDeprecated],

	    propTypes: {
	        cells: React.PropTypes.arrayOf(
	            React.PropTypes.arrayOf(React.PropTypes.bool)
	        ),
	        startCells: React.PropTypes.arrayOf(
	            React.PropTypes.arrayOf(React.PropTypes.bool)
	        ),
	        flipPattern: React.PropTypes.string.isRequired,
	        moveCount: React.PropTypes.number.isRequired
	    },

	    getDefaultProps: function() {
	        return {
	            cells: [
	                [false, false, false],
	                [false, false, false],
	                [false, false, false]
	            ],
	            startCells: [
	                [false, false, false],
	                [false, false, false],
	                [false, false, false]
	            ],
	            flipPattern: "plus",
	            moveCount: 0
	        };
	    },

	    render: function() {
	        var width = this._width();
	        var tileSize = MAIN_TILE_SIZE;
	        var pxWidth = width * (tileSize + 2 * CELL_PADDING);
	        return React.createElement("div", null, 
	            React.createElement(TileGrid, {
	                cells: this.props.cells, 
	                size: tileSize, 
	                onChange: this._flipTile}), 
	            React.createElement("div", {style: {width: pxWidth}}, 
	                React.createElement("div", {style: MOVE_COUNT_STYLE}, 
	                    "Moves: ", this.props.moveCount
	                ), 
	                React.createElement("div", {style: RESET_BUTTON_STYLE}, 
	                React.createElement("input", {
	                    type: "button", 
	                    value: "Reset", 
	                    onClick: this._reset, 
	                    className: "simple-button"})
	                )
	            ), 
	            React.createElement("div", {className: "clearfix"})
	        );
	    },

	    _width: function() {
	        if (this.props.cells.length !== 0) {
	            return this.props.cells[0].length;
	        } else {
	            return 0; // default to 0
	        }
	    },

	    componentDidMount: function() {
	        this._initNextPatterns();
	    },

	    componentDidUpdate: function(prevProps) {
	        if (prevProps.flipPattern !== this.props.flipPattern) {
	            this._initNextPatterns();
	        }
	    },

	    _initNextPatterns: function() {
	        this._currPattern = PATTERNS[this.props.flipPattern](0);
	        this._nextPattern = PATTERNS[this.props.flipPattern](1);
	        this._patternIndex = 2;
	    },

	    _shiftPatterns: function() {
	        this._currPattern = this._nextPattern;
	        this._nextPattern = PATTERNS[this.props.flipPattern](
	            this._patternIndex
	        );
	        this._patternIndex++;
	    },

	    _flipTile: function(tileY, tileX) {
	        var newCells = flipTilesPattern(
	            this.props.cells,
	            tileY,
	            tileX,
	            this._currPattern
	        );
	        this._shiftPatterns();

	        this.change({
	            cells: newCells,
	            moveCount: this.props.moveCount + 1
	        });
	    },

	    _reset: function() {
	        this.change({
	            cells: this.props.startCells,
	            moveCount: 0
	        });
	    },

	    simpleValidate: function(rubric) {
	        return validate(rubric, this.getUserInput());
	    },

	    statics: {
	        displayMode: "block"
	    }
	});

	// The widget editor
	var LightsPuzzleEditor = React.createClass({displayName: 'LightsPuzzleEditor',
	    mixins: [Changeable, EditorJsonify],

	    propTypes: {
	        startCells: React.PropTypes.arrayOf(
	            React.PropTypes.arrayOf(React.PropTypes.bool)
	        ),
	        flipPattern: React.PropTypes.string.isRequired,
	        gradeIncompleteAsWrong: React.PropTypes.bool.isRequired
	    },

	    getDefaultProps: function() {
	        return {
	            startCells: [
	                [false, false, false],
	                [false, false, false],
	                [false, false, false]
	            ],
	            flipPattern: "plus",
	            gradeIncompleteAsWrong: false
	        };
	    },

	    _height: function() {
	        return this.props.startCells.length;
	    },

	    _width: function() {
	        if (this.props.startCells.length !== 0) {
	            return this.props.startCells[0].length;
	        } else {
	            return 0; // default to 0
	        }
	    },

	    render: function() {
	        return React.createElement("div", null, 
	            React.createElement("div", null, 
	                "Width:", 
	                React.createElement(NumberInput, {
	                    value: this._width(), 
	                    placeholder: 5, 
	                    onChange: this._changeWidth}), 
	                ", ", 
	                "Height:", 
	                React.createElement(NumberInput, {
	                    value: this._height(), 
	                    placeholder: 5, 
	                    onChange: this._changeHeight})
	            ), 
	            React.createElement("div", null, 
	                "Flip pattern:", 
	                React.createElement("select", {
	                        value: this.props.flipPattern, 
	                        onChange: this._handlePatternChange}, 
	                    _.map(_.keys(PATTERNS), function(pattern)  {
	                        return React.createElement("option", {value: pattern}, pattern);
	                    })
	                )
	            ), 
	            React.createElement("div", null, 
	                "Grade incomplete puzzles as wrong:", 
	                " ", 
	                React.createElement(PropCheckBox, {
	                    gradeIncompleteAsWrong: this.props.gradeIncompleteAsWrong, 
	                    onChange: this.props.onChange}), 
	                    React.createElement(InfoTip, null, 
	                        "By default, incomplete puzzles are graded as empty."
	                    )
	                ), 
	            React.createElement("div", null, 
	                "Starting configuration:"
	            ), 
	            React.createElement("div", {style: {overflowX: "auto"}}, 
	                React.createElement(TileGrid, {
	                    cells: this.props.startCells, 
	                    size: 50, 
	                    onChange: this._switchTile})
	            )
	        );
	    },

	    _handlePatternChange: function(e) {
	        this.change("flipPattern", e.target.value);
	    },

	    _changeWidth: function(newWidth) {
	        newWidth = clampToInt(newWidth, 1, MAX_SIZE);
	        this._truncateCells(newWidth, this._height());
	    },

	    _changeHeight: function(newHeight) {
	        newHeight = clampToInt(newHeight, 1, MAX_SIZE);
	        this._truncateCells(this._width(), newHeight);
	    },

	    _truncateCells: function(newWidth, newHeight) {
	        var newCells = _.times(newHeight, function(y)  {
	            return _.times(newWidth, function(x)  {
	                // explicitly cast the result to a boolean with !!
	                return !!(this.props.startCells[y] &&
	                        this.props.startCells[y][x]);
	            }.bind(this));
	        }.bind(this));

	        this.change({startCells: newCells});
	    },

	    _switchTile: function(tileY, tileX) {
	        var newCells = flipTilesPredicate(this.props.startCells, function(y, x)  {
	            return y === tileY && x === tileX;
	        });

	        this.change({startCells: newCells});
	    }
	});

	// grading function
	var validate = function(rubric, state) {
	    var empty = _.all(state.cells, function(row, y)  {
	        return _.all(row, function(cell, x)  {
	            return cell === rubric.startCells[y][x];
	        });
	    });
	    if (empty) {
	        return {
	            type: "invalid",
	            message: $._("Click on the tiles to change the lights.")
	        };
	    }

	    var correct = _.all(state.cells, function(row)  {
	        return _.all(row, function(cell)  {
	            return cell;
	        });
	    });

	    if (correct) {
	        return {
	            type: "points",
	            earned: 1,
	            total: 1,
	            message: null
	        };
	    } else if (rubric.gradeIncompleteAsWrong) {
	        return {
	            type: "points",
	            earned: 0,
	            total: 1,
	            message: null
	        };
	    } else {
	        return {
	            type: "invalid",
	            message: $._("You must turn on all of the lights to continue.")
	        };
	    }
	};

	// The function run on the editor props to create the widget props
	var transformProps = function(editorProps) {
	    return {
	        cells: editorProps.startCells,
	        startCells: editorProps.startCells,
	        flipPattern: editorProps.flipPattern
	    };
	};

	module.exports = {
	    name: "lights-puzzle",
	    displayName: "Lights Puzzle",
	    hidden: true,
	    widget: LightsPuzzle,
	    editor: LightsPuzzleEditor,
	    transform: transformProps
	};


/***/ },
/* 28 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);
	var _ = __webpack_require__(60);

	var Changeable = __webpack_require__(66);
	var EditorJsonify = __webpack_require__(67);

	var Editor = __webpack_require__(7);
	var NumberInput = __webpack_require__(85);
	var RangeInput = __webpack_require__(83);
	var Renderer = __webpack_require__(10);
	var TextInput = __webpack_require__(86);
	var MathOutput = __webpack_require__(88);

	var ApiOptions = __webpack_require__(12).Options;

	var assert = __webpack_require__(89).assert;
	var stringArrayOfSize = __webpack_require__(3).stringArrayOfSize;

	// Really large matrices will cause issues with question formatting, so we
	// have to cap it at some point.
	var MAX_BOARD_SIZE = 6;

	// We store two sets of dimensions for the brackets, because mobile formatting
	// is different. These dimensions come from `matrix.less`.
	var MOBILE_DIMENSIONS = {
	    INPUT_MARGIN: 4,
	    INPUT_HEIGHT: 38,
	    INPUT_WIDTH: 82
	};

	var NORMAL_DIMENSIONS = {
	    INPUT_MARGIN: 3,
	    INPUT_HEIGHT: 30,
	    INPUT_WIDTH: 40
	};

	/* Input handling: Maps a (row, column) pair to a unique ref used by React,
	 * and extracts (row, column) pairs from input paths, used to allow outsiders
	 * to focus, blur, set input values, etc. */
	var getInputPath = function(row, column) {
	    return ["" + row, "" + column];
	};

	var getDefaultPath = function() {
	    return getInputPath(0, 0);
	};

	var getRowFromPath = function(path) {
	    // 'path' should be a (row, column) pair
	    assert(_.isArray(path) && path.length === 2);
	    return +path[0];
	};

	var getColumnFromPath = function(path) {
	    // 'path' should be a (row, column) pair
	    assert(_.isArray(path) && path.length === 2);
	    return +path[1];
	};

	var getRefForPath = function(path) {
	    var row = getRowFromPath(path);
	    var column = getColumnFromPath(path);
	    return "answer" + row + "," + column;
	};

	var getMatrixSize = function(matrix) {
	    var matrixSize = [1, 1];

	    // We need to find the widest row and tallest column to get the correct
	    // matrix size.
	    _(matrix).each(function(matrixRow, row)  {
	        var rowWidth = 0;
	        _(matrixRow).each(function(matrixCol, col)  {
	            if (matrixCol != null && matrixCol.toString().length) {
	                rowWidth = col + 1;
	            }
	        });

	        // Matrix width:
	        matrixSize[1] = Math.max(matrixSize[1], rowWidth);

	        // Matrix height:
	        if (rowWidth > 0) {
	            matrixSize[0] = Math.max(matrixSize[0], row + 1);
	        }
	    });
	    return matrixSize;
	};

	var Matrix = React.createClass({displayName: 'Matrix',
	    propTypes: {
	        matrixBoardSize: React.PropTypes.arrayOf(
	            React.PropTypes.number
	        ).isRequired,
	        answers: React.PropTypes.arrayOf(
	            React.PropTypes.arrayOf(
	                React.PropTypes.oneOfType([
	                    React.PropTypes.string,
	                    React.PropTypes.number
	                ])
	            )
	        ),
	        prefix: React.PropTypes.string,
	        suffix: React.PropTypes.string,
	        cursorPosition: React.PropTypes.arrayOf(
	            React.PropTypes.number
	        )
	    },

	    getDefaultProps: function() {
	        return {
	            matrixBoardSize: [3, 3],
	            answers: [[]],
	            prefix: "",
	            suffix: "",
	            cursorPosition: [0, 0],
	            apiOptions: ApiOptions.defaults
	        };
	    },

	    getInitialState: function() {
	        return {
	            enterTheMatrix: 0
	        };
	    },

	    componentDidMount: function() {
	        // Used in the `onBlur` and `onFocus` handlers
	        this.cursorPosition = [0, 0];
	    },

	    render: function() {
	        // Set the input sizes through JS so we can control the size of the
	        // brackets. (If we set them in CSS we won't know values until the
	        // inputs are rendered.)
	        var dimensions = this.props.apiOptions.staticRender ?
	                MOBILE_DIMENSIONS : NORMAL_DIMENSIONS;
	        var $__0=      dimensions,INPUT_MARGIN=$__0.INPUT_MARGIN,INPUT_HEIGHT=$__0.INPUT_HEIGHT,INPUT_WIDTH=$__0.INPUT_WIDTH;

	        var matrixSize = getMatrixSize(this.props.answers);
	        var maxRows = this.props.matrixBoardSize[0];
	        var maxCols = this.props.matrixBoardSize[1];
	        var cursorRow = this.props.cursorPosition[0];
	        var cursorCol = this.props.cursorPosition[1];

	        var highlightedRow = Math.max(cursorRow, matrixSize[0] - 1);
	        var highlightedCol = Math.max(cursorCol, matrixSize[1] - 1);
	        var bracketHeight = (highlightedRow + 1) *
	                (INPUT_HEIGHT + 2 * INPUT_MARGIN);
	        var bracketOffset = (highlightedCol + 1) *
	                (INPUT_WIDTH + 2 * INPUT_MARGIN);

	        var cx = React.addons.classSet;
	        var className = cx({
	            "perseus-matrix": true,
	            "the-matrix": this.state.enterTheMatrix >= 5
	        });

	        return React.createElement("div", {className: className}, 
	            this.props.prefix && React.createElement("div", {className: "matrix-prefix"}, 
	                React.createElement(Renderer, {content: this.props.prefix})
	            ), 
	            React.createElement("div", {className: "matrix-input"}, 
	                React.createElement("div", {
	                    className: "matrix-bracket bracket-left", 
	                    style: {
	                        height: bracketHeight
	                    }}
	                ), 
	                React.createElement("div", {
	                    className: "matrix-bracket bracket-right", 
	                    style: {
	                        height: bracketHeight,
	                        left: bracketOffset
	                    }}
	                ), 
	                _(maxRows).times(function(row)  {
	                    var rowVals = this.props.answers[row];
	                    return React.createElement("div", {className: "matrix-row", key: row}, 
	                        _(maxCols).times(function(col)  {
	                            var outside = row > highlightedRow ||
	                                    col > highlightedCol;
	                            var inputProps = {
	                                className: outside ? "outside" : "inside",
	                                ref: getRefForPath(getInputPath(row, col)),
	                                value: rowVals ? rowVals[col] : null,
	                                style: {
	                                    height: INPUT_HEIGHT,
	                                    width: INPUT_WIDTH,
	                                    margin: INPUT_MARGIN
	                                },
	                                onFocus: function()  {
	                                    // We store this locally so that we can use
	                                    // the new information in the `onBlur`
	                                    // handler, which happens before the props
	                                    // change has time to propagate.
	                                    // TODO(emily): Try to fix `MathOutput` so
	                                    // it correctly sends blur events before
	                                    // focus events.
	                                    this.cursorPosition = [row, col];
	                                    this.props.onChange({
	                                        cursorPosition: [row, col]
	                                    }, function()  {
	                                        // This isn't a user interaction, so
	                                        // return false to signal that the
	                                        // matrix shouldn't be focused
	                                        return false;
	                                    });
	                                    this._handleFocus(row, col);
	                                }.bind(this),
	                                onBlur: function()  {
	                                    if (row === this.cursorPosition[0] &&
	                                        col === this.cursorPosition[1]) {
	                                        this.props.onChange({
	                                            cursorPosition: [0, 0]
	                                        }, function()  {
	                                            // This isn't a user interaction,
	                                            // so return false to signal that
	                                            // the matrix shouldn't be focused
	                                            return false;
	                                        });
	                                    }
	                                    this._handleBlur(row, col);
	                                }.bind(this),
	                                onKeyDown: function(e)  {
	                                    this.handleKeyDown(row, col, e);
	                                }.bind(this),
	                                onChange: function(value)  {
	                                    this.onValueChange(row, col, value);
	                                }.bind(this)
	                            };

	                            var MatrixInput;
	                            if (this.props.apiOptions.staticRender) {
	                                MatrixInput = React.createElement(MathOutput, React.__spread({},  inputProps));
	                            } else if (this.props.numericInput) {
	                                MatrixInput = React.createElement(NumberInput, React.__spread({},  inputProps));
	                            } else {
	                                MatrixInput = React.createElement(TextInput, React.__spread({},  inputProps));
	                            }
	                            return React.createElement("span", {
	                                        key: col, 
	                                        className: "matrix-input-field"}, 
	                                MatrixInput
	                            );
	                        }.bind(this))
	                    );
	                }.bind(this))
	            ), 
	            this.props.suffix && React.createElement("div", {className: "matrix-suffix"}, 
	                React.createElement(Renderer, {content: this.props.suffix})
	            )
	        );
	    },

	    getInputPaths: function() {
	        var inputPaths = [];
	        var maxRows = this.props.matrixBoardSize[0];
	        var maxCols = this.props.matrixBoardSize[1];

	        _(maxRows).times(function(row)  {
	            _(maxCols).times(function(col)  {
	                var inputPath = getInputPath(row, col);
	                inputPaths.push(inputPath);
	            });
	        });

	        return inputPaths;
	    },

	    getGrammarTypeForPath: function(inputPath) {
	        return "number";
	    },

	    _handleFocus: function(row, col) {
	        this.props.onFocus(getInputPath(row, col));
	    },

	    _handleBlur: function(row, col) {
	        this.props.onBlur(getInputPath(row, col));
	    },

	    focus: function() {
	        this.focusInputPath(getDefaultPath());
	        return true;
	    },

	    focusInputPath: function(path) {
	        var inputID = getRefForPath(path);
	        this.refs[inputID].focus();
	    },

	    blurInputPath: function(path) {
	        if (path.length === 0) {
	            path = getDefaultPath();
	        }

	        var inputID = getRefForPath(path);
	        this.refs[inputID].blur();
	    },

	    getDOMNodeForPath: function(inputPath) {
	        var inputID = getRefForPath(inputPath);
	        return this.refs[inputID].getDOMNode();
	    },

	    setInputValue: function(inputPath, value, callback) {
	        var row = getRowFromPath(inputPath);
	        var col = getColumnFromPath(inputPath);
	        this.onValueChange(row, col, value, callback);
	    },

	    handleKeyDown: function (row, col, e) {
	        var maxRow = this.props.matrixBoardSize[0];
	        var maxCol = this.props.matrixBoardSize[1];
	        var enterTheMatrix = null;

	        var nextPath = null;
	        if (e.key === "ArrowUp" && row > 0) {
	            nextPath = getRefForPath(getInputPath(row - 1, col));
	        } else if (e.key === "ArrowDown" && row + 1 < maxRow) {
	            nextPath = getRefForPath(getInputPath(row + 1, col));
	        } else if (e.key === "ArrowLeft" && col > 0) {
	            nextPath = getRefForPath(getInputPath(row, col - 1));
	        } else if (e.key === "ArrowRight" && col + 1 < maxCol) {
	            nextPath = getRefForPath(getInputPath(row, col + 1));
	        } else if (e.key === "Enter") {
	            enterTheMatrix = this.state.enterTheMatrix + 1;
	        } else if (e.key === "Escape") {
	            enterTheMatrix = 0;
	        }

	        if (nextPath) {
	            this.refs[nextPath].focus();
	        }

	        if (enterTheMatrix != null) {
	            this.setState({
	                enterTheMatrix: enterTheMatrix
	            });
	        }
	    },

	    onValueChange: function(row, column, value, cb) {
	        var answers = _.map(this.props.answers, _.clone);
	        if (!answers[row]) {
	            answers[row] = [];
	        }
	        answers[row][column] = value;
	        this.props.onChange({
	            answers: answers
	        }, cb);
	    },

	    getUserInput: function() {
	        return {
	            answers: this.props.answers
	        };
	    },

	    simpleValidate: function(rubric) {
	        return Matrix.validate(this.getUserInput(), rubric);
	    },

	    statics: {
	        displayMode: "block"
	    }
	});

	_.extend(Matrix, {
	    validate: function(state, rubric) {
	        var solution = rubric.answers;
	        var supplied = state.answers;
	        var solutionSize = getMatrixSize(solution);
	        var suppliedSize = getMatrixSize(supplied);

	        var incorrectSize = solutionSize[0] !== suppliedSize[0] ||
	                solutionSize[1] !== suppliedSize[1];

	        var createValidator = Khan.answerTypes
	                                  .number.createValidatorFunctional;
	        var message = null;
	        var hasEmptyCell = false;
	        var incorrect = false;
	        _(suppliedSize[0]).times(function(row)  {
	            _(suppliedSize[1]).times(function(col)  {
	                if (supplied[row][col] == null ||
	                        supplied[row][col].toString().length === 0) {
	                    hasEmptyCell = true;
	                }
	                var validator = createValidator(
	                        solution[row][col],
	                        { simplify: true }
	                    );
	                var result = validator(supplied[row][col]);
	                if (result.message) {
	                    message = result.message;
	                }
	                if (!result.correct) {
	                    incorrect = true;
	                }
	            });
	        });

	        if (hasEmptyCell) {
	            return {
	                type: "invalid",
	                message: $._("Make sure you fill in all cells in the matrix.")
	            };
	        }

	        if (incorrectSize) {
	            return {
	                type: "points",
	                earned: 0,
	                total: 1,
	                message: null
	            };
	        }

	        return {
	            type: "points",
	            earned: incorrect ? 0 : 1,
	            total: 1,
	            message: message
	        };
	    }
	});

	var MatrixEditor = React.createClass({displayName: 'MatrixEditor',
	    mixins: [EditorJsonify, Changeable],

	    propTypes: {
	        matrixBoardSize: React.PropTypes.arrayOf(
	            React.PropTypes.number
	        ).isRequired,
	        answers: React.PropTypes.arrayOf(
	            React.PropTypes.arrayOf(
	                React.PropTypes.number
	            )
	        ),
	        prefix: React.PropTypes.string,
	        suffix: React.PropTypes.string,
	        cursorPosition: React.PropTypes.arrayOf(
	            React.PropTypes.number
	        )
	    },

	    getDefaultProps: function() {
	        return {
	            matrixBoardSize: [3, 3],
	            answers: [[]],
	            prefix: "",
	            suffix: "",
	            cursorPosition: [0, 0]
	        };
	    },

	    render: function() {
	        var matrixProps = _.extend({ numericInput: true }, this.props);
	        return React.createElement("div", {className: "perseus-matrix-editor"}, 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                " ", "Max matrix size:", " ", 
	                React.createElement(RangeInput, {
	                    value: this.props.matrixBoardSize, 
	                    onChange: this.onMatrixBoardSizeChange, 
	                    format: this.props.labelStyle, 
	                    useArrowKeys: true})
	            ), 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                React.createElement(Matrix, React.__spread({},  matrixProps))
	            ), 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                " ", "Matrix prefix:", " ", 
	                React.createElement(Editor, {
	                    ref: "prefix", 
	                    content: this.props.prefix, 
	                    widgetEnabled: false, 
	                    onChange: function(newProps)  {
	                        this.change({ prefix: newProps.content });
	                    }.bind(this)})
	            ), 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                " ", "Matrix suffix:", " ", 
	                React.createElement(Editor, {
	                    ref: "suffix", 
	                    content: this.props.suffix, 
	                    widgetEnabled: false, 
	                    onChange: function(newProps)  {
	                        this.change({ suffix: newProps.content });
	                    }.bind(this)})
	            )
	        );
	    },

	    onMatrixBoardSizeChange: function (range) {
	        var matrixSize = getMatrixSize(this.props.answers);
	        if (range[0] !== null && range[1] !== null) {
	            range = [
	                Math.round(Math.min(Math.max(range[0], 1), MAX_BOARD_SIZE)),
	                Math.round(Math.min(Math.max(range[1], 1), MAX_BOARD_SIZE))
	            ];
	            var answers = _(Math.min(range[0], matrixSize[0])).times(function(row)  {
	                return _(Math.min(range[1], matrixSize[1])).times(function(col)  {
	                    return this.props.answers[row][col];
	                }.bind(this));
	            }.bind(this));
	            this.props.onChange({
	                matrixBoardSize: range,
	                answers: answers
	            });
	        }
	    }
	});

	var propTransform = function(editorProps)  {
	    // Remove answers before passing to widget
	    var blankAnswers = _(editorProps.matrixBoardSize[0]).times(function() {
	        return stringArrayOfSize(editorProps.matrixBoardSize[1]);
	    });
	    editorProps = _.pick(editorProps, "matrixBoardSize", "prefix", "suffix");
	    return _.extend(editorProps, {
	        answers: blankAnswers
	    });
	};

	module.exports = {
	    name: "matrix",
	    displayName: "Matrix",
	    widget: Matrix,
	    editor: MatrixEditor,
	    transform: propTransform
	};


/***/ },
/* 29 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);
	var _ = __webpack_require__(60);

	var InfoTip        = __webpack_require__(69);
	var PropCheckBox   = __webpack_require__(58);
	var Renderer       = __webpack_require__(10);
	var Sortable       = __webpack_require__(90);
	var TextListEditor = __webpack_require__(70);

	var shuffle = __webpack_require__(3).shuffle;
	var seededRNG = __webpack_require__(3).seededRNG;

	var Matcher = React.createClass({displayName: 'Matcher',
	    propTypes: {
	        left: React.PropTypes.array,
	        right: React.PropTypes.array,
	        labels: React.PropTypes.array,
	        orderMatters: React.PropTypes.bool,
	        padding: React.PropTypes.bool,
	        problemNum: React.PropTypes.number,
	        onChange: React.PropTypes.func
	    },

	    getDefaultProps: function() {
	        return {
	            left: [],
	            right: [],
	            labels: ["", ""],
	            orderMatters: false,
	            padding: true,
	            problemNum: 0,
	            onChange: function() {}
	        };
	    },

	    getInitialState: function() {
	        return {
	            leftHeight: 0,
	            rightHeight: 0
	        };
	    },

	    render: function() {
	        // Use the same random() function to shuffle both columns sequentially
	        var rng = seededRNG(this.props.problemNum);

	        var left;
	        if (!this.props.orderMatters) {
	            // If the order doesn't matter, don't shuffle the left column
	            left = this.props.left;
	        } else {
	            left = shuffle(this.props.left, rng, /* ensurePermuted */ true);
	        }

	        var right = shuffle(this.props.right, rng, /* ensurePermuted */ true);

	        var showLabels = _.any(this.props.labels);
	        var constraints = {height: _.max([this.state.leftHeight,
	            this.state.rightHeight])};

	        return React.createElement("div", {className: "perseus-widget-matcher ui-helper-clearfix"}, 
	            React.createElement("div", {className: "column"}, 
	                showLabels && React.createElement("div", {className: "column-label"}, 
	                    React.createElement(Renderer, {content: this.props.labels[0] || "..."})
	                ), 
	                React.createElement(Sortable, {
	                    options: left, 
	                    layout: "vertical", 
	                    padding: this.props.padding, 
	                    disabled: !this.props.orderMatters, 
	                    constraints: constraints, 
	                    onMeasure: this.onMeasureLeft, 
	                    onChange: this.props.onChange, 
	                    ref: "left"})
	            ), 
	            React.createElement("div", {className: "column"}, 
	                showLabels && React.createElement("div", {className: "column-label"}, 
	                    React.createElement(Renderer, {content: this.props.labels[1] || "..."})
	                ), 
	                React.createElement(Sortable, {
	                    options: right, 
	                    layout: "vertical", 
	                    padding: this.props.padding, 
	                    constraints: constraints, 
	                    onMeasure: this.onMeasureRight, 
	                    onChange: this.props.onChange, 
	                    ref: "right"})
	            )
	        );
	    },

	    onMeasureLeft: function(dimensions) {
	        var height = _.max(dimensions.heights);
	        this.setState({leftHeight: height});
	    },

	    onMeasureRight: function(dimensions) {
	        var height = _.max(dimensions.heights);
	        this.setState({rightHeight: height});
	    },

	    getUserInput: function() {
	        return {
	            left: this.refs.left.getOptions(),
	            right: this.refs.right.getOptions()
	        };
	    },

	    simpleValidate: function(rubric) {
	        return Matcher.validate(this.getUserInput(), rubric);
	    },

	    statics: {
	        displayMode: "block"
	    }
	});


	_.extend(Matcher, {
	    validate: function(state, rubric) {
	        var correct = _.isEqual(state.left, rubric.left) &&
	                      _.isEqual(state.right, rubric.right);

	        return {
	            type: "points",
	            earned: correct ? 1 : 0,
	            total: 1,
	            message: null
	        };
	    }
	});


	var MatcherEditor = React.createClass({displayName: 'MatcherEditor',
	    propTypes: {
	        left: React.PropTypes.array,
	        right: React.PropTypes.array,
	        labels: React.PropTypes.array,
	        orderMatters: React.PropTypes.bool,
	        padding: React.PropTypes.bool
	    },

	    getDefaultProps: function() {
	        return {
	            left: ["$x$", "$y$", "$z$"],
	            right: ["$1$", "$2$", "$3$"],
	            labels: ["test", "label"],
	            orderMatters: false,
	            padding: true
	        };
	    },

	    render: function() {
	        return React.createElement("div", {className: "perseus-matcher-editor"}, 
	            React.createElement("div", null, 
	                ' ', "Correct answer:", ' ', 
	                React.createElement(InfoTip, null, 
	                    React.createElement("p", null, "Enter the correct answers here. The preview on the right" + ' ' +
	                    "will show the cards in a randomized order, which is how the" + ' ' +
	                    "student will see them.")
	                )
	            ), 
	            React.createElement("div", {className: "ui-helper-clearfix"}, 
	                React.createElement(TextListEditor, {
	                    options: this.props.left, 
	                    onChange: function(options, cb)  {
	                        this.props.onChange({left: options}, cb);
	                    }.bind(this), 
	                    layout: "vertical"}), 
	                React.createElement(TextListEditor, {
	                    options: this.props.right, 
	                    onChange: function(options, cb)  {
	                        this.props.onChange({right: options}, cb);
	                    }.bind(this), 
	                    layout: "vertical"})
	            ), 
	            React.createElement("span", null, 
	                ' ', "Labels:", ' ', 
	                React.createElement(InfoTip, null, 
	                    React.createElement("p", null, "These are entirely optional.")
	                )
	            ), 
	            React.createElement("div", null, 
	                React.createElement("input", {type: "text", 
	                    defaultValue: this.props.labels[0], 
	                    onChange: this.onLabelChange.bind(this, 0)}), 
	                React.createElement("input", {type: "text", 
	                    defaultValue: this.props.labels[1], 
	                    onChange: this.onLabelChange.bind(this, 1)})
	            ), 
	            React.createElement("div", null, 
	                React.createElement(PropCheckBox, {
	                    label: "Order of the matched pairs matters:", 
	                    orderMatters: this.props.orderMatters, 
	                    onChange: this.props.onChange}), 
	                React.createElement(InfoTip, null, 
	                    React.createElement("p", null, "With this option enabled, only the order provided above" + ' ' +
	                    "will be treated as correct. This is useful when ordering is" + ' ' +
	                    "significant, such as in the context of a proof."), 
	                    React.createElement("p", null, "If disabled, pairwise matching is sufficient. To make" + ' ' +
	                    "this clear, the left column becomes fixed in the provided" + ' ' +
	                    "order and only the cards in the right column can be" + ' ' +
	                    "moved.")
	                )
	            ), 
	            React.createElement("div", null, 
	                React.createElement(PropCheckBox, {
	                    label: "Padding:", 
	                    padding: this.props.padding, 
	                    onChange: this.props.onChange}), 
	                React.createElement(InfoTip, null, 
	                    React.createElement("p", null, "Padding is good for text, but not needed for images.")
	                )
	            )
	        );
	    },

	    onLabelChange: function(index, e) {
	        var labels = _.clone(this.props.labels);
	        labels[index] = e.target.value;
	        this.props.onChange({labels: labels});
	    },

	    getSaveWarnings: function() {
	        if (this.props.left.length !== this.props.right.length) {
	            return [
	                "Warning: The two halves of the matcher have different" +
	                " numbers of cards."
	            ];
	        }
	    },

	    serialize: function() {
	        return _.pick(this.props,
	            "left", "right", "labels", "orderMatters", "padding"
	        );
	    }
	});

	module.exports = {
	    name: "matcher",
	    displayName: "Two column matcher",
	    widget: Matcher,
	    editor: MatcherEditor
	};


/***/ },
/* 30 */
/***/ function(module, exports, __webpack_require__) {

	var React        = __webpack_require__(61);
	var _ = __webpack_require__(60);

	var Changeable   = __webpack_require__(66);
	var EditorJsonify = __webpack_require__(67);

	var InfoTip       = __webpack_require__(69);
	var NumberInput   = __webpack_require__(85);
	var PropCheckBox  = __webpack_require__(58);
	var RangeInput    = __webpack_require__(83);

	var defaultImage = {
	    url: null,
	    top: 0,
	    left: 0
	};

	var Measurer = React.createClass({displayName: 'Measurer',
	    propTypes: {
	        box: React.PropTypes.arrayOf(React.PropTypes.number),
	        image: React.PropTypes.shape({
	            url: React.PropTypes.string,
	            top: React.PropTypes.number,
	            left: React.PropTypes.number
	        }),
	        showProtractor: React.PropTypes.bool,
	        protractorX: React.PropTypes.number,
	        protractorY: React.PropTypes.number,
	        showRuler: React.PropTypes.bool,
	        rulerLabel: React.PropTypes.string,
	        rulerTicks: React.PropTypes.number,
	        rulerPixels: React.PropTypes.number,
	        rulerLength: React.PropTypes.number
	    },

	    getDefaultProps: function() {
	        return {
	            box: [480, 480],
	            image: {},
	            showProtractor: true,
	            protractorX: 7.5,
	            protractorY: 0.5,
	            showRuler: false,
	            rulerLabel: "",
	            rulerTicks: 10,
	            rulerPixels: 40,
	            rulerLength: 10
	        };
	    },

	    getInitialState: function() {
	        return {};
	    },

	    render: function() {
	        var image = _.extend({}, defaultImage, this.props.image);
	        return React.createElement("div", {
	                className: 
	                    "perseus-widget perseus-widget-measurer " +
	                    "graphie-container above-scratchpad", 
	                
	                style: {width: this.props.box[0], height: this.props.box[1]}}, 
	            image.url &&
	                React.createElement("img", {
	                    src: image.url, 
	                    style: {
	                        top: image.top,
	                        left: image.left
	                    }}), 
	            
	            React.createElement("div", {className: "graphie", ref: "graphieDiv"})
	        );
	    },

	    componentDidMount: function() {
	        this.setupGraphie();
	    },

	    componentDidUpdate: function(prevProps) {
	        var shouldSetupGraphie = _.any([
	                "box", "showProtractor", "showRuler", "rulerLabel",
	                "rulerTicks", "rulerPixels", "rulerLength"
	            ],
	            function(prop) {
	                return prevProps[prop] !== this.props[prop];
	            },
	            this
	        );

	        if (shouldSetupGraphie) {
	            this.setupGraphie();
	        }
	    },

	    setupGraphie: function() {
	        var graphieDiv = this.refs.graphieDiv.getDOMNode();
	        $(graphieDiv).empty();
	        var graphie = this.graphie = KhanUtil.createGraphie(graphieDiv);

	        var scale = [40, 40];
	        var range = [
	            [0, this.props.box[0] / scale[0]],
	            [0, this.props.box[1] / scale[1]]
	        ];
	        graphie.init({
	            range: range,
	            scale: scale
	        });
	        graphie.addMouseLayer({
	            allowScratchpad: true
	        });

	        if (this.protractor) {
	            this.protractor.remove();
	        }

	        if (this.props.showProtractor) {
	            this.protractor = graphie.protractor([
	                this.props.protractorX,
	                this.props.protractorY
	            ]);
	        }

	        if (this.ruler) {
	            this.ruler.remove();
	        }

	        if (this.props.showRuler) {
	            this.ruler = graphie.ruler({
	                center: [
	                    (range[0][0] + range[0][1]) / 2,
	                    (range[1][0] + range[1][1]) / 2
	                ],
	                label: this.props.rulerLabel,
	                pixelsPerUnit: this.props.rulerPixels,
	                ticksPerUnit: this.props.rulerTicks,
	                units: this.props.rulerLength
	            });
	        }
	    },

	    getUserInput: function() {
	        return {};
	    },

	    simpleValidate: function(rubric) {
	        // TODO(joel) - I don't understand how this is useful!
	        return Measurer.validate(this.getUserInput(), rubric);
	    },

	    focus: $.noop,

	    statics: {
	        displayMode: "block"
	    }
	});


	_.extend(Measurer, {
	    validate: function(state, rubric) {
	        return {
	            type: "points",
	            earned: 1,
	            total: 1,
	            message: null
	        };
	    }
	});


	var MeasurerEditor = React.createClass({displayName: 'MeasurerEditor',
	    mixins: [Changeable, EditorJsonify],
	    className: "perseus-widget-measurer",

	    propTypes: {
	        box: React.PropTypes.arrayOf(React.PropTypes.number),
	        image: React.PropTypes.shape({
	            url: React.PropTypes.string,
	            top: React.PropTypes.number,
	            left: React.PropTypes.number
	        }),
	        showProtractor: React.PropTypes.bool,
	        showRuler: React.PropTypes.bool,
	        rulerLabel: React.PropTypes.string,
	        rulerTicks: React.PropTypes.number,
	        rulerPixels: React.PropTypes.number,
	        rulerLength: React.PropTypes.number
	    },

	    getDefaultProps: function() {
	        return {
	            box: [480, 480],
	            image: {},
	            showProtractor: true,
	            showRuler: false,
	            rulerLabel: "",
	            rulerTicks: 10,
	            rulerPixels: 40,
	            rulerLength: 10
	        };
	    },

	    render: function() {
	        var image = _.extend({}, defaultImage, this.props.image);

	        return React.createElement("div", {className: "perseus-widget-measurer"}, 
	            React.createElement("div", null, "Image displayed under protractor and/or ruler:"), 
	            React.createElement("div", null, "URL:", ' ', 
	                React.createElement("input", {type: "text", 
	                        className: "perseus-widget-measurer-url", 
	                        ref: "image-url", 
	                        defaultValue: image.url, 
	                        onChange: this._changeUrl}), 
	            React.createElement(InfoTip, null, 
	                React.createElement("p", null, "Create an image in graphie, or use the \"Add image\" function" + ' ' +
	                "to create a background.")
	            )
	            ), 
	            image.url && React.createElement("div", {className: "perseus-widget-row"}, 
	                React.createElement("div", {className: "perseus-widget-left-col"}, 
	                    React.createElement(NumberInput, {label: "Pixels from top:", 
	                        placeholder: 0, 
	                        onChange: this._changeTop, 
	                        value: image.top, 
	                        useArrowKeys: true})
	                ), 
	                React.createElement("div", {className: "perseus-widget-right-col"}, 
	                    React.createElement(NumberInput, {label: "Pixels from left:", 
	                        placeholder: 0, 
	                        onChange: this._changeLeft, 
	                        value: image.left, 
	                        useArrowKeys: true})
	                )
	            ), 
	            React.createElement("div", null, "Containing area [width, height]:", ' ', 
	                React.createElement(RangeInput, {
	                    onChange: this.change("box"), 
	                    value: this.props.box, 
	                    useArrowKeys: true})
	            ), 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                React.createElement("div", {className: "perseus-widget-left-col"}, 
	                    React.createElement(PropCheckBox, {label: "Show ruler", 
	                        showRuler: this.props.showRuler, 
	                        onChange: this.props.onChange})
	                ), 
	                React.createElement("div", {className: "perseus-widget-right-col"}, 
	                    React.createElement(PropCheckBox, {label: "Show protractor", 
	                        showProtractor: this.props.showProtractor, 
	                        onChange: this.props.onChange})
	                )
	            ), 
	            this.props.showRuler && React.createElement("div", null, 
	            React.createElement("div", null, 
	                React.createElement("label", null, 
	                    ' ', "Ruler label:", ' ', 
	                    React.createElement("select", {
	                        onChange: function(e) 
	                            {return this.change("rulerLabel", e.target.value);}.bind(this), 
	                        value: this.props.rulerLabel}, 
	                            React.createElement("option", {value: ""}, "None"), 
	                            React.createElement("optgroup", {label: "Metric"}, 
	                                this.renderLabelChoices([
	                                    ["milimeters", "mm"],
	                                    ["centimeters", "cm"],
	                                    ["meters", "m"],
	                                    ["kilometers", "km"]
	                                ])
	                            ), 
	                            React.createElement("optgroup", {label: "Imperial"}, 
	                                this.renderLabelChoices([
	                                    ["inches", "in"],
	                                    ["feet", "ft"],
	                                    ["yards", "yd"],
	                                    ["miles", "mi"]
	                                ])
	                            )
	                    )
	                )
	            ), 
	            React.createElement("div", null, 
	                React.createElement("label", null, 
	                    ' ', "Ruler ticks:", ' ', 
	                    React.createElement("select", {
	                        onChange: function(e) 
	                            {return this.change("rulerTicks", +e.target.value);}.bind(this), 
	                        value: this.props.rulerTicks}, 
	                            _.map([1, 2, 4, 8, 10, 16], function(n) {
	                                return React.createElement("option", {value: n}, n);
	                            })
	                    )
	                )
	            ), 
	            React.createElement("div", null, 
	                React.createElement(NumberInput, {label: "Ruler pixels per unit:", 
	                    placeholder: 40, 
	                    onChange: this.change("rulerPixels"), 
	                    value: this.props.rulerPixels, 
	                    useArrowKeys: true})
	            ), 
	            React.createElement("div", null, 
	                React.createElement(NumberInput, {label: "Ruler length in units:", 
	                    placeholder: 10, 
	                    onChange: this.change("rulerLength"), 
	                    value: this.props.rulerLength, 
	                    useArrowKeys: true})
	            )
	            )
	        );
	    },

	    _changeUrl: function(e) {
	        this._changeImage("url", e.target.value);
	    },

	    _changeTop: function(newTop) {
	        this._changeImage("top", newTop);
	    },

	    _changeLeft: function(newLeft) {
	        this._changeImage("left", newLeft);
	    },

	    _changeImage: function(subProp, newValue) {
	        var image = _.clone(this.props.image);
	        image[subProp] = newValue;
	        this.change("image", image);
	    },

	    renderLabelChoices: function(choices) {
	        return _.map(choices, function(nameAndValue) {
	            return React.createElement("option", {value: nameAndValue[1]}, nameAndValue[0]);
	        });
	    }
	});

	propUpgrades = {
	    1: function(v0props)  {
	        var v1props = _(v0props).chain()
	            .omit("imageUrl", "imageTop", "imageLeft")
	            .extend({
	                image: {
	                    url: v0props.imageUrl,
	                    top: v0props.imageTop,
	                    left: v0props.imageLeft
	                }
	            })
	            .value();
	        return v1props;
	    }
	};

	module.exports = {
	    name: "measurer",
	    displayName: "Measurer",
	    widget: Measurer,
	    editor: MeasurerEditor,
	    version: {major: 1, minor: 0},
	    propUpgrades: propUpgrades
	};


/***/ },
/* 31 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);
	var _ = __webpack_require__(60);

	var Changeable   = __webpack_require__(66);
	var EditorJsonify = __webpack_require__(67);

	var ButtonGroup  = __webpack_require__(78);
	var InfoTip      = __webpack_require__(69);
	var Interactive2 = __webpack_require__(80);
	var NumberInput  = __webpack_require__(85);
	var PropCheckBox = __webpack_require__(58);
	var RangeInput   = __webpack_require__(83);
	var MathOutput   = __webpack_require__(88);

	var ApiOptions = __webpack_require__(12).Options;

	var Graphie = __webpack_require__(72);
	var MovablePoint = Graphie.MovablePoint;
	var Line = Graphie.Line;
	var Label = Graphie.Label;

	var knumber = __webpack_require__(103).number;
	var kpoint = __webpack_require__(103).point;

	var bound = function(x, gt, lt)  {return Math.min(Math.max(x, gt), lt);};
	var deepEq = __webpack_require__(3).deepEq;
	var assert = __webpack_require__(89).assert;

	var EN_DASH = "\u2013";

	var reverseRel = {
	    ge: "le",
	    gt: "lt",
	    le: "ge",
	    lt: "gt"
	};

	var toggleStrictRel = {
	    ge: "gt",
	    gt: "ge",
	    le: "lt",
	    lt: "le"
	};

	function formatImproper(n, d) {
	    if (d === 1) {
	        return "" + n;
	    } else {
	        return n + "/" + d;
	    }
	}

	function formatMixed(n, d) {
	    if (n < 0) {
	        return "-" + formatMixed(-n, d);
	    }
	    var w = Math.floor(n / d);
	    if (w === 0) {
	        return formatImproper(n, d);
	    } else if (n - w * d === 0) {
	        return "" + w;
	    } else {
	        return w + "\\:" + formatImproper(n - w * d, d);
	    }
	}

	function formatNonReduced(n, d, base) {
	    var factor = Math.floor(base / d);
	    return formatImproper(n * factor, base);
	}

	_label = function(graphie, labelStyle, pos, value, base)  {
	    value = value || pos;

	    // TODO(jack): Find out if any exercises have "decimal ticks" set,
	    // and if so, re-save them and remove this check.
	    if (labelStyle === "decimal" || labelStyle === "decimal ticks") {
	        return graphie.label([pos, -0.53],
	            Math.round(value * 100) / 100, "center");
	    } else if (labelStyle === "improper") {
	        var frac = KhanUtil.toFraction(value);
	        return graphie.label([pos, -0.53],
	                formatImproper(frac[0], frac[1]), "center");
	    } else if (labelStyle === "mixed") {
	        var frac = KhanUtil.toFraction(value);
	        return graphie.label([pos, -0.53],
	                formatMixed(frac[0], frac[1]), "center");
	    } else if (labelStyle === "non-reduced") {
	        var frac = KhanUtil.toFraction(value);
	        return graphie.label([pos, -0.53],
	                formatNonReduced(frac[0], frac[1], base), "center");
	    }
	};

	TickMarks = Graphie.createSimpleClass(function(graphie, props)  {
	    // Avoid infinite loop
	    if (!_.isFinite(props.tickStep) || props.tickStep <= 0) {
	        return []; // this has screwed me for the last time!
	    }

	    var results = [];

	    // For convenience, extract some props into separate variables
	    var range = props.range;
	    var labelRange = props.labelRange;
	    var range = props.range;
	    var leftLabel = labelRange[0] == null ? range[0] : labelRange[0];
	    var rightLabel = labelRange[1] == null ? range[1] : labelRange[1];

	    // Find base via GCD for non-reduced fractions
	    var base;
	    if (props.labelStyle === "non-reduced") {
	        var fractions = [leftLabel, rightLabel];
	        for (var i = 0; i <= props.numDivisions; i++) {
	            var x = range[0] + i * props.tickStep;
	            fractions.push(x);
	        }
	        var getDenom = function(x)  {return knumber.toFraction(x)[1];};
	        var denoms = _.map(fractions, getDenom);
	        base = _.reduce(denoms, function(x, y)  {return KhanUtil.getLCM(x, y);});
	    } else {
	        base = undefined;
	    }

	    // Draw and save the tick marks and tick labels
	    for (var i = 0; i <= props.numDivisions; i++) {
	        var x = range[0] + i * props.tickStep;
	        results.push(graphie.line([x, -0.2], [x, 0.2]));

	        var labelTicks = props.labelTicks;
	        if (labelTicks || props.labelStyle === "decimal ticks") {
	            results.push(_label(graphie, props.labelStyle, x, x, base));
	        }
	    }

	    // Render the text labels
	    graphie.style({color: KhanUtil.DYNAMIC}, function()  {
	        results.push(_label(graphie, props.labelStyle, leftLabel, leftLabel,
	            base));
	        results.push(_label(graphie, props.labelStyle, rightLabel, rightLabel,
	            base));
	    });

	    // Render the labels' lines
	    graphie.style(
	        {
	            stroke: KhanUtil.DYNAMIC,
	            strokeWidth: 3.5
	        },
	        function()  {
	            results.push(graphie.line([leftLabel, -0.2], [leftLabel, 0.2]));
	            results.push(graphie.line([rightLabel, -0.2], [rightLabel, 0.2]));
	        }
	    );

	    return results;
	});


	var NumberLine = React.createClass({displayName: 'NumberLine',
	    mixins: [Changeable],

	    propTypes: {
	        range: React.PropTypes.arrayOf(React.PropTypes.number).isRequired,

	        labelRange: React.PropTypes.array.isRequired,
	        labelStyle: React.PropTypes.string.isRequired,
	        labelTicks: React.PropTypes.bool.isRequired,

	        divisionRange: React.PropTypes.arrayOf(
	            React.PropTypes.number
	        ).isRequired,
	        numDivisions: React.PropTypes.number.isRequired,
	        snapDivisions: React.PropTypes.number.isRequired,

	        isTickCtrl: React.PropTypes.bool.isRequired,
	        isInequality: React.PropTypes.bool.isRequired,

	        numLinePosition: React.PropTypes.number.isRequired,
	        rel: React.PropTypes.oneOf(["lt", "gt", "le", "ge"])
	    },

	    getDefaultProps: function() {
	        return {
	            range: [0, 10],
	            labelStyle: "decimal",
	            labelRange: [null, null],
	            divisionRange: [1, 12],
	            labelTicks: true,
	            isTickCtrl: false,
	            isInequality: false,
	            numLinePosition: 0,
	            snapDivisions: 2,
	            rel: "ge",
	            apiOptions: ApiOptions.defaults
	        };
	    },

	    isValid: function() {
	        var range = this.props.range;
	        var initialX = this.props.numLinePosition;
	        var divisionRange = this.props.divisionRange;

	        initialX = initialX == null ? range[0] : initialX;

	        return range[0] <  range[1] &&
	               initialX >= range[0] &&
	               initialX <= range[1] &&
	               divisionRange[0] < divisionRange[1] &&
	               0 < this.props.numDivisions &&
	               0 < this.props.snapDivisions;
	    },

	    render: function() {
	        var range = this.props.range;
	        var width = range[1] - range[0];
	        var divisionRange = this.props.divisionRange;
	        var divRangeString = divisionRange[0] + EN_DASH + divisionRange[1];
	        var invalidNumDivisions = this.props.numDivisions < divisionRange[0] ||
	                this.props.numDivisions > divisionRange[1];

	        var inequalityControls = React.createElement("div", null, 
	            React.createElement("input", {
	                type: "button", 
	                className: "simple-button", 
	                value: $._("Switch direction"), 
	                onClick: this.handleReverse}), 
	            React.createElement("input", {
	                type: "button", 
	                className: "simple-button", 
	                value: _(["le", "ge"]).contains(this.props.rel) ?
	                        $._("Make circle open") :
	                        $._("Make circle filled"), 
	                onClick: this.handleToggleStrict})
	        );

	        var tickCtrl;
	        if (this.props.isTickCtrl) {
	            var Input;
	            if (this.props.apiOptions.staticRender) {
	                Input = MathOutput;
	            } else {
	                Input = NumberInput;
	            }
	            tickCtrl = React.createElement("label", null, $_(null, "Number of divisions:"), " ", 
	                React.createElement(Input, {
	                    ref: "tick-ctrl", 
	                    value: this.props.numDivisions || divisionRange[0], 
	                    checkValidity: function(val) 
	                        {return val >= divisionRange[0] && val <= divisionRange[1];}, 
	                    onChange: this.onNumDivisionsChange, 
	                    onFocus: this._handleTickCtrlFocus, 
	                    onBlur: this._handleTickCtrlBlur, 
	                    useArrowKeys: true})
	            );
	        }

	        return React.createElement("div", {className: "perseus-widget " +
	                "perseus-widget-interactive-number-line"}, 
	            tickCtrl, 
	            !this.isValid() ?
	                React.createElement("div", {className: "perseus-error"}, 
	                    "Invalid number line configuration."
	                ) :
	                (this.props.isTickCtrl && invalidNumDivisions ?
	                    React.createElement("div", {className: "perseus-error"}, 
	                        $_({divRangeString: divRangeString}, 
	                            "Please make sure the number of divisions is in the" + ' ' +
	                            "range %(divRangeString)s."
	                        )
	                    ) : this._renderGraphie()), 
	            this.props.isInequality && inequalityControls
	        );
	    },

	    onNumDivisionsChange: function(numDivisions, cb) {
	        var divRange = this.props.divisionRange.slice();
	        var width = this.props.range[1] - this.props.range[0];

	        // Don't allow a fraction for the number of divisions
	        numDivisions = Math.round(numDivisions);

	        // Don't allow negative numbers for the number of divisions
	        numDivisions = numDivisions < 0 ? numDivisions * -1 : numDivisions;

	        // If the number of divisions isn't blank, update the number line
	        if (numDivisions) {
	            var nextProps = _.extend({}, this.props, {
	                tickStep: width / numDivisions
	            });

	            var newNumLinePosition = this.snapNumLinePosition(
	                nextProps,
	                this.props.numLinePosition
	            );

	            this.props.onChange({
	                divisionRange: divRange,
	                numDivisions: numDivisions,
	                numLinePosition: newNumLinePosition
	            }, cb);
	        }
	    },

	    _handleTickCtrlFocus: function() {
	        this.props.onFocus(["tick-ctrl"]);
	    },

	    _handleTickCtrlBlur: function() {
	        this.props.onBlur(["tick-ctrl"]);
	    },

	    focus: function() {
	        if (this.props.isTickCtrl) {
	            this.refs["tick-ctrl"].focus();
	            return true;
	        }
	    },

	    focusInputPath: function(path) {
	        if (path.length === 1) {
	            this.refs[path[0]].focus();
	        }
	    },

	    blurInputPath: function(path) {
	        if (path.length === 1) {
	            this.refs[path[0]].blur();
	        }
	    },

	    getInputPaths: function() {
	        if (this.props.isTickCtrl) {
	            return [["tick-ctrl"]];
	        } else {
	            return [];
	        }
	    },

	    getDOMNodeForPath: function(inputPath) {
	        if (inputPath.length === 1) {
	            return this.refs[inputPath[0]].getDOMNode();
	        }
	    },

	    getGrammarTypeForPath: function(inputPath) {
	        if (inputPath.length === 1 && inputPath[0] === "tick-ctrl") {
	            return "number";
	        }
	    },

	    setInputValue: function(inputPath, value, callback) {
	        if (inputPath.length === 1 && inputPath[0] === "tick-ctrl") {
	            this.onNumDivisionsChange(value, callback);
	        }
	    },

	    _renderGraphie: function() {
	        // Position variables
	        var widthInPixels = 400;
	        var range = this.props.range;
	        var width = range[1] - range[0];
	        var scale = width / widthInPixels;
	        var buffer = 30 * scale;

	        // Initiate the graphie without actually drawing anything
	        var left = range[0] - buffer;
	        var right = range[1] + buffer;
	        var bottom = -1;
	        var top = 1;

	        var options = _.pick(this.props, [
	            "range",
	            "isTickCtrl",
	        ]);

	        // TODO(aria): Maybe save this as `this.calculatedProps`?
	        var props = _.extend({}, this.props, {
	            tickStep: width / this.props.numDivisions
	        });

	        return React.createElement(Graphie, {
	                ref: "graphie", 
	                box: [460, 80], 
	                options: options, 
	                onMouseDown: function(coord)  {
	                    this.refs.graphie.movables.numberLinePoint.grab(coord);
	                }.bind(this), 
	                setup: this._setupGraphie}, 
	            TickMarks(_.pick(props, [
	                "range",
	                "numDivisions",
	                "labelTicks",
	                "labelStyle",
	                "labelRange",
	                "tickStep"
	            ])), 
	            this._renderInequality(props), 
	            this._renderNumberLinePoint(props)
	        );
	    },

	    snapNumLinePosition: function(props, numLinePosition) {
	        var left = props.range[0];
	        var right = props.range[1];
	        var snapX = props.tickStep / props.snapDivisions;

	        x = bound(numLinePosition, left, right);
	        x = left + knumber.roundTo(x - left, snapX);
	        assert(_.isFinite(x));
	        return x;
	    },

	    _renderNumberLinePoint: function(props) {
	        var isOpen = _(["lt", "gt"]).contains(props.rel);
	        var normalStyle = {
	            fill: isOpen ? KhanUtil._BACKGROUND : KhanUtil.INTERACTIVE,
	            "stroke-width": isOpen ? 3 : 1
	        };
	        var highlightStyle = {
	            fill: isOpen ? KhanUtil._BACKGROUND : KhanUtil.INTERACTING,
	            "stroke-width": isOpen ? 3 : 1
	        };

	        return React.createElement(MovablePoint, {
	            ref: "numberLinePoint", 
	            pointSize: 6, 
	            coord: [props.numLinePosition, 0], 
	            constraints: [
	                function(coord, prevCoord)  {  // constrain-y
	                    return [coord[0], prevCoord[1]];
	                },
	                function(coord, prevCoord)  {  // snap X
	                    var x = this.snapNumLinePosition(props, coord[0]);
	                    return [x, coord[1]];
	                }.bind(this)
	            ], 
	            normalStyle: normalStyle, 
	            highlightStyle: highlightStyle, 
	            onMove: function(coord)  {
	                this.change({numLinePosition: coord[0]});
	            }.bind(this)});
	    },

	    handleReverse: function() {
	        var newRel = reverseRel[this.props.rel];
	        this.props.onChange({rel: newRel});
	    },

	    handleToggleStrict: function() {
	        var newRel = toggleStrictRel[this.props.rel];
	        this.props.onChange({rel: newRel});
	    },

	    _getInequalityEndpoint: function(props) {
	        var isGreater = _(["ge","gt"]).contains(props.rel);
	        var widthInPixels = 400;
	        var range = props.range;
	        var scale = (range[1] - range[0]) / widthInPixels;
	        var buffer = 30 * scale;
	        var left = range[0] - buffer;
	        var right = range[1] + buffer;
	        var end = isGreater ? [right, 0] : [left, 0];
	        return end;
	    },

	    _renderInequality: function(props) {
	        if (props.isInequality) {
	            var end = this._getInequalityEndpoint(props);
	            var style = {
	                arrows: "->",
	                stroke: KhanUtil.DYNAMIC,
	                strokeWidth: 3.5
	            };

	            return React.createElement(Line, {
	                start: [props.numLinePosition, 0], 
	                end: end, 
	                style: style});
	        } else {
	            return null;
	        }
	    },

	    _setupGraphie: function(graphie, options) {
	        // Ensure a sane configuration to avoid infinite loops
	        if (!this.isValid()) {return;}

	        // Position variables
	        var widthInPixels = 400;
	        var range = options.range;
	        var scale = (range[1] - range[0]) / widthInPixels;
	        var buffer = 30 * scale;

	        // Initiate the graphie without actually drawing anything
	        var left = range[0] - buffer;
	        var right = range[1] + buffer;
	        var bottom = -1;
	        var top = 1;

	        graphie.init({
	            range: [[left, right], [bottom, top]],
	            scale: [1 / scale, 40]
	        });

	        // Draw the number line
	        var center = (range[0] + range[1]) / 2;
	        graphie.line([center, 0], [right, 0], {arrows: "->"});
	        graphie.line([center, 0], [left, 0], {arrows: "->"});
	    },

	    getUserInput: function() {
	        return {
	            numLinePosition: this.props.numLinePosition,
	            rel: this.props.isInequality ? this.props.rel : "eq",
	            numDivisions: this.props.numDivisions,
	            divisionRange: this.props.divisionRange
	        };
	    },

	    simpleValidate: function(rubric) {
	        return NumberLine.validate(this.getUserInput(), rubric);
	    },

	    statics: {
	        displayMode: "block"
	    }
	});


	_.extend(NumberLine, {
	    validate: function(state, rubric) {
	        var range = rubric.range;
	        var divisionRange = state.divisionRange;
	        var start = rubric.initialX != null ? rubric.initialX : range[0];
	        var startRel = rubric.isInequality ? "ge" : "eq";
	        var correctRel = rubric.correctRel || "eq";
	        var correctPos = knumber.equal(
	                state.numLinePosition,
	                rubric.correctX || 0);
	        var outsideAllowedRange = state.numDivisions > divisionRange[1] ||
	                state.numDivisions < divisionRange[0];

	        if (state.isTickCrtl && outsideAllowedRange) {
	            return {
	                type: "invalid",
	                message: "Number of divisions is outside the allowed range."
	            };
	        } else if (correctPos && correctRel === state.rel) {
	            return {
	                type: "points",
	                earned: 1,
	                total: 1,
	                message: null
	            };
	        } else if (state.numLinePosition === start && state.rel === startRel) {
	            // We're where we started.
	            return {
	                type: "invalid",
	                message: null
	            };
	        } else {
	            return {
	                type: "points",
	                earned: 0,
	                total: 1,
	                message: null
	            };
	        }
	    }
	});


	var NumberLineEditor = React.createClass({displayName: 'NumberLineEditor',
	    getDefaultProps: function() {
	        return {
	            range: [0, 10],
	            labelRange: [null, null],
	            divisionRange: [1, 12],
	            labelStyle: "decimal",
	            labelTicks: true,
	            numDivisions: 5,
	            tickStep: null,
	            snapDivisions: 2,
	            correctRel: "eq",
	            correctX: null,
	            initialX: null
	        };
	    },

	    mixins: [EditorJsonify],

	    render: function() {
	        var range = this.props.range;
	        var labelRange = this.props.labelRange;
	        var divisionRange = this.props.divisionRange;

	        range[0] = +range[0]; range[1] = +range[1];

	        var width = range[1] - range[0];
	        var numDivisions = this.props.numDivisions;
	        var snapDivisions = this.props.snapDivisions;
	        var tickStep = this.props.tickStep;
	        var isTickCtrl = this.props.isTickCtrl;

	        if (!isTickCtrl) {
	            // this will help constrain the answer to what is reachable
	            step = tickStep ? tickStep / snapDivisions :
	                  (width / numDivisions) / snapDivisions;
	        } else {
	            // but if tickCtrl is on, the range of what is reachable is
	            // rather large, and it becomes obnoxious to check for this
	            step = null;
	        }

	        var labelStyleEditorButtons = [
	              {value: "decimal", content: "0.75", title: "Decimals",},
	              {value: "improper", content: "\u2077\u2044\u2084",
	                title: "Improper fractions"},
	              {value: "mixed", content: "1\u00BE",
	                title: "Mixed numbers"},
	              {value: "non-reduced", content: "\u2078\u2044\u2084",
	                title: "Non-reduced"}];

	        return React.createElement("div", {className: "perseus-widget-number-line-editor"}, 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                React.createElement("label", null, "correct x"), 
	                React.createElement("select", {value: this.props.correctRel, 
	                  onChange: this.onChangeRelation}, 
	                    React.createElement("option", {value: "eq"}, " = "), 
	                    React.createElement("option", {value: "lt"}, " < "), 
	                    React.createElement("option", {value: "gt"}, " > "), 
	                    React.createElement("option", {value: "le"}, " ≤ "), 
	                    React.createElement("option", {value: "ge"}, " ≥ ")
	                ), 
	                React.createElement(NumberInput, {
	                    value: this.props.correctX, 
	                    format: this.props.labelStyle, 
	                    onChange: this.onNumChange.bind(this, "correctX"), 
	                    checkValidity: function(val) 
	                        {return val >= range[0] && val <= range[1] &&
	                        (!step || knumber.isInteger((val - range[0]) / step));}, 
	                    
	                    placeholder: "answer", size: "normal", 
	                    useArrowKeys: true}), 
	                React.createElement(InfoTip, null, React.createElement("p", null, 
	                    "This is the correct answer. The answer is validated" + ' ' +
	                    "(as right or wrong) by using only the end position of the" + ' ' +
	                    "point and the relation (=, <, >, ≤, ≥)"
	                ))
	            ), 

	            React.createElement("div", {className: "perseus-widget-row"}, 
	                React.createElement(NumberInput, {
	                    label: "position", 
	                    value: this.props.initialX, 
	                    format: this.props.labelStyle, 
	                    onChange: this.onNumChange.bind(this, "initialX"), 
	                    placeholder: range[0], 
	                    checkValidity: function(val)  {return val >= range[0] && val <= range[1];}, 
	                    useArrowKeys: true}), 
	                React.createElement("span", null, " ∈ ", ' ', " "), 
	                React.createElement(RangeInput, {
	                    value: range, 
	                    onChange: this.onRangeChange, 
	                    format: this.props.labelStyle, 
	                    useArrowKeys: true}), 
	                React.createElement(InfoTip, null, React.createElement("p", null, 
	                    "This controls the initial position of the point along the" + ' ' +
	                    "number line and the ", React.createElement("strong", null, "range"), ", the position" + ' ' +
	                    "of the endpoints of the number line. Setting the range" + ' ' +
	                    "constrains the position of the answer and the labels."
	                ))
	            ), 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                React.createElement("div", {className: "perseus-widget-left-col"}, 
	                    React.createElement("span", null, "labels "), 
	                    React.createElement(NumberInput, {
	                        value: labelRange[0], placeholder: range[0], 
	                        format: this.props.labelStyle, 
	                        checkValidity: function(val) 
	                            {return val >= range[0] && val <= range[1];}, 
	                        onChange: this.onLabelRangeChange.bind(this, 0), 
	                        useArrowKeys: true}), 
	                    React.createElement("span", null, " & "), 
	                    React.createElement(NumberInput, {
	                        value: labelRange[1], placeholder: range[1], 
	                        format: this.props.labelStyle, 
	                        checkValidity: function(val) 
	                            {return val >= range[0] && val <= range[1];}, 
	                        onChange: this.onLabelRangeChange.bind(this, 1), 
	                        useArrowKeys: true}), 
	                    React.createElement(InfoTip, null, React.createElement("p", null, 
	                        "This controls the position of the left / right labels." + ' ' +
	                        "By default, the labels are set by the range ", React.createElement("br", null), 
	                        React.createElement("strong", null, "Note:"), " Ensure that the labels line up" + ' ' +
	                        "with the tick marks, or it may be confusing for users."
	                    ))
	                )
	            ), 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                React.createElement("div", {className: "perseus-widget-left-col"}, 
	                    React.createElement("label", null, "style"), 
	                    React.createElement(ButtonGroup, {
	                        allowEmpty: false, 
	                        value: this.props.labelStyle, 
	                        buttons: labelStyleEditorButtons, 
	                        onChange: this.onLabelStyleChange}), 
	                    React.createElement(InfoTip, null, React.createElement("p", null, 
	                        "This controls the styling of the labels for the two" + ' ' +
	                        "main labels as well as all the tick mark labels," + ' ' +
	                        "if applicable. Your choices are decimal," + ' ' +
	                        "improper fractions, mixed fractions, and non-reduced" + ' ' +
	                        "fractions."
	                    ))
	                )
	            ), 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                React.createElement("div", {className: "perseus-widget-left-col"}, 
	                    React.createElement(PropCheckBox, {
	                        label: "show tick controller", 
	                        isTickCtrl: this.props.isTickCtrl, 
	                        onChange: this.props.onChange})
	                ), 
	                React.createElement("div", {className: "perseus-widget-right-col"}, 
	                    React.createElement(PropCheckBox, {
	                        label: "show label ticks", 
	                        labelTicks: this.props.labelTicks, 
	                        onChange: this.props.onChange})
	                )
	            ), 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                isTickCtrl && React.createElement("span", null, 
	                    React.createElement(NumberInput, {
	                        label: "start num divisions at", 
	                        value: this.props.numDivisions || null, 
	                        format: "decimal", 
	                        onChange: this.onNumDivisionsChange, 
	                        checkValidity: function(val) 
	                            {return val >= divisionRange[0] && val <= divisionRange[1];}, 
	                        placeholder: width / this.props.tickStep, 
	                        useArrowKeys: true}), 
	                    React.createElement(InfoTip, null, React.createElement("p", null, 
	                        "This controls the number (and position) of the tick" + ' ' +
	                        "marks. The number of divisions is constrained to", 
	                        " " + divisionRange[0] + EN_DASH + divisionRange[1], ".", 
	                        React.createElement("br", null), 
	                        React.createElement("strong", null, "Note:"), " The user will be able to specify" + ' ' +
	                        "the number of divisions in a number input."
	                    ))), 
	                !isTickCtrl && React.createElement("span", null, 
	                    React.createElement(NumberInput, {
	                        label: "num divisions", 
	                        value: this.props.numDivisions || null, 
	                        format: "decimal", 
	                        onChange: this.onNumDivisionsChange, 
	                        checkValidity: function(val) 
	                            {return val >= divisionRange[0] && val <= divisionRange[1];}, 
	                        placeholder: width / this.props.tickStep, 
	                        useArrowKeys: true}), 
	                    React.createElement(NumberInput, {
	                        label: " or tick step", 
	                        value: this.props.tickStep || null, 
	                        format: this.props.labelStyle, 
	                        onChange: this.onTickStepChange, 
	                        checkValidity: function(val)  {return val > 0 && val <= width;}, 
	                        placeholder: width / this.props.numDivisions, 
	                        useArrowKeys: true}), 
	                    React.createElement(InfoTip, null, React.createElement("p", null, 
	                        "This controls the number (and position) of the tick" + ' ' +
	                        "marks; you can either set the number of divisions (2" + ' ' +
	                        "divisions would split the entire range in two halves)," + ' ' +
	                        "or the tick step (the distance between ticks) and the" + ' ' +
	                        "other value will be updated accordingly. ", React.createElement("br", null), 
	                        React.createElement("strong", null, "Note:"), " There is no check to see if" + ' ' +
	                        "labels coordinate with the tick marks, which may be" + ' ' +
	                        "confusing for users if the blue labels and black ticks" + ' ' +
	                        "are off-step."
	                    )))
	            ), 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                React.createElement(NumberInput, {
	                    label: "snap increments per tick", 
	                    value: snapDivisions, 
	                    checkValidity: function(val)  {return val > 0;}, 
	                    format: this.props.labelStyle, 
	                    onChange: this.onNumChange.bind(this, "snapDivisions"), 
	                    useArrowKeys: true}), 
	                React.createElement(InfoTip, null, React.createElement("p", null, 
	                    "This determines the number of different places the point" + ' ' +
	                    "will snap between two adjacent tick marks. ", React.createElement("br", null), 
	                    React.createElement("strong", null, "Note:"), "Ensure the required number of" + ' ' +
	                    "snap increments is provided to answer the question."
	                ))
	            )

	        );
	    },

	    onRangeChange: function(range) {
	        // Changing the range constrains the initial position, as well as the
	        // position of the answer and labels. Atm, it just marks them as
	        // invalid and prevents the number line from showing; it was annoying
	        // to change it for them, because if they're typing in fractions,
	        // it registers one-at-a-time and messes things up.
	        this.props.onChange({range: range});
	    },

	    onLabelRangeChange: function(i, num) {
	        var range = this.props.range.slice(),
	            labelRange = this.props.labelRange.slice(),
	            otherNum = labelRange[1-i];

	        if (num == null || otherNum == null) {
	            labelRange[i] = num;
	        } else {
	            // If both labels have values, this updates the "appropriate" one.
	            // It enforces that the position of the left label <= right label.
	            // If left otherwise, it makes certain aspects of validation hard.
	            labelRange = [Math.min(num, otherNum), Math.max(num, otherNum)];
	        }

	        this.props.onChange({labelRange: labelRange});
	    },

	    onDivisionRangeChange: function(divisionRange) {
	        var numDivisions = this.props.numDivisions;
	        numDivisions = bound(numDivisions, divisionRange[0], divisionRange[1]);
	        this.props.onChange({
	            divisionRange: divisionRange,
	            numDivisions: numDivisions});
	    },

	    onNumChange: function(key, value) {
	        var opts = {};
	        opts[key] = value;
	        this.props.onChange(opts);
	    },

	    onNumDivisionsChange: function(numDivisions) {
	        var divRange = this.props.divisionRange.slice();

	        if (!_.isFinite(numDivisions)) {
	            numDivisions = null;
	        }

	        // Don't allow a fraction for the number of divisions
	        numDivisions = Math.round(numDivisions);

	        // Don't allow negative numbers for the number of divisions
	        numDivisions = numDivisions < 0 ? numDivisions * -1 : numDivisions;

	        // If the number of divisions isn't blank, update the number line
	        if (numDivisions) {
	            // Constrain numDivisions to be within the allowed range
	            numDivisions = Math.min(
	                divRange[1],
	                Math.max(divRange[0], numDivisions)
	            );

	            this.props.onChange({
	                tickStep: null,
	                divisionRange: divRange,
	                numDivisions: numDivisions,
	            });
	        }
	    },

	    onTickStepChange: function(tickStep) {
	        this.props.onChange({
	            numDivisions: null,
	            tickStep: tickStep,
	        });
	    },

	    onChangeRelation: function(e) {
	        value = e.target.value;
	        this.props.onChange({
	            correctRel: value,
	            isInequality: value !== "eq",
	        });
	    },

	    onLabelStyleChange: function(labelStyle) {
	        this.props.onChange({
	            labelStyle: labelStyle
	        });
	    }
	});

	var NumberLineTransform = function(editorProps)  {
	    var props = _.pick(editorProps, [
	        "range",

	        "labelRange",
	        "labelStyle",
	        "labelTicks",

	        "divisionRange",
	        "snapDivisions",

	        "isTickCtrl",
	        "isInequality"
	    ]);

	    var numLinePosition = (editorProps.initialX != null) ?
	            editorProps.initialX :
	            editorProps.range[0];

	    var width = editorProps.range[1] - editorProps.range[0];

	    var numDivisions;
	    if (editorProps.numDivisions != null) {
	        numDivisions = editorProps.numDivisions;
	    } else if (editorProps.tickStep != null) {
	        numDivisions = width / editorProps.tickStep;
	    } else {
	        numDivisions = undefined; // send to getDefaultProps()
	    }

	    _.extend(props, {
	        numLinePosition: numLinePosition,
	        numDivisions: numDivisions
	    });

	    return props;
	};

	module.exports = {
	    name: "number-line",
	    displayName: "Number line",
	    widget: NumberLine,
	    editor: NumberLineEditor,
	    transform: NumberLineTransform
	};


/***/ },
/* 32 */
/***/ function(module, exports, __webpack_require__) {

	var React         = __webpack_require__(61);
	var _ = __webpack_require__(60);

	var Changeable    = __webpack_require__(66);
	var EditorJsonify = __webpack_require__(67);

	var InfoTip = __webpack_require__(69);
	var PropCheckBox = __webpack_require__(58);
	var NumberInput = __webpack_require__(85);
	var ButtonGroup = __webpack_require__(78);
	var MultiButtonGroup = __webpack_require__(81);
	var InputWithExamples = __webpack_require__(75);
	var ParseTex = __webpack_require__(84).parseTex;

	var ApiClassNames   = __webpack_require__(12).ClassNames;
	var ApiOptions      = __webpack_require__(12).Options;
	var EnabledFeatures = __webpack_require__(49);

	var Editor = __webpack_require__(7);

	var firstNumericalParse = __webpack_require__(3).firstNumericalParse;

	var answerFormButtons = [
	    {title: "Integers", value: "integer", content: "6"},
	    {title: "Decimals", value: "decimal", content: "0.75"},
	    {title: "Proper fractions", value: "proper", content: "\u2157"},
	    {title: "Improper fractions", value: "improper",
	        content: "\u2077\u2044\u2084"},
	    {title: "Mixed numbers", value: "mixed", content: "1\u00BE"},
	    {title: "Numbers with \u03C0", value: "pi", content: "\u03C0"}
	];

	var formExamples = {
	    "integer": function(options)  {return $._("an integer, like $6$");},
	    "proper": function(options)  {return options.simplify === "optional" ?
	        $._("a *proper* fraction, like $1/2$ or $6/10$") :
	        $._("a *simplified proper* fraction, like $3/5$");},
	    "improper": function(options)  {return options.simplify === "optional" ?
	        $._("an *improper* fraction, like $10/7$ or $14/8$") :
	        $._("a *simplified improper* fraction, like $7/4$");},
	    "mixed": function()  {return $._("a mixed number, like $1\\ 3/4$");},
	    "decimal": function()  {return $._("an *exact* decimal, like $0.75$");},
	    "pi": function()  {return $._("a multiple of pi, like $12\\ \\text{pi}$ or " +
	                "$2/3\\ \\text{pi}$");}
	};

	var NumericInput = React.createClass({displayName: 'NumericInput',
	    propTypes: {
	        currentValue: React.PropTypes.string,
	        enabledFeatures: EnabledFeatures.propTypes,
	        reviewModeRubric: React.PropTypes.object,
	    },

	    getDefaultProps: function() {
	        return {
	            currentValue: "",
	            size: "normal",
	            enabledFeatures: EnabledFeatures.defaults,
	            apiOptions: ApiOptions.defaults,
	            coefficient: false,
	            answerForms: [],
	        };
	    },

	    render: function() {
	        // HACK(johnsullivan): Create a function with shared logic between this
	        // and InputNumber.
	        var rubric = this.props.reviewModeRubric;
	        if (rubric) {
	            var score = this.simpleValidate(rubric);
	            var correct = score.type === "points" &&
	                          score.earned === score.total;

	            var answerBlurb = null;
	            if (!correct) {
	                var correctAnswers = _.filter(
	                    rubric.answers, function(answer)  {return answer.status === "correct";});
	                var answerComponents = _.map(correctAnswers, function(answer, key)  {
	                    // Figure out how this answer is supposed to be displayed
	                    var format = "decimal";
	                    if (answer.answerForms && answer.answerForms[0]) {
	                        // NOTE(johnsullivan): This isn't exactly ideal, but
	                        // it does behave well for all the currently known
	                        // problems. See D14742 for some discussion on
	                        // alternate strategies.
	                        format = answer.answerForms[0]
	                    }

	                    var answerString = KhanUtil.toNumericString(answer.value,
	                                                                format);
	                    if (answer.maxError) {
	                        answerString += " \u00B1 " +
	                            KhanUtil.toNumericString(answer.maxError, format);
	                    }
	                    return React.createElement("span", {key: key, className: "perseus-possible-answer"}, 
	                        answerString
	                    )
	                });
	                answerBlurb = React.createElement("span", {className: "perseus-possible-answers"}, 
	                    answerComponents
	                );
	            }
	        }

	        var classes = {};
	        classes["perseus-input-size-" + this.props.size] = true;
	        classes[ApiClassNames.CORRECT] =
	            rubric && correct && this.props.currentValue;
	        classes[ApiClassNames.INCORRECT] =
	            rubric && !correct && this.props.currentValue;
	        classes[ApiClassNames.UNANSWERED] = rubric && !this.props.currentValue;

	        var input = React.createElement(InputWithExamples, {
	            ref: "input", 
	            value: this.props.currentValue, 
	            onChange: this.handleChange, 
	            className: React.addons.classSet(classes), 
	            type: this._getInputType(), 
	            examples: this.examples(), 
	            shouldShowExamples: this.shouldShowExamples(), 
	            onFocus: this._handleFocus, 
	            onBlur: this._handleBlur});

	        if (answerBlurb) {
	            return React.createElement("span", {className: "perseus-input-with-answer-blurb"}, 
	                input, 
	                answerBlurb
	            );
	        } else {
	            return input;
	        }
	    },

	    handleChange: function(newValue) {
	        // TODO(johnsullivan): It would be better to support this lower down so
	        // that the input element actually gets marked with the disabled
	        // attribute. Because of how many layers of widgets are below us
	        // though, and because we're using CSS to disable click events (only
	        // unsupported on IE 10), I'm calling this sufficient for now.
	        if (!this.props.apiOptions.readOnly) {
	            this.props.onChange({ currentValue: newValue });
	        }
	    },

	    _getInputType: function() {
	        if (this.props.apiOptions.staticRender) {
	            return "tex";
	        } else {
	            return "text";
	        }
	    },

	    _handleFocus: function() {
	        this.props.onFocus([]);
	    },

	    _handleBlur: function() {
	        this.props.onBlur([]);
	    },

	    focus: function() {
	        this.refs.input.focus();
	        return true;
	    },

	    focusInputPath: function(inputPath) {
	        this.refs.input.focus();
	    },

	    blurInputPath: function(inputPath) {
	        this.refs.input.blur();
	    },

	    getInputPaths: function() {
	        // The widget itself is an input, so we return a single empty list to
	        // indicate this.
	        return [[]];
	    },

	    getGrammarTypeForPath: function(inputPath) {
	        return "number";
	    },

	    setInputValue: function(path, newValue, cb) {
	        this.props.onChange({
	            currentValue: newValue
	        }, cb);
	    },

	    getUserInput: function() {
	        return {currentValue: this.props.currentValue};
	    },

	    simpleValidate: function(rubric) {
	        return NumericInput.validate(this.getUserInput(), rubric);
	    },

	    shouldShowExamples: function() {
	        var noFormsAccepted = this.props.answerForms.length === 0;
	        var allFormsAccepted = this.props.answerForms.length >=
	                _.size(formExamples);
	        return this.props.enabledFeatures.toolTipFormats &&
	                !noFormsAccepted && !allFormsAccepted;
	    },

	    examples: function() {
	        // if the set of specified forms are empty, allow all forms
	        var forms = this.props.answerForms.length !== 0 ?
	                this.props.answerForms : _.keys(formExamples);
	        return _.map(forms, function(form)  {
	            return formExamples[form](this.props);
	        }.bind(this));
	    },

	    statics: {
	        displayMode: "inline-block"
	    }
	});

	_.extend(NumericInput, {
	    validate: function(state, rubric) {
	        var allAnswerForms = _.pluck(answerFormButtons, "value");

	        var createValidator = function(answer) 
	            {return Khan.answerTypes.number.createValidatorFunctional(
	                answer.value, {
	                    message: answer.message,
	                    simplify: answer.status === "correct" ?
	                        answer.simplify : "optional",
	                    inexact: true, // TODO(merlob) backfill / delete
	                    maxError: answer.maxError,
	                    forms: (answer.strict && answer.answerForms.length !== 0) ?
	                            answer.answerForms : allAnswerForms
	            });};

	        // We may have received TeX; try to parse it before grading.
	        // If `currentValue` is not TeX, this should be a no-op.
	        var currentValue = ParseTex(state.currentValue);

	        // Look through all correct answers for one that matches either
	        // precisely or approximately and return the appropriate message:
	        // - if precise, return the message that the answer came with
	        // - if it needs to be simplified, etc., show that message
	        var correctAnswers = _.where(rubric.answers, {status: "correct"});
	        var result = _.find(_.map(correctAnswers, function(answer)  {
	            // The coefficient is an attribute of the widget
	            var localValue = currentValue;
	            if (rubric.coefficient) {
	                if (localValue == "") {
	                    localValue = 1;
	                }
	                else if (localValue == "-") {
	                    localValue = -1;
	                }
	            }

	            var validate = createValidator(answer);
	            return validate(localValue);
	        }), function(match)  {return match.correct || match.empty;});

	        if (!result) { // Otherwise, if the guess is not correct
	            var otherAnswers = ([]).concat(
	                _.where(rubric.answers, {status: "ungraded"}),
	                _.where(rubric.answers, {status: "wrong"})
	            );

	            // Look through all other answers and if one matches either
	            // precisely or approximately return the answer's message
	            match = _.find(otherAnswers, function(answer)  {
	                 var validate = createValidator(answer);
	                 return validate(currentValue).correct;
	             });
	            result = {
	                empty: match ? match.status === "ungraded" : false,
	                correct: match ? match.status === "correct" : false,
	                message: match ? match.message : null,
	                guess: currentValue
	            };
	        }

	        // TODO(eater): Seems silly to translate result to this invalid/points
	        // thing and immediately translate it back in ItemRenderer.scoreInput()
	        if (result.empty) {
	            return {
	                type: "invalid",
	                message: result.message
	            };
	        } else {
	            return {
	                type: "points",
	                earned: result.correct ? 1 : 0,
	                total: 1,
	                message: result.message
	            };
	        }
	    }
	});

	var initAnswer = function(status)  {
	    return {
	        value: null,
	        status: status,
	        message: "",
	        simplify: "required",
	        answerForms: [],
	        strict: false,
	        maxError: null
	    };
	};

	var NumericInputEditor = React.createClass({displayName: 'NumericInputEditor',
	    mixins: [EditorJsonify, Changeable],

	    getDefaultProps: function() {
	        return {
	            answers: [initAnswer("correct")],
	            size: "normal",
	            coefficient: false
	        };
	    },

	    getInitialState: function() {
	        return {
	            lastStatus: "wrong",
	            showOptions: _.map(this.props.answers, function()  {return false;})
	        };
	    },

	    render: function() {
	        var lastStatus = this.state.lastStatus; // for a phantom last answer
	        var answers = this.props.answers;

	        var unsimplifiedAnswers = function(i)  {return React.createElement("div", {className: "perseus-widget-row"}, 
	            React.createElement("label", null, "Unsimplified answers are"), 
	            React.createElement(ButtonGroup, {value: answers[i]["simplify"], 
	                         allowEmpty: false, 
	                         buttons: [
	                            {value: "required", content: "ungraded"},
	                            {value: "optional", content: "accepted"},
	                            {value: "enforced", content: "wrong"}], 
	                         onChange: this.updateAnswer(i, "simplify")}), 
	            React.createElement(InfoTip, null, 
	                React.createElement("p", null, "Normally select \"ungraded\". This will give the" + ' ' +
	                "user a message saying the answer is correct but not" + ' ' +
	                "simplified. The user will then have to simplify it and" + ' ' +
	                "re-enter, but will not be penalized. (5th grade and after)"), 
	                React.createElement("p", null, "Select \"accepted\" only if the user is not" + ' ' +
	                "expected to know how to simplify fractions yet. (Anything" + ' ' +
	                "prior to 5th grade)"), 
	                React.createElement("p", null, "Select \"wrong\" ", React.createElement("em", null, "only"), " if we are" + ' ' +
	                "specifically assessing the ability to simplify.")
	            )
	        );}.bind(this);

	        var suggestedAnswerTypes = function(i)  {return React.createElement("div", null, 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                React.createElement("label", null, "Choose the suggested answer formats"), 
	                React.createElement(MultiButtonGroup, {buttons: answerFormButtons, 
	                    values: answers[i]["answerForms"], 
	                    onChange: this.updateAnswer(i, "answerForms")}), 
	                React.createElement(InfoTip, null, 
	                    React.createElement("p", null, "Formats will be autoselected for you based on the" + ' ' +
	                        "given answer; to show no suggested formats and" + ' ' +
	                        "accept all types, simply have a decimal/integer be" + ' ' +
	                        "the answer. Values with π will have format \"pi\"," + ' ' +
	                        "and values that are fractions will have some subset" + ' ' +
	                        "(mixed will be \"mixed\" and \"proper\"; improper/proper" + ' ' +
	                        "will both be \"improper\" and \"proper\"). If you would" + ' ' +
	                        "like to specify that it is only a proper fraction" + ' ' +
	                        "(or only a mixed/improper fraction), deselect the" + ' ' +
	                        "other format. Except for specific cases, you should" + ' ' +
	                        "not need to change the autoselected formats."), 
	                    React.createElement("p", null, "To restrict the answer to ", React.createElement("em", null, "only"), " an improper" + ' ' +
	                        "fraction (i.e. 7/4), select the" + ' ' +
	                        "improper fraction and toggle \"strict\" to true." + ' ' +
	                        "This ", React.createElement("b", null, "will not"), " accept 1.75 as an answer. "), 
	                    React.createElement("p", null, "Unless you are testing that specific skill, please" + ' ' +
	                        "do not restrict the answer format.")
	                )
	            ), 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                React.createElement(PropCheckBox, {label: "Strictly match only these formats", 
	                    strict: answers[i]["strict"], 
	                    onChange: this.updateAnswer.bind(this, i)})
	            )
	        );}.bind(this);

	        var maxError = function(i)  {return React.createElement("div", {className: "perseus-widget-row"}, 
	            React.createElement(NumberInput, {label: "Max error", 
	                className: "max-error", 
	                value: answers[i]["maxError"], 
	                onChange: this.updateAnswer(i, "maxError"), 
	                placeholder: "0"})
	        );}.bind(this);

	        var inputSize = React.createElement("div", {className: "perseus-widget-row"}, 
	                React.createElement("label", null, "Width:", ' ', " "), 
	                React.createElement(ButtonGroup, {value: this.props.size, allowEmpty: false, 
	                    buttons: [
	                        {value: "normal", content: "Normal (80px)"},
	                        {value: "small", content: "Small (40px)"}], 
	                    onChange: this.change("size")}), 
	                React.createElement(InfoTip, null, 
	                    React.createElement("p", null, "Use size \"Normal\" for all text boxes, unless there are" + ' ' +
	                    "multiple text boxes in one line and the answer area is too" + ' ' +
	                    "narrow to fit them.")
	                )
	            );

	        var coefficientCheck = React.createElement("div", null, 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                React.createElement(PropCheckBox, {label: "Coefficient", 
	                    coefficient: this.props.coefficient, 
	                    onChange: this.props.onChange}), 
	                React.createElement(InfoTip, null, 
	                    React.createElement("p", null, "A coefficient style number allows the student to use - for -1 and an empty string to mean 1.")
	                )
	            )
	        );

	        var addAnswerButton = React.createElement("div", null, 
	            React.createElement("a", {
	                href: "javascript:void(0)", 
	                className: "simple-button orange", 
	                onClick: function()  {return this.addAnswer();}.bind(this), 
	                onKeyDown: function(e)  {return this.onSpace(e, this.addAnswer);}.bind(this)}, 
	              React.createElement("span", null, "Add new answer")
	            )
	        );

	        var instructions = {
	            "wrong":    "(address the mistake/misconception)",
	            "ungraded": "(explain in detail to avoid confusion)",
	            "correct":  "(reinforce the user's understanding)"
	        };

	        var generateInputAnswerEditors = function()  {return answers.map(function(answer, i)  {
	            var editor = React.createElement(Editor, {
	                content: answer.message || "", 
	                placeholder: "Why is this answer " + answer.status + "?\t" +
	                    instructions[answer.status], 
	                widgetEnabled: false, 
	                onChange: function(newProps)  {
	                    if ("content" in newProps) {
	                        this.updateAnswer(i, {message: newProps.content});
	                    }
	                }.bind(this)});
	            return React.createElement("div", {className: "perseus-widget-row", key: i}, 
	                React.createElement("div", {className: "input-answer-editor-value-container" +
	                    (answer.maxError ? " with-max-error" : "")}, 
	                    React.createElement(NumberInput, {value: answer.value, 
	                        className: "numeric-input-value", 
	                        placeholder: "answer", 
	                        format: _.last(answer.answerForms), 
	                        onFormatChange: function(newValue, format)  {
	                            var forms;
	                            if (format === "pi") {
	                                forms = ["pi"];
	                            } else if (format === "mixed") {
	                                forms = ["proper", "mixed"];
	                            } else if (format === "proper" ||
	                                       format === "improper") {
	                                forms = ["proper", "improper"];
	                            }
	                            this.updateAnswer(i, {
	                                value: firstNumericalParse(newValue),
	                                answerForms: forms
	                            });
	                        }.bind(this), 
	                        onChange: function(newValue)  {
	                            this.updateAnswer(i, {
	                                value: firstNumericalParse(newValue)});
	                        }.bind(this)}), 
	                    answer.strict && React.createElement("div", {className: "is-strict-indicator", 
	                        title: "strictly equivalent to"}, "≡"), 
	                    answer.simplify !== "required" &&
	                     answer.status === "correct" &&
	                      React.createElement("div", {className: "simplify-indicator " + answer.simplify, 
	                        title: "accepts unsimplified answers"}, "‰"), 
	                    answer.maxError ? React.createElement("div", {className: "max-error-container"}, 
	                        React.createElement("div", {className: "max-error-plusmn"}, "±"), 
	                        React.createElement(NumberInput, {placeholder: 0, 
	                            value: answers[i]["maxError"], 
	                            format: _.last(answer.answerForms), 
	                            onChange: this.updateAnswer(i, "maxError")})
	                    ) : null, 
	                    React.createElement("div", {className: "value-divider"}), 
	                    React.createElement("a", {href: "javascript:void(0)", 
	                        className: "answer-status " + answer.status, 
	                        onClick: function()  {return this.onStatusChange(i);}.bind(this), 
	                        onKeyDown: function(e)  {return this.onSpace(e, this.onStatusChange, i);}.bind(this)}, 
	                        answer.status
	                    ), 
	                    React.createElement("a", {
	                        href: "javascript:void(0)", 
	                        className: "answer-trash", 
	                        onClick: function()  {return this.onTrashAnswer(i);}.bind(this), 
	                        onKeyDown: function(e)  {return this.onSpace(e, this.onTrashAnswer, i);}.bind(this)}, 
	                      React.createElement("span", {className: "icon-trash"})
	                    ), 
	                    React.createElement("a", {href: "javascript:void(0)", 
	                        className: "options-toggle", 
	                        onClick: function()  {return this.onToggleOptions(i);}.bind(this), 
	                        onKeyDown: function(e)  {return this.onSpace(e, this.onToggleOptions, i);}.bind(this)}, 
	                      React.createElement("i", {className: "icon-gear"})
	                    )
	                ), 
	                React.createElement("div", {className: "input-answer-editor-message"}, editor), 
	                this.state.showOptions[i] &&
	                    React.createElement("div", {className: "options-container"}, 
	                        maxError(i), 
	                        answer.status === "correct" && unsimplifiedAnswers(i), 
	                        suggestedAnswerTypes(i)
	                    )
	            );
	        }.bind(this));}.bind(this);

	        return React.createElement("div", {className: "perseus-input-number-editor"}, 
	            React.createElement("div", {className: "ui-title"}, "User input"), 
	            React.createElement("div", {className: "msg-title"}, "Message shown to user on attempt"), 
	            generateInputAnswerEditors(), 
	            addAnswerButton, 
	            inputSize, 
	            coefficientCheck
	        );

	    },

	    onToggleOptions: function(choiceIndex) {
	        var showOptions = this.state.showOptions.slice();
	        showOptions[choiceIndex] = !showOptions[choiceIndex];
	        this.setState({showOptions: showOptions});
	    },

	    onTrashAnswer: function(choiceIndex) {
	        if (choiceIndex >= 0 && choiceIndex < this.props.answers.length) {
	            var answers = this.props.answers.slice(0);
	            answers.splice(choiceIndex, 1);
	            this.props.onChange({answers: answers});
	        }
	    },

	    onSpace: function(e, callback) {
	        if (e.key === " ") {
	            e.preventDefault(); // prevent page shifting
	            var args = _.toArray(arguments).slice(2);
	            callback.apply(this, args);
	        }
	    },

	    onStatusChange: function(choiceIndex) {
	        var statuses = ["wrong", "ungraded", "correct"];
	        var answers = this.props.answers;
	        var i = _.indexOf(statuses, answers[choiceIndex].status);
	        var newStatus = statuses[(i + 1) % statuses.length];

	        this.updateAnswer(choiceIndex, {
	            status: newStatus,
	            simplify: newStatus === "correct" ? "required" : "accepted"
	        });
	    },

	    updateAnswer: function(choiceIndex, update) {
	        if (!_.isObject(update)) {
	            return _.partial(function(choiceIndex, key, value)  {
	                var update = {};
	                update[key] = value;
	                this.updateAnswer(choiceIndex, update);
	            }.bind(this), choiceIndex, update);
	        }

	        var answers = _.clone(this.props.answers);

	        // Don't bother to make a new answer box unless we are editing the last one
	        // TODO(michelle): This might not be necessary anymore.
	        if (choiceIndex == answers.length) {
	            var lastAnswer = initAnswer(this.state.lastStatus);
	            var answers = answers.concat(lastAnswer);
	        }

	        answers[choiceIndex] = _.extend({}, answers[choiceIndex], update);
	        this.props.onChange({answers: answers});
	    },

	    addAnswer: function() {
	        var lastAnswer = initAnswer(this.state.lastStatus);
	        var answers = this.props.answers.concat(lastAnswer);
	        this.props.onChange({answers: answers});
	    },

	    getSaveWarnings: function() {
	        // Filter out all the empty answers
	        if (_.contains(_.pluck(this.props.answers, "value"), "")) {
	            return ["Warning: one or more answers is empty."];
	        }
	        return [];
	    },
	});

	var unionAnswerForms = function(answerFormsList) {
	    var set = {};
	    _.each(answerFormsList, function(answerForms)  {
	        _.each(answerForms, function(form)  {
	            set[form] = true;
	        });
	    });
	    // Make sure to keep the order of forms in formExamples
	    return _.filter(_.keys(formExamples), function(form)  {return set[form] === true;});
	};

	var propsTransform = function(editorProps) {
	    var rendererProps = _.extend(
	        _.omit(editorProps, "answers"),
	        {
	            answerForms: unionAnswerForms(
	                _.pluck(editorProps.answers, "answerForms")
	            )
	        }
	    );
	    return rendererProps;
	};

	module.exports = {
	    name: "numeric-input",
	    displayName: "Number text box",
	    widget: NumericInput,
	    editor: NumericInputEditor,
	    transform: propsTransform
	};


/***/ },
/* 33 */
/***/ function(module, exports, __webpack_require__) {

	var InfoTip = __webpack_require__(69);
	var React = __webpack_require__(61);
	var _ = __webpack_require__(60);

	var Renderer = __webpack_require__(10);
	var TextListEditor = __webpack_require__(70);
	var Util = __webpack_require__(3);

	var ApiClassNames = __webpack_require__(12).ClassNames;

	var PlaceholderCard = React.createClass({displayName: 'PlaceholderCard',
	    propTypes: {
	        width: React.PropTypes.number.isRequired,
	        height: React.PropTypes.number.isRequired
	    },

	    render: function() {
	        return React.createElement("div", {
	                className: "card-wrap " + ApiClassNames.INTERACTIVE, 
	                style: {width: this.props.width}}, 
	            React.createElement("div", {
	                className: "card placeholder", 
	                style: {height: this.props.height}})
	        );
	    }
	});

	var DragHintCard = React.createClass({displayName: 'DragHintCard',
	    render: function() {
	        return React.createElement("div", {className: "card-wrap " + ApiClassNames.INTERACTIVE}, 
	            React.createElement("div", {className: "card drag-hint"})
	        );
	    }
	});

	var PropTypes = {
	    position: React.PropTypes.shape({
	        left: React.PropTypes.number,
	        top: React.PropTypes.number
	    })
	};

	var Card = React.createClass({displayName: 'Card',
	    propTypes: {
	        floating: React.PropTypes.bool.isRequired,
	        animating: React.PropTypes.bool,
	        width: React.PropTypes.number,
	        stack: React.PropTypes.bool,

	        onMouseDown: React.PropTypes.func,
	        onMouseMove: React.PropTypes.func,
	        onMouseUp: React.PropTypes.func,

	        // Used only for floating/animating cards
	        startMouse: PropTypes.position,
	        startOffset: PropTypes.position,
	        animateTo: PropTypes.position,
	        onAnimationEnd: React.PropTypes.func
	    },

	    getDefaultProps: function() {
	        return {
	            stack: false,
	            animating: false
	        };
	    },

	    render: function() {
	        var style = {};

	        if (this.props.floating) {
	            style = {
	                position: "absolute",
	                left: this.props.startOffset.left,
	                top: this.props.startOffset.top,
	            };
	        }

	        if (this.props.width) {
	            style.width = this.props.width;
	        }

	        var className = ["card"];
	        if (this.props.stack) {
	            className.push("stack");
	        }
	        if (this.props.floating && !this.props.animating) {
	            className.push("dragging");
	            style.left += this.props.mouse.left - this.props.startMouse.left;
	            style.top += this.props.mouse.top - this.props.startMouse.top;
	        }

	        // Pull out the content to get rendered
	        var rendererProps = _.pick(this.props, "content");

	        var onMouseDown = (this.props.animating) ? $.noop : this.onMouseDown;

	        return React.createElement("div", {className: "card-wrap " + ApiClassNames.INTERACTIVE, 
	                    style: style, 
	                    onMouseDown: onMouseDown, 
	                    onTouchStart: onMouseDown, 
	                    onTouchMove: this.onMouseMove, 
	                    onTouchEnd: this.onMouseUp, 
	                    onTouchCancel: this.onMouseUp}, 
	                React.createElement("div", {className: className.join(" ")}, 
	                    React.createElement(Renderer, React.__spread({},  rendererProps))
	                )
	            );
	    },

	    shouldComponentUpdate: function(nextProps, nextState) {
	        // Cards in the bank or drag list don't usually change -- they only
	        // reorder themselves -- so we want to skip the update to things a
	        // little faster. We also need to re-render if the content changes,
	        // which happens only in the editor. (We do want to update the floating
	        // card on mouse move to update its position.)
	        return this.props.floating || nextProps.floating ||
	            this.props.content !== nextProps.content ||
	            // TODO(alpert): Remove ref here after fixing facebook/react#1392.
	            this.props.fakeRef !== nextProps.fakeRef;
	    },

	    componentDidMount: function() {
	        this.mouseMoveUpBound = false;
	    },

	    componentDidUpdate: function(prevProps, prevState) {
	        if (this.props.animating && !prevProps.animating) {
	            // If we just were changed into animating, start the animation.
	            // We pick the animation speed based on the distance that the card
	            // needs to travel. (Why sqrt? Just because it looks nice -- with a
	            // linear scale, far things take too long to come back.)
	            var ms = 15 * Math.sqrt(
	                Math.sqrt(
	                    Math.pow(this.props.animateTo.left -
	                             this.props.startOffset.left, 2) +
	                    Math.pow(this.props.animateTo.top -
	                             this.props.startOffset.top, 2)
	                )
	            );
	            $(this.getDOMNode()).animate(
	                this.props.animateTo, Math.max(ms, 1),
	                this.props.onAnimationEnd
	            );
	        }
	    },

	    componentWillUnmount: function() {
	        // Event handlers should be unbound before component unmounting, but
	        // just in case...
	        if (this.mouseMoveUpBound) {
	            console.warn("Removing an element with bound event handlers.");

	            this.unbindMouseMoveUp();
	            Util.resetTouchHandlers();
	        }
	    },

	    bindMouseMoveUp: function() {
	        this.mouseMoveUpBound = true;
	        $(document).on("mousemove", this.onMouseMove);
	        $(document).on("mouseup", this.onMouseUp);
	    },

	    unbindMouseMoveUp: function() {
	        this.mouseMoveUpBound = false;
	        $(document).off("mousemove", this.onMouseMove);
	        $(document).off("mouseup", this.onMouseUp);
	    },

	    onMouseDown: function(event) {
	        event.preventDefault();
	        var loc = Util.extractPointerLocation(event);
	        if (loc) {
	            this.bindMouseMoveUp();
	            this.props.onMouseDown && this.props.onMouseDown(loc, this);
	        }
	    },

	    onMouseMove: function(event) {
	        event.preventDefault();
	        var loc = Util.extractPointerLocation(event);
	        if (loc) {
	            this.props.onMouseMove && this.props.onMouseMove(loc);
	        }
	    },

	    onMouseUp: function(event) {
	        event.preventDefault();
	        var loc = Util.extractPointerLocation(event);
	        if (loc) {
	            this.unbindMouseMoveUp();
	            this.props.onMouseUp && this.props.onMouseUp(loc);
	        }
	    }
	});

	var NORMAL = "normal",
	    AUTO = "auto",
	    HORIZONTAL = "horizontal",
	    VERTICAL = "vertical";

	var Orderer = React.createClass({displayName: 'Orderer',
	    propTypes: {
	        current: React.PropTypes.array,
	        options: React.PropTypes.array,
	        correctOptions: React.PropTypes.array,
	        height: React.PropTypes.oneOf([NORMAL, AUTO]),
	        layout: React.PropTypes.oneOf([HORIZONTAL, VERTICAL])
	    },

	    getDefaultProps: function() {
	        return {
	            current: [],
	            options: [],
	            correctOptions: [],
	            height: NORMAL,
	            layout: HORIZONTAL
	        };
	    },

	    getInitialState: function() {
	        return {
	            current: [],
	            dragging: false,
	            placeholderIndex: null,
	        };
	    },

	    componentWillReceiveProps: function(nextProps) {
	        if (!_.isEqual(this.props.current, nextProps.current)) {
	            this.setState({current: nextProps.current});
	        }
	    },

	    render: function() {
	        // This is the card we are currently dragging
	        var dragging = this.state.dragging &&
	            React.createElement(Card, {ref: "dragging", 
	                       floating: true, 
	                       content: this.state.dragContent, 
	                       startOffset: this.state.offsetPos, 
	                       startMouse: this.state.grabPos, 
	                       mouse: this.state.mousePos, 
	                       width: this.state.dragWidth, 
	                       onMouseUp: this.onRelease, 
	                       onMouseMove: this.onMouseMove, 
	                       key: this.state.dragKey || "draggingCard"}
	                       );

	        // This is the card that is currently animating
	        var animating = this.state.animating &&
	            React.createElement(Card, {floating: true, 
	                       animating: true, 
	                       content: this.state.dragContent, 
	                       startOffset: this.state.offsetPos, 
	                       width: this.state.dragWidth, 
	                       animateTo: this.state.animateTo, 
	                       onAnimationEnd: this.state.onAnimationEnd, 
	                       key: this.state.dragKey || "draggingCard"}
	                       );

	        // This is the list of draggable, rearrangable cards
	        var sortableCards = _.map(this.state.current, function(opt, i) {
	            return React.createElement(Card, {
	                ref: "sortable" + i, 
	                fakeRef: "sortable" + i, 
	                floating: false, 
	                content: opt.content, 
	                width: opt.width, 
	                key: opt.key, 
	                onMouseDown: (this.state.animating) ?
	                    $.noop :
	                    this.onClick.bind(null, "current", i)});
	        }, this);

	        if (this.state.placeholderIndex != null) {
	            var placeholder = React.createElement(PlaceholderCard, {
	                ref: "placeholder", 
	                width: this.state.dragWidth, 
	                height: this.state.dragHeight, 
	                key: "placeholder"});
	            sortableCards.splice(this.state.placeholderIndex, 0, placeholder);
	        }

	        var anySortableCards = sortableCards.length > 0;
	        sortableCards.push(dragging, animating);

	        // If there are no cards in the list, then add a "hint" card
	        var sortable = React.createElement("div", {className: "ui-helper-clearfix draggable-box"}, 
	            !anySortableCards && React.createElement(DragHintCard, null), 
	            React.createElement("div", {ref: "dragList"}, sortableCards)
	        );

	        // This is the bank of stacks of cards
	        var bank = React.createElement("div", {ref: "bank", className: "bank ui-helper-clearfix"}, 
	            _.map(this.props.options, function(opt, i)  {
	                return React.createElement(Card, {
	                    ref: "bank" + i, 
	                    floating: false, 
	                    content: opt.content, 
	                    stack: true, 
	                    key: i, 
	                    onMouseDown: (this.state.animating) ?
	                        $.noop :
	                        this.onClick.bind(null, "bank", i), 
	                    onMouseMove: this.onMouseMove, 
	                    onMouseUp: this.onRelease});
	            }.bind(this), this)
	        );

	        return React.createElement("div", {className: "draggy-boxy-thing orderer " +
	                        "height-" + this.props.height + " " +
	                        "layout-" + this.props.layout + " " +
	                        "above-scratchpad blank-background " +
	                        "ui-helper-clearfix " + ApiClassNames.INTERACTIVE, 
	                    ref: "orderer"}, 
	                   bank, 
	                   sortable
	               );
	    },

	    onClick: function(type, index, loc, draggable) {
	        var $draggable = $(draggable.getDOMNode());
	        var list = this.state.current.slice();

	        var opt;
	        var placeholderIndex = null;

	        if (type === "current") {
	            // If this is coming from the original list, remove the original
	            // card from the list
	            list.splice(index, 1);
	            opt = this.state.current[index];
	            placeholderIndex = index;
	        } else if (type === "bank") {
	            opt = this.props.options[index];
	        }

	        this.setState({
	            current: list,
	            dragging: true,
	            placeholderIndex: placeholderIndex,
	            dragKey: opt.key,
	            dragContent: opt.content,
	            dragWidth: $draggable.width(),
	            dragHeight: $draggable.height(),
	            grabPos: loc,
	            mousePos: loc,
	            offsetPos: $draggable.position()
	        });
	    },

	    onRelease: function(loc) {
	        var draggable = this.refs.dragging;
	        if (draggable == null) {
	            return;
	        }
	        var inCardBank = this.isCardInBank(draggable);
	        var index = this.state.placeholderIndex;

	        // Here, we build a callback function for the card to call when it is
	        // done animating
	        var onAnimationEnd = function()  {
	            var list = this.state.current.slice();

	            if (!inCardBank) {
	                // Insert the new card into the position
	                var newCard = {
	                    content: this.state.dragContent,
	                    key: _.uniqueId("perseus_draggable_card_"),
	                    width: this.state.dragWidth
	                };

	                list.splice(index, 0, newCard);
	            }

	            this.props.onChange({
	                current: list
	            });
	            this.setState({
	                current: list,
	                dragging: false,
	                placeholderIndex: null,
	                animating: false
	            });
	        }.bind(this);

	        // Find the position of the card we should animate to
	        // TODO(alpert): Update mouse position once more before animating?
	        var offset = $(draggable.getDOMNode()).position();
	        var finalOffset = null;
	        if (inCardBank) {
	            // If we're in the card bank, go through the options to find the
	            // one with the same content
	            _.each(this.props.options, function(opt, i) {
	                if (opt.content === this.state.dragContent) {
	                    var card = this.refs["bank" + i].getDOMNode();
	                    finalOffset = $(card).position();
	                }
	            }, this);
	        } else {
	            // Otherwise, go to the position that the placeholder is at
	            finalOffset = $(this.refs.placeholder.getDOMNode()).position();
	        }

	        if (finalOffset == null) {
	            // If we didn't find a card to go to, simply make the changes we
	            // would have made at the end. (should only happen if we are
	            // messing around with card contents, and not on the real site)
	            onAnimationEnd();
	        } else {
	            this.setState({
	                offsetPos: offset,
	                animateTo: finalOffset,
	                onAnimationEnd: onAnimationEnd,
	                animating: true,
	                dragging: false
	            });
	        }
	    },

	    onMouseMove: function(loc) {
	        var draggable = this.refs.dragging;
	        if (draggable == null) {
	            return;
	        }

	        var index;
	        if (this.isCardInBank(draggable)) {
	            index = null;
	        } else {
	            index = this.findCorrectIndex(draggable, this.state.current);
	        }

	        this.setState({
	            mousePos: loc,
	            placeholderIndex: index
	        });
	    },

	    findCorrectIndex: function(draggable, list) {
	        // Find the correct index for a card given the current cards.
	        var isHorizontal = this.props.layout === HORIZONTAL,
	            $dragList = $(this.refs.dragList.getDOMNode()),
	            leftEdge = $dragList.offset().left,
	            topEdge = $dragList.offset().top,
	            midWidth = $(draggable.getDOMNode()).offset().left - leftEdge,
	            midHeight = $(draggable.getDOMNode()).offset().top - topEdge,
	            index = 0,
	            sumWidth = 0,
	            sumHeight = 0;

	        if (isHorizontal) {
	            _.each(list, function(opt, i) {
	                var card = this.refs["sortable" + i].getDOMNode();
	                var outerWidth = $(card).outerWidth(true);
	                if (midWidth > sumWidth + outerWidth / 2) {
	                    index += 1;
	                }
	                sumWidth += outerWidth;
	            }, this);
	        } else {
	            _.each(list, function(opt, i) {
	                var card = this.refs["sortable" + i].getDOMNode();
	                var outerHeight = $(card).outerHeight(true);
	                if (midHeight > sumHeight + outerHeight / 2) {
	                    index += 1;
	                }
	                sumHeight += outerHeight;
	            }, this);
	        }

	        return index;
	    },

	    isCardInBank: function(draggable) {
	        if (draggable == null) {
	            return false;
	        }

	        var isHorizontal = this.props.layout === HORIZONTAL,
	            $draggable = $(draggable.getDOMNode()),
	            $bank = $(this.refs.bank.getDOMNode()),
	            draggableOffset = $draggable.offset(),
	            bankOffset = $bank.offset(),
	            draggableHeight = $draggable.outerHeight(true),
	            bankHeight = $bank.outerHeight(true),
	            bankWidth = $bank.outerWidth(true),
	            dragList = this.refs.dragList.getDOMNode(),
	            dragListWidth = $(dragList).width(),
	            draggableWidth = $draggable.outerWidth(true);

	        if (isHorizontal) {
	            return (draggableOffset.top + draggableHeight / 2 <
	                    bankOffset.top + bankHeight);
	        } else {
	            return (draggableOffset.left + draggableWidth / 2 <
	                    bankOffset.left + bankWidth);
	        }
	    },

	    getUserInput: function() {
	        return {current: _.map(this.props.current, function(v) {
	            return v.content;
	        })};
	    },

	    simpleValidate: function(rubric) {
	        return Orderer.validate(this.getUserInput(), rubric);
	    },

	    statics: {
	        displayMode: "block"
	    }
	});

	_.extend(Orderer, {
	    validate: function(state, rubric) {
	        if (state.current.length === 0) {
	            return {
	                type: "invalid",
	                message: null
	            };
	        }

	        var correct = _.isEqual(
	            state.current,
	            _.pluck(rubric.correctOptions, 'content')
	        );

	        return {
	            type: "points",
	            earned: correct ? 1 : 0,
	            total: 1,
	            message: null
	        };
	    }
	});


	var OrdererEditor = React.createClass({displayName: 'OrdererEditor',
	    propTypes: {
	        correctOptions: React.PropTypes.array,
	        otherOptions: React.PropTypes.array,
	        height: React.PropTypes.oneOf([NORMAL, AUTO]),
	        layout: React.PropTypes.oneOf([HORIZONTAL, VERTICAL]),
	        onChange: React.PropTypes.func.isRequired
	    },

	    getDefaultProps: function() {
	        return {
	            correctOptions: [
	                {content: "$x$"}
	            ],
	            otherOptions: [
	                {content: "$y$"}
	            ],
	            height: NORMAL,
	            layout: HORIZONTAL
	        };
	    },

	    render: function() {
	        var editor = this;

	        return React.createElement("div", {className: "perseus-widget-orderer"}, 
	            React.createElement("div", null, 
	                ' ', "Correct answer:", ' ', 
	                React.createElement(InfoTip, null, React.createElement("p", null, 
	                    "Place the cards in the correct order. The same card can be" + ' ' +
	                    "used more than once in the answer but will only be" + ' ' +
	                    "displayed once at the top of a stack of identical cards."
	                ))
	            ), 
	            React.createElement(TextListEditor, {
	                options: _.pluck(this.props.correctOptions, "content"), 
	                onChange: this.onOptionsChange.bind(this, "correctOptions"), 
	                layout: this.props.layout}), 

	            React.createElement("div", null, 
	                ' ', "Other cards:", ' ', 
	                React.createElement(InfoTip, null, 
	                    React.createElement("p", null, "Create cards that are not part of the answer.")
	                )
	            ), 
	            React.createElement(TextListEditor, {
	                options: _.pluck(this.props.otherOptions, "content"), 
	                onChange: this.onOptionsChange.bind(this, "otherOptions"), 
	                layout: this.props.layout}), 

	            React.createElement("div", null, 
	                React.createElement("label", null, 
	                    ' ', "Layout:", ' ', 
	                    React.createElement("select", {value: this.props.layout, 
	                            onChange: this.onLayoutChange}, 
	                        React.createElement("option", {value: HORIZONTAL}, "Horizontal"), 
	                        React.createElement("option", {value: VERTICAL}, "Vertical")
	                    )
	                ), 
	                React.createElement(InfoTip, null, 
	                    React.createElement("p", null, "Use the horizontal layout for short text and small" + ' ' +
	                    "images. The vertical layout is best for longer text (e.g." + ' ' +
	                    "proofs).")
	                )
	            ), 
	            React.createElement("div", null, 
	                React.createElement("label", null, 
	                    ' ', "Height:", ' ', 
	                    React.createElement("select", {value: this.props.height, 
	                            onChange: this.onHeightChange}, 
	                        React.createElement("option", {value: NORMAL}, "Normal"), 
	                        React.createElement("option", {value: AUTO}, "Automatic")
	                    )
	                ), 
	                React.createElement(InfoTip, null, 
	                    React.createElement("p", null, "Use \"Normal\" for text, \"Automatic\" for images.")
	                )
	            )
	        );
	    },

	    onOptionsChange: function(whichOptions, options, cb) {
	        var props = {};
	        props[whichOptions] = _.map(options, function(option) {
	            return {content: option};
	        });
	        this.props.onChange(props, cb);
	    },

	    onLayoutChange: function(e) {
	        this.props.onChange({layout: e.target.value});
	    },

	    onHeightChange: function(e) {
	        this.props.onChange({height: e.target.value});
	    },

	    serialize: function() {
	        // We combine the correct answer and the other cards by merging them,
	        // removing duplicates and empty cards, and sorting them into
	        // categories based on their content
	        var options =
	            _.chain(_.pluck(this.props.correctOptions, 'content'))
	             .union(_.pluck(this.props.otherOptions, 'content'))
	             .uniq()
	             .reject(function(content) { return content === ""; })
	             .sort()
	             .sortBy(function(content) {
	                 if (/\d/.test(content)) {
	                     return 0;
	                 } else if (/^\$?[a-zA-Z]+\$?$/.test(content)) {
	                     return 2;
	                 } else {
	                     return 1;
	                 }
	             })
	             .map(function(content) {
	                 return { content: content };
	             })
	             .value();

	        return {
	            options: options,
	            correctOptions: this.props.correctOptions,
	            otherOptions: this.props.otherOptions,
	            height: this.props.height,
	            layout: this.props.layout
	        };
	    }
	});

	module.exports = {
	    name: "orderer",
	    displayName: "Orderer",
	    widget: Orderer,
	    editor: OrdererEditor
	};


/***/ },
/* 34 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);
	var _ = __webpack_require__(60);

	var Changeable   = __webpack_require__(66);
	var EditorJsonify = __webpack_require__(67);
	var WidgetJsonifyDeprecated = __webpack_require__(68);

	var Editor = __webpack_require__(7);
	var Renderer = __webpack_require__(10);
	var InfoTip = __webpack_require__(69);
	var PropCheckBox  = __webpack_require__(58);
	var PassageMarkdown = __webpack_require__(101);

	var Passage = React.createClass({displayName: 'Passage',
	    mixins: [WidgetJsonifyDeprecated, Changeable],

	    propTypes: {
	        passageTitle: React.PropTypes.string,
	        passageText: React.PropTypes.string,
	        footnotes: React.PropTypes.string,
	        showLineNumbers: React.PropTypes.bool
	    },

	    getDefaultProps: function() {
	        return {
	            passageTitle: "",
	            passageText: "",
	            footnotes: "",
	            showLineNumbers: true
	        };
	    },

	    getInitialState: function() {
	        return {
	            nLines: null,
	            startLineNumbersAfter: 0,
	        };
	    },

	    shouldComponentUpdate: function(nextProps, nextState) {
	        return !_.isEqual(this.props, nextProps) ||
	            !_.isEqual(this.state, nextState);
	    },

	    render: function() {
	        var lineNumbers;
	        var nLines = this.state.nLines;
	        if (this.props.showLineNumbers && nLines) {
	            // lineN is the line number in the current passage;
	            // the displayed line number is
	            // lineN + this.state.startLineNumbersAfter, where
	            // startLineNumbersAfter is the sum of all line numbers
	            // in earlier passages.
	            lineNumbers = _.range(1, nLines + 1).map(function(lineN)  {
	                if (lineN === 4 && nLines > 4) {
	                    return React.createElement("span", {className: "line-marker"}, "Line");
	                } else if (lineN % 5 === 0) {
	                    return lineN + this.state.startLineNumbersAfter;
	                } else {
	                    return "\n";
	                }
	            }.bind(this));
	        }
	        return React.createElement("div", {className: "perseus-widget-passage"}, 
	            React.createElement("div", {className: "passage-title"}, 
	                React.createElement(Renderer, {content: this.props.passageTitle})
	            ), 
	            lineNumbers && React.createElement("div", {className: "line-numbers"}, 
	                lineNumbers
	            ), 
	            React.createElement("div", {className: "passage-text"}, 
	                this._renderContent()
	            ), 
	            React.createElement("div", {className: "footnotes"}, 
	                this._renderFootnotes()
	            )
	        );
	    },

	    componentDidMount: function() {
	        this._updateState();
	    },

	    componentDidUpdate: function() {
	        this._updateState();
	    },

	    _updateState: function() {
	        this.setState({
	            nLines: this._measureLines(),
	            startLineNumbersAfter: this._getInitialLineNumber(),
	        });
	    },

	    _measureLines: function() {
	        var $renderer = $(this.refs.content.getDOMNode());
	        var contentsHeight = $renderer.height();
	        var lineHeight = parseInt($renderer.css("line-height"));
	        var nLines = Math.round(contentsHeight / lineHeight);
	        return nLines;
	    },

	    _getInitialLineNumber: function() {
	        var isPassageBeforeThisPassage = true;
	        var passagesBeforeUs = this.props.interWidgets(function(id, widgetInfo)  {
	            if (widgetInfo.type !== "passage") {
	                return false;
	            }
	            if (id === this.props.widgetId) {
	                isPassageBeforeThisPassage = false;
	            }
	            return isPassageBeforeThisPassage;
	        }.bind(this));

	        return passagesBeforeUs.map(function(passageWidget)  {
	            return passageWidget.getLineCount();
	        }).reduce(function(a, b)  {return a + b;}, 0);
	    },

	    getLineCount: function() {
	        if (this.state.nLines != null) {
	            return this.state.nLines;
	        } else {
	            return this._measureLines();
	        }
	    },

	    _renderContent: function() {
	        var rawContent = this.props.passageText;
	        var parsed = PassageMarkdown.parse(rawContent);
	        return React.createElement("div", {ref: "content"}, 
	            PassageMarkdown.output(parsed)
	        );
	    },

	    _renderFootnotes: function() {
	        var rawContent = this.props.footnotes;
	        var parsed = PassageMarkdown.parse(rawContent);
	        return PassageMarkdown.output(parsed);
	    },

	    _getStartRefLineNumber: function(referenceNumber) {
	        var refRef = PassageMarkdown.START_REF_PREFIX + referenceNumber;
	        var ref = this.refs[refRef];
	        if (!ref) {
	            return null;
	        }

	        var $ref = $(ref.getDOMNode());
	        // We really care about the first text after the ref, not the
	        // ref element itself:
	        var $refText = $ref.next();
	        if ($refText.length === 0) {
	            // But if there are no elements after the ref, just
	            // use the ref itself.
	            $refText = $ref;
	        }
	        var vPos = $refText.offset().top;

	        return this.state.startLineNumbersAfter + 1 +
	            this._convertPosToLineNumber(vPos);
	    },

	    _getEndRefLineNumber: function(referenceNumber) {
	        var refRef = PassageMarkdown.END_REF_PREFIX + referenceNumber;
	        var ref = this.refs[refRef];
	        if (!ref) {
	            return null;
	        }

	        var $ref = $(ref.getDOMNode());
	        // We really care about the last text before the ref, not the
	        // ref element itself:
	        var $refText = $ref.prev();
	        if ($refText.length === 0) {
	            // But if there are no elements after the ref, just
	            // use the ref itself.
	            $refText = $ref;
	        }
	        var height = $refText.height();
	        var vPos = $refText.offset().top;

	        var line = this._convertPosToLineNumber(vPos + height);
	        if (height === 0) {
	            // If the element before the end ref span was the start
	            // ref span, it might have 0 height. This is obviously not
	            // the intended use case, but we should handle it gracefully.
	            // If this is the case, then the "bottom" of our element is
	            // actually the top of the line we're on, so we need to add
	            // one to the line number.
	            line += 1;
	        }

	        return this.state.startLineNumbersAfter + line;
	    },

	    _convertPosToLineNumber: function(absoluteVPos) {
	        var $content= $(this.refs.content.getDOMNode());
	        var relativeVPos = absoluteVPos - $content.offset().top;
	        var lineHeight = parseInt($content.css("line-height"));

	        var line = Math.round(relativeVPos / lineHeight);
	        return line;
	    },

	    getReference: function(referenceNumber) {
	        var refStartLine = this._getStartRefLineNumber(referenceNumber);
	        var refEndLine = this._getEndRefLineNumber(referenceNumber);
	        if (refStartLine == null || refEndLine == null) {
	            return null;
	        }

	        return [refStartLine, refEndLine];
	    },

	    simpleValidate: function(rubric) {
	        return Passage.validate(this.getUserInput(), rubric);
	    },

	    statics: {
	        displayMode: "block"
	    }
	});

	_.extend(Passage, {
	    validate: function(state, rubric) {
	        return {
	            type: "points",
	            earned: 0,
	            total: 0,
	            message: null
	        };
	    }
	});

	var PassageEditor = React.createClass({displayName: 'PassageEditor',
	    mixins: [EditorJsonify, Changeable],

	    propTypes: {
	        passageTitle: React.PropTypes.string,
	        passageText: React.PropTypes.string,
	        footnotes: React.PropTypes.string,
	        showLineNumbers: React.PropTypes.bool
	    },

	    getDefaultProps: function() {
	        return {
	            passageTitle: "",
	            passageText: "",
	            footnotes: "",
	            showLineNumbers: true
	        };
	    },

	    render: function() {
	        var passageEditor = React.createElement(Editor, {
	            ref: "passage-editor", 
	            content: this.props.passageText, 
	            widgetEnabled: false, 
	            placeholder: "Type passage here...", 
	            onChange: function(newProps)  {
	                this.change({ passageText: newProps.content });
	            }.bind(this)}
	        );
	        var footnotesEditor = React.createElement(Editor, {
	            ref: "passage-footnotes-editor", 
	            content: this.props.footnotes, 
	            widgetEnabled: false, 
	            placeholder: "Type footnotes here...", 
	            onChange: function(newProps)  {
	                this.change({ footnotes: newProps.content });
	            }.bind(this)}
	        );
	        return React.createElement("div", {className: "perseus-widget-passage-editor"}, 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                React.createElement(PropCheckBox, {
	                    label: "Show line numbers", 
	                    labelAlignment: "right", 
	                    showLineNumbers: this.props.showLineNumbers, 
	                    onChange: this.props.onChange})
	            ), 
	            React.createElement("div", null, 
	                "Passage title:", 
	                React.createElement(InfoTip, null, 
	                    React.createElement("p", null, "An optional title that will appear directly above the" + ' ' +
	                    "passage in the same font style. (E.g. Passage 1)")
	                ), 
	                React.createElement("div", null, 
	                    React.createElement("input", {
	                        type: "text", 
	                        defaultValue: this.props.passageTitle, 
	                        onChange: function(e)  {
	                            this.change({ passageTitle: e.target.value });
	                        }.bind(this)})
	                )
	            ), 
	            React.createElement("div", null, 
	                "Passage Text:", 
	                passageEditor
	            ), 
	            React.createElement("div", null, 
	                "Footnotes:", 
	                React.createElement(InfoTip, null, 
	                    React.createElement("p", null, "To add footnotes, add ^ characters where they belong in" + ' ' +
	                    "the passage. Then, add ^ in the footnotes area to reference" + ' ' +
	                    "the footnotes in the passage.")
	                ), 
	                footnotesEditor
	            )
	        );
	    },
	});

	module.exports = {
	    name: "passage",
	    displayName: "Passage",
	    widget: Passage,
	    editor: PassageEditor,
	    transform: function(editorProps)  {
	        return _.pick(editorProps, "passageTitle", "passageText", "footnotes",
	            "showLineNumbers");
	    }
	};


/***/ },
/* 35 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);
	var _ = __webpack_require__(60);

	var Changeable   = __webpack_require__(66);
	var EditorJsonify = __webpack_require__(67);
	var WidgetJsonifyDeprecated = __webpack_require__(68);
	var NumberInput = __webpack_require__(85);

	var EN_DASH = "\u2013";

	var PassageRef = React.createClass({displayName: 'PassageRef',
	    mixins: [WidgetJsonifyDeprecated, Changeable],

	    propTypes: {
	        passageNumber: React.PropTypes.number,
	        referenceNumber: React.PropTypes.number
	    },

	    getDefaultProps: function() {
	        return {
	            passageNumber: 1,
	            referenceNumber: 1
	        };
	    },

	    getInitialState: function() {
	        return {
	            lineRange: null
	        };
	    },

	    shouldComponentUpdate: function(nextProps, nextState) {
	        return !_.isEqual(this.props, nextProps) ||
	            !_.isEqual(this.state, nextState);
	    },

	    render: function() {
	        var lineRange = this.state.lineRange;
	        if (!lineRange) {
	            return React.createElement("span", null, 
	                $_({lineRange: "?" + EN_DASH + "?"}, 
	                    "lines %(lineRange)s"
	                )
	            );
	        }

	        if (lineRange[0] === lineRange[1]) {
	            return React.createElement("span", null, 
	                $_({lineNumber: lineRange[0]}, 
	                    "line %(lineNumber)s"
	                )
	            );
	        }

	        return React.createElement("span", null, 
	            $_({lineRange: lineRange[0] + EN_DASH + lineRange[1]}, 
	                "lines %(lineRange)s"
	            )
	        );
	    },

	    componentDidMount: function() {
	        _.defer(this._updateRange);
	    },

	    componentDidUpdate: function() {
	        _.defer(this._updateRange);
	    },

	    _updateRange: function() {
	        var passage = this.props.interWidgets(
	                "passage " + this.props.passageNumber)[0];

	        var range = null;
	        if (passage) {
	            range = passage.getReference(this.props.referenceNumber);
	        }

	        if (this.isMounted()) {
	            this.setState({
	                lineRange: range
	            });
	        }
	    },

	    simpleValidate: function(rubric) {
	        return PassageRef.validate(this.getUserInput(), rubric);
	    },

	    statics: {
	        displayMode: "inline"
	    }
	});

	_.extend(PassageRef, {
	    validate: function(state, rubric) {
	        return {
	            type: "points",
	            earned: 0,
	            total: 0,
	            message: null
	        };
	    }
	});

	var PassageRefEditor = React.createClass({displayName: 'PassageRefEditor',
	    mixins: [EditorJsonify, Changeable],

	    propTypes: {
	        passageNumber: React.PropTypes.number,
	        referenceNumber: React.PropTypes.number
	    },

	    getDefaultProps: function() {
	        return {
	            passageNumber: 1,
	            referenceNumber: 1
	        };
	    },

	    render: function() {
	        return React.createElement("div", null, 
	            React.createElement("div", null, 
	                "Passage Number:", 
	                React.createElement(NumberInput, {
	                    value: this.props.passageNumber, 
	                    onChange: this.change("passageNumber")})
	            ), 
	            React.createElement("div", null, 
	                "Reference Number:", 
	                React.createElement(NumberInput, {
	                    value: this.props.referenceNumber, 
	                    onChange: this.change("referenceNumber")})
	            )
	        );
	    }
	});

	module.exports = {
	    name: "passage-ref",
	    displayName: "PassageRef",
	    widget: PassageRef,
	    editor: PassageRefEditor,
	    transform: function(editorProps)  {
	        return _.pick(editorProps, "passageNumber", "referenceNumber");
	    },
	    version: {major: 0, minor: 0}
	};


/***/ },
/* 36 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);
	var _ = __webpack_require__(60);

	var Changeable   = __webpack_require__(66);
	var EditorJsonify = __webpack_require__(67);
	var WidgetJsonifyDeprecated = __webpack_require__(68);
	var Renderer = __webpack_require__(10);

	var PassageRefTarget = React.createClass({displayName: 'PassageRefTarget',
	    mixins: [WidgetJsonifyDeprecated, Changeable],

	    propTypes: {
	        content: React.PropTypes.string
	    },

	    getDefaultProps: function() {
	        return {
	            content: ""
	        };
	    },

	    render: function() {
	        return React.createElement(Renderer, {
	            content: this.props.content, 
	            inline: true, 
	            enabledFeatures: this.props.enabledFeatures, 
	            apiOptions: this.props.apiOptions}
	            );
	    },

	    simpleValidate: function(rubric) {
	        return PassageRefTarget.validate(this.getUserInput(), rubric);
	    },

	    statics: {
	        displayMode: "inline"
	    }
	});

	_.extend(PassageRefTarget, {
	    validate: function(state, rubric) {
	        return {
	            type: "points",
	            earned: 0,
	            total: 0,
	            message: null
	        };
	    }
	});

	var PassageRefTargetEditor = React.createClass({displayName: 'PassageRefTargetEditor',
	    mixins: [EditorJsonify, Changeable],

	    propTypes: {
	        content: React.PropTypes.string
	    },

	    getDefaultProps: function() {
	        return {
	            content: ""
	        };
	    },

	    render: function() {
	        return React.createElement("div", null, 
	            "Content:", 
	            React.createElement("input", {type: "text", 
	                value: this.props.content, 
	                onChange: this.handleContentChange})
	        );
	    },

	    handleContentChange: function(e) {
	        this.change({content: e.target.value});
	    }
	});

	module.exports = {
	    name: "passage-ref-target",
	    displayName: "PassageRefTarget",
	    widget: PassageRefTarget,
	    editor: PassageRefTargetEditor,
	    hidden: true,
	    transform: function(editorProps)  {
	        return _.pick(editorProps, "content");
	    },
	    version: {major: 0, minor: 0}
	};


/***/ },
/* 37 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);
	var InfoTip = __webpack_require__(69);
	var BlurInput = __webpack_require__(82);
	var _ = __webpack_require__(60);

	var NumberInput = __webpack_require__(85);
	var TextListEditor = __webpack_require__(70);
	var RangeInput = __webpack_require__(83);
	var SvgImage = __webpack_require__(57);

	var ApiClassNames = __webpack_require__(12).ClassNames;

	var deepEq = __webpack_require__(3).deepEq;
	var knumber = __webpack_require__(103).number;

	var BAR = "bar",
	    LINE = "line",
	    PIC = "pic",
	    HISTOGRAM = "histogram",
	    DOTPLOT = "dotplot";

	var DOT_PLOT_POINT_SIZE = 4;
	var DOT_PLOT_POINT_PADDING = 8;

	var widgetPropTypes = {
	    type: React.PropTypes.oneOf([BAR, LINE, PIC, HISTOGRAM, DOTPLOT]),
	    labels: React.PropTypes.arrayOf(React.PropTypes.string),
	    categories: React.PropTypes.arrayOf(React.PropTypes.oneOfType([
	        React.PropTypes.number,
	        React.PropTypes.string
	    ])),

	    scaleY: React.PropTypes.number,
	    maxY: React.PropTypes.number,
	    snapsPerLine: React.PropTypes.number,

	    picSize: React.PropTypes.number,
	    pixBoxHeight: React.PropTypes.number,
	    picUrl: React.PropTypes.string,

	    plotDimensions: React.PropTypes.arrayOf(React.PropTypes.number),
	    labelInterval: React.PropTypes.number
	};

	var formatNumber = function(num)  {return "$" + knumber.round(num, 2) + "$";};

	var Plotter = React.createClass({displayName: 'Plotter',
	    propTypes: widgetPropTypes,

	    getDefaultProps: function () {
	        return {
	            type: BAR,
	            labels: ["", ""],
	            categories: [""],

	            scaleY: 1,
	            maxY: 10,
	            snapsPerLine: 2,

	            picSize: 40,
	            picBoxHeight: 48,
	            picUrl: "",

	            plotDimensions: [380, 300],
	            labelInterval: 1
	        };
	    },

	    getInitialState: function() {
	        return {
	            values: this.props.starting || [1]
	        };
	    },

	    render: function() {
	        return React.createElement("div", {
	            className: "perseus-widget-plotter graphie " +
	                ApiClassNames.INTERACTIVE, 
	            ref: "graphieDiv"});
	    },

	    componentDidUpdate: function(prevProps, prevState) {
	        if (this.shouldSetupGraphie) {
	            this.setupGraphie(prevState);
	        }
	    },

	    componentDidMount: function() {
	        this.setupGraphie(this.state);
	    },

	    componentWillReceiveProps: function(nextProps) {
	        var props = ["type", "labels", "categories", "scaleY", "maxY",
	            "snapsPerLine", "picUrl", "labelInterval"];

	        this.shouldSetupGraphie = _.any(props, function (prop) {
	            return !_.isEqual(this.props[prop], nextProps[prop]);
	        }, this);

	        if (!_.isEqual(this.props.starting, nextProps.starting) &&
	            !_.isEqual(this.state.values, nextProps.starting)) {
	            this.shouldSetupGraphie = true;
	            this.setState({values: nextProps.starting});
	        }
	    },

	    setupGraphie: function(prevState) {
	        var self = this;
	        self.shouldSetupGraphie = false;
	        var graphieDiv = self.refs.graphieDiv.getDOMNode();
	        $(graphieDiv).empty();
	        var graphie = KhanUtil.createGraphie(graphieDiv);

	        // TODO(jakesandlund): It's not the react way to hang
	        // something off the component object, but since graphie
	        // is outside React, it makes it easier to do this.
	        self.graphie = graphie;
	        self.graphie.pics = [];

	        var isBar = self.props.type === BAR,
	            isLine = self.props.type === LINE,
	            isPic = self.props.type === PIC,
	            isHistogram = self.props.type === HISTOGRAM,
	            isDotplot = self.props.type === DOTPLOT;

	        var isTiledPlot = isPic || isDotplot;

	        var config = {};
	        var c = config; // c for short

	        c.graph = {
	            lines: [],
	            bars: [],
	            points: [],
	            dividers: []
	        };
	        c.scaleY = self.props.scaleY;
	        c.dimX = self.props.categories.length;
	        var plotDimensions = self.props.plotDimensions;
	        if (isLine) {
	            c.dimX += 1;
	        } else if (isHistogram) {
	            c.barPad = 0;
	            c.barWidth = 1;
	        } else if (isBar) {
	            c.barPad = 0.15;
	            c.barWidth = 1 - 2 * c.barPad;
	            c.dimX += 2 * c.barPad;
	        } else if (isTiledPlot) {
	            c.picBoxHeight = self.props.picBoxHeight;
	            c.picBoxWidthPx = plotDimensions[0] / self.props.categories.length;
	            var picPadAllWidth = plotDimensions[0] - c.dimX * c.picBoxWidthPx;
	            c.picPad = picPadAllWidth / (2 * c.dimX + 2);
	            var picFullWidth = c.picBoxWidthPx + 2 * c.picPad;

	            // Convert from px to "unscaled"
	            c.picPad = c.picPad / picFullWidth;
	            c.picBoxWidth = c.picBoxWidthPx / picFullWidth;
	            c.dimX += 2 * c.picPad;
	        }

	        if (isDotplot) {
	            c.picBoxHeight = DOT_PLOT_POINT_SIZE * 2 + DOT_PLOT_POINT_PADDING;
	        }

	        c.dimY = Math.ceil(self.props.maxY / c.scaleY) * c.scaleY;
	        c.scale = _.map([c.dimX, c.dimY], function (dim, i) {
	            return plotDimensions[i] / dim;
	        });
	        if (isTiledPlot) {
	            c.scale[1] = c.picBoxHeight / c.scaleY;
	        }

	        var padX = 25 / c.scale[0];
	        var padY = 25 / c.scale[1];

	        // Since dotplot doesn't have an axis along the left it looks weird
	        // with the same padding as the others
	        if (isDotplot) {
	            padX /= 2;
	        }

	        graphie.init({
	            range: [[-3 * padX, c.dimX + padX], [-3 * padY, c.dimY + padY]],
	            scale: c.scale
	        });
	        graphie.addMouseLayer({
	            allowScratchpad: true
	        });

	        if (!isTiledPlot) {
	            for (var y = 0; y <= c.dimY; y += c.scaleY) {
	                graphie.label(
	                    [0, y],
	                    KhanUtil.roundToApprox(y, 2),
	                    "left",
	                    /* isTeX */ true /* for the \approx symbol */
	                );
	                graphie.style(
	                    {stroke: "#000", strokeWidth: 1, opacity: 0.3},
	                    function() {
	                        graphie.line([0, y], [c.dimX, y]);
	                    });
	            }
	        }

	        self.setupCategories(config);

	        if (isTiledPlot) {
	            self.drawPicHeights(self.state.values, prevState.values);
	        }

	        graphie.style(
	            {stroke: "#000", strokeWidth: 2, opacity: 1.0},
	            function() {
	                if (isDotplot) {
	                    graphie.line([0.5, 0], [c.dimX - 0.5, 0]);
	                } else {
	                    graphie.line([0, 0], [c.dimX, 0]);
	                    graphie.line([0, 0], [0, c.dimY]);
	                }
	            });

	        graphie.label([c.dimX / 2, -35 / c.scale[1]],
	            self.props.labels[0],
	            "below", false)
	            .css("font-weight", "bold");

	        graphie.label([-60 / c.scale[0], c.dimY / 2],
	            self.props.labels[1],
	            "center", false)
	            .css("font-weight", "bold")
	            .addClass("rotate");
	    },

		labelCategory: function(x, category) {
			var graphie = this.graphie;
			category = category + "";
			var isTeX = false;
			var mathyCategory = category.match(/^\$(.*)\$$/);
			if (mathyCategory) {
				category = mathyCategory[1];
				isTeX = true;
			}
			graphie.label([x, 0], category, "below", isTeX);
		},

	    setupCategories: function(config) {
	        var self = this;
	        var c = config;
	        var graphie = self.graphie;

	        if (self.props.type === HISTOGRAM) {
	            // Histograms with n labels/categories have n - 1 buckets
	            _.times(self.props.categories.length - 1, function(i) {
	                self.setupBar({
	                    index: i,
	                    startHeight: self.state.values[i],
	                    config: config,
	                    isHistogram: true
	                });
	            });

	            // Label categories
	            _.each(self.props.categories, function(category, i) {
	                var x = 0.5 + i * c.barWidth;

	                self.labelCategory(x, category);
	                var tickHeight = 6 / c.scale[1];
	                graphie.style({
	                    stroke: "#000", strokeWidth: 2, opacity: 1.0
	                }, function() {
	                    graphie.line([x, -tickHeight], [x, 0]);
	                });
	            });
	        } else {
	            _.each(self.props.categories, function (category, i) {
	                var startHeight = self.state.values[i];
	                var x;

	                if (self.props.type === BAR) {
	                    x = self.setupBar({
	                        index: i,
	                        startHeight: startHeight,
	                        config: config,
	                        isHistogram: false
	                    });
	                } else if (self.props.type === LINE) {
	                    x = self.setupLine(i, startHeight, config);
	                } else if (self.props.type === PIC) {
	                    x = self.setupPic(i, config);
	                } else if (self.props.type === DOTPLOT) {
	                    x = self.setupDotplot(i, config);
	                }

	                var tickStart = 0;
	                var tickEnd = -6 / c.scale[1];

	                if (self.props.type === DOTPLOT) {
	                    tickStart = -tickEnd;
	                }

	                if (self.props.type === DOTPLOT) {
	                    // Dotplot lets you specify to only show labels every 'n'
	                    // ticks. It also looks nicer if it makes the labelled
	                    // ticks a bit bigger.
	                    if (i % self.props.labelInterval === 0 ||
	                            i === self.props.categories.length - 1) {
	                        self.labelCategory(x, category);
	                        tickStart *= 1.5;
	                        tickEnd *= 1.5;
	                    }
	                } else {
	                    self.labelCategory(x, category);
	                }

	                graphie.style({
	                    stroke: "#000", strokeWidth: 2, opacity: 1.0
	                }, function() {
	                    graphie.line([x, tickStart], [x, tickEnd]);
	                });
	            });
	        }
	    },

	    setupBar: function(args) {
	        var i = args.index;
	        var startHeight = args.startHeight;
	        var config = args.config;
	        var isHistogram = args.isHistogram;

	        var self = this;
	        var graphie = self.graphie;
	        var barHalfWidth = config.barWidth / 2;
	        var x;
	        if (isHistogram) {
	            x = 0.5 + i * config.barWidth + barHalfWidth;
	        } else {
	            x = 0.5 + i + config.barPad;
	        }

	        var scaleBar = function(i, height) {
	            var center = graphie.scalePoint(0);

	            // Scale filled bucket (bar)
	            config.graph.bars[i].scale(
	                    1, Math.max(0.01, height / config.scaleY),
	                    center[0], center[1]);

	            if (isHistogram) {
	                // Scale dividers between buckets
	                var leftDivider = config.graph.dividers[i - 1],
	                    rightDivider = config.graph.dividers[i];

	                if (leftDivider) {
	                    var divHeight = Math.min(self.state.values[i - 1], height);
	                    leftDivider.scale(
	                        1, Math.max(0.01, divHeight / config.scaleY),
	                        center[0], center[1]);
	                }

	                if (rightDivider) {
	                    var divHeight = Math.min(self.state.values[i + 1], height);
	                    rightDivider.scale(
	                        1, Math.max(0.01, divHeight / config.scaleY),
	                        center[0], center[1]
	                    );
	                }
	            }
	        };

	        graphie.style({
	            stroke: "none", fill: KhanUtil.LIGHT_BLUE, opacity: 1.0
	        }, function() {
	            config.graph.bars[i] = graphie.path([
	                [x - barHalfWidth, 0],
	                [x - barHalfWidth, config.scaleY],
	                [x + barHalfWidth, config.scaleY],
	                [x + barHalfWidth, 0],
	                [x - barHalfWidth, 0]
	            ]);
	        });

	        if (isHistogram) {
	            if (i > 0) {
	                // Don't draw a divider to the left of the first bucket
	                graphie.style({
	                    stroke: "#000", strokeWidth: 1, opacity: 0.3
	                }, function() {
	                    config.graph.dividers.push(graphie.path([
	                        [x - barHalfWidth, 0],
	                        [x - barHalfWidth, config.scaleY]
	                    ]));
	                });
	            }
	        }

	        config.graph.lines[i] = graphie.addMovableLineSegment({
	            coordA: [x - barHalfWidth, startHeight],
	            coordZ: [x + barHalfWidth, startHeight],
	            snapY: config.scaleY / self.props.snapsPerLine,
	            constraints: {
	                constrainX: true
	            },
	            normalStyle: {
	                "stroke": KhanUtil.INTERACTIVE,
	                "stroke-width": 4
	            }
	        });

	        config.graph.lines[i].onMove = function(dx, dy) {
	            var y = this.coordA[1];
	            if (y < 0 || y > config.dimY) {
	                y = Math.min(Math.max(y, 0), config.dimY);
	                this.coordA[1] = this.coordZ[1] = y;

	                // Snap the line back into range.
	                this.transform();
	            }

	            var values = _.clone(self.state.values);
	            values[i] = y;
	            self.setState({values: values});
	            self.props.onChange({ values: values });

	            scaleBar(i, y);
	        };

	        scaleBar(i, startHeight);
	        return x;
	    },

	    setupLine: function(i, startHeight, config) {
	        var self = this;
	        var c = config;
	        var graphie = self.graphie;
	        var x = i + 1;
	        c.graph.points[i] = graphie.addMovablePoint({
	            coord: [x, startHeight],
	            constraints: {
	                constrainX: true
	            },
	            normalStyle: {
	                fill: KhanUtil.INTERACTIVE,
	                stroke: KhanUtil.INTERACTIVE
	            },
	            snapY: c.scaleY / self.props.snapsPerLine,
	        });
	        c.graph.points[i].onMove = function(x, y) {
	            y = Math.min(Math.max(y, 0), c.dimY);
	            var values = _.clone(self.state.values);
	            values[i] = y;
	            self.setState({values: values});
	            self.props.onChange({ values: values });
	            return [x, y];
	        };
	        if (i > 0) {
	            c.graph.lines[i] = graphie.addMovableLineSegment({
	                pointA: c.graph.points[i - 1],
	                pointZ: c.graph.points[i],
	                constraints: {
	                    fixed: true
	                },
	                normalStyle: {
	                    stroke: "#9ab8ed",
	                    "stroke-width": 2
	                }
	            });
	        }
	        return x;
	    },

	    setupDotplot: function(i, config) {
	        var graphie = this.graphie;
	        return this.setupTiledPlot(i, 1, config, function(x, y)  {
	            return graphie.ellipse([x, y],
	                 [
	                     DOT_PLOT_POINT_SIZE / graphie.scale[0],
	                     DOT_PLOT_POINT_SIZE / graphie.scale[1]
	                 ],
	                 {
	                    fill: KhanUtil.INTERACTIVE,
	                    stroke: KhanUtil.INTERACTIVE
	                 });
	        });
	    },

	    setupPic: function(i, config) {
	        var graphie = this.graphie;
	        return this.setupTiledPlot(i, 0, config, function(x, y)  {
	            var scaledCenter = graphie.scalePoint([x, y]);
	            var size = this.props.picSize;
	            return graphie.raphael.image(
	                    this.props.picUrl,
	                    scaledCenter[0] - size / 2,
	                    scaledCenter[1] - size / 2,
	                    size,
	                    size);
	        }.bind(this));
	    },

	    setupTiledPlot: function(i, bottomMargin, config, createImage) {
	        var self = this;
	        var c = config;
	        var graphie = self.graphie;
	        var pics = graphie.pics;
	        var x = i + 0.5 + c.picPad;

	        pics[i] = [];
	        var n = Math.round(c.dimY / c.scaleY) + 1;
	        _(n).times(function(j) {
	            j -= 1;
	            var midY = (j + 0.5) * c.scaleY;
	            var leftX = x - c.picBoxWidth / 2;
	            var topY = midY + 0.5 * c.scaleY;
	            var coord = graphie.scalePoint([leftX, topY + bottomMargin]);
	            var mouseRect = graphie.mouselayer.rect(
	                    coord[0], coord[1], c.picBoxWidthPx, c.picBoxHeight);
	            $(mouseRect[0])
	                .css({fill: "#000", opacity: 0.0, cursor: "pointer"})
	                .on("vmousedown", function(e) {
	                    e.preventDefault();
	                    self.whichPicClicked = i;
	                    self.setPicHeight(i, topY);

	                    $(document).on("vmouseup.plotTile", function(e) {
	                        $(document).unbind(".plotTile");
	                    });

	                    $(document).on("vmousemove.plotTile", function(e) {
	                        e.preventDefault();

	                        // Reverse-engineer the initial calculation
	                        var yCoord = graphie.getMouseCoord(e)[1];
	                        var adjustedCoord = Math.floor(yCoord - bottomMargin);

	                        // Calculate top coord from j value, but don't let them
	                        // go below j = -1, which is equivalent to having '0'
	                        // on the dot plot (due to weird indexing).
	                        var newJ = Math.max(-1,
	                            Math.floor(adjustedCoord / c.scaleY));
	                        var newMidY = (newJ + 0.5) * c.scaleY;
	                        var newTopY = newMidY + 0.5 * c.scaleY;
	                        self.setPicHeight(self.whichPicClicked, newTopY);
	                    });
	                });

	            if (j < 0) {
	                // Don't show a pic underneath the axis!
	                return;
	            }
	            pics[i][j] = createImage(x, midY + bottomMargin);
	        });
	        return x;
	    },

	    setPicHeight: function(i, y) {
	        var values = _.clone(this.state.values);
	        values[i] = y;
	        this.drawPicHeights(values, this.state.values);
	        this.setState({values: values});
	        this.props.onChange({ values: values });
	    },

	    drawPicHeights: function(values, prevValues) {
	        var self = this;
	        var graphie = self.graphie;
	        var pics = graphie.pics;
	        _.each(pics, function(ps, i) {
	            _.each(ps, function(pic, j) {
	                var y = (j + 1) * self.props.scaleY;
	                var show = y <= values[i];
	                if (self.props.type === DOTPLOT) {
	                    var wasShown = y <= prevValues[i];
	                    var wasJustShown = show && !wasShown;
	                    if (wasJustShown) {
	                        pic.animate({
	                            "stroke-width": 8
	                        }, 75, function()  {return pic.animate({
	                                "stroke-width": 2
	                            }, 75);});
	                    }
	                }
	                $(pic[0]).css({display: show ? "inline" : "none"});
	            });
	        });
	    },

	    getUserInput: function() {
	        return this.state.values;
	    },

	    simpleValidate: function(rubric) {
	        return Plotter.validate(this.getUserInput(), rubric);
	    },

	    statics: {
	        displayMode: "block"
	    }
	});

	_.extend(Plotter, {
	    validate: function (guess, rubric) {
	        if (deepEq(guess, rubric.starting)) {
	            return {
	                type: "invalid",
	                message: null
	            };
	        } else {
	            return {
	                type: "points",
	                earned: deepEq(guess, rubric.correct) ? 1 : 0,
	                total: 1,
	                message: null
	            };
	        }
	    }
	});


	// Return a copy of array with length n, padded with given value
	function padArray(array, n, value) {
	    var copy = _.clone(array);
	    copy.length = n;
	    for (var i = array.length; i < n; i++) {
	        copy[i] = value;
	    }
	    return copy;
	}

	var editorDefaults = {
	    scaleY: 1,
	    maxY: 10,
	    snapsPerLine: 2
	};

	var PlotterEditor = React.createClass({displayName: 'PlotterEditor',
	    propTypes: widgetPropTypes,

	    getDefaultProps: function () {
	        return _.extend({}, editorDefaults, {
	            correct: [1],
	            starting: [1],

	            type: BAR,
	            labels: ["", ""],
	            categories: [""],

	            picSize: 30,
	            picBoxHeight: 36,
	            picUrl: Khan.imageBase + "badges/earth-small.png",

	            plotDimensions: [275, 200],
	            labelInterval: 1
	        });
	    },

	    getInitialState: function() {
	        return {
	            editing: "correct",
	            pic: null,
	            loadedUrl: null,
	            minX: null,
	            maxX: null,
	            tickStep: null
	        };
	    },

	    componentWillMount: function() {
	        this.fetchPic(this.props.picUrl);
	    },

	    componentWillReceiveProps: function(nextProps) {
	        this.fetchPic(nextProps.picUrl);
	    },

	    fetchPic: function(url) {
	        if (this.state.loadedUrl !== url) {
	            var pic = new Image();
	            pic.src = url;
	            pic.onload = function()  {
	                this.setState({
	                    pic: pic,
	                    loadedUrl: url
	                });
	            }.bind(this);
	        }
	    },

	    render: function() {
	        var setFromScale = _.contains([LINE, HISTOGRAM, DOTPLOT],
	                                      this.props.type);
	        var canChangeSnaps = !_.contains([PIC, DOTPLOT], this.props.type);
	        return React.createElement("div", {className: "perseus-widget-plotter-editor"}, 
	            React.createElement("div", null, 
	                "Chart type:", ' ', 
	                _.map([BAR, LINE, PIC, HISTOGRAM, DOTPLOT], function(type) {
	                    return React.createElement("label", {key: type}, 
	                        React.createElement("input", {
	                            type: "radio", 
	                            name: "chart-type", 
	                            checked: this.props.type === type, 
	                            onChange: _.partial(this.changeType, type)}), 
	                        type
	                    );
	                }, this)
	            ), 
	            React.createElement("div", null, 
	                "Labels:", ' ', 
	                _.map(["x", "y"], function(axis, i) {
	                    return React.createElement("label", {key: axis}, 
	                        axis + ":", 
	                        React.createElement("input", {
	                            type: "text", 
	                            onChange: _.partial(this.changeLabel, i), 
	                            defaultValue: this.props.labels[i]})
	                    );
	                }, this)
	            ), 

	            setFromScale && React.createElement("div", {className: "set-from-scale-box"}, 
	                React.createElement("span", {className: "categories-title"}, 
	                    "Set Categories From Scale"
	                ), 
	                React.createElement("div", null, 
	                    React.createElement("label", null, 
	                        "Tick Step:", ' ', 
	                        React.createElement(NumberInput, {
	                            placeholder: 1, 
	                            useArrowKeys: true, 
	                            value: this.state.tickStep, 
	                            onChange: this.handleChangeTickStep})
	                    ), 
	                    React.createElement(InfoTip, null, 
	                        React.createElement("p", null, "The difference between adjacent ticks.")
	                    )
	                ), 
	                React.createElement("div", null, 
	                    React.createElement("label", null, 
	                        "Range:", ' ', 
	                        React.createElement(RangeInput, {
	                            placeholder: [0, 10], 
	                            useArrowKeys: true, 
	                            value: [this.state.minX, this.state.maxX], 
	                            onChange: this.handleChangeRange})
	                    )
	                ), 
	                React.createElement("div", null, 
	                    React.createElement("button", {onClick: this.setCategoriesFromScale}, 
	                        "Set Categories", ' '
	                    )
	                )
	            ), 
	            React.createElement("div", null, 
	                React.createElement("label", null, 
	                    "Label Interval:", ' ', 
	                    React.createElement(NumberInput, {
	                        useArrowKeys: true, 
	                        value: this.props.labelInterval, 
	                        onChange: this.changeLabelInterval})
	                ), 
	                React.createElement(InfoTip, null, 
	                    React.createElement("p", null, "Which ticks to display the labels for. For instance," + ' ' +
	                    "setting this to \"4\" will only show every 4th label (plus" + ' ' +
	                    "the last one)")
	                )
	            ), 
	            this.props.type === PIC && React.createElement("div", null, 
	                React.createElement("label", null, 
	                    "Picture:", ' ', 
	                    React.createElement(BlurInput, {
	                        className: "pic-url", 
	                        value: this.props.picUrl, 
	                        onChange: this.changePicUrl}), 
	                React.createElement(InfoTip, null, 
	                    React.createElement("p", null, "Use the default picture of Earth, or insert the URL for" + ' ' +
	                    "a different picture using the \"Add image\" function.")
	                )
	                ), 
	                this.state.pic &&
	                    this.state.pic.width !== this.state.pic.height &&
	                    React.createElement("p", {className: "warning"}, 
	                        React.createElement("b", null, "Warning"), ": You are using a picture which is not" + ' ' +
	                        "square.  This means the image will get distorted. You" + ' ' +
	                        "should probably crop it to be square."
	                    )
	            ), 
	            React.createElement("div", null, 
	                React.createElement("label", null, 
	                    "Categories:", ' ', 
	                    React.createElement(TextListEditor, {
	                        ref: "categories", 
	                        layout: "horizontal", 
	                        options: this.props.categories, 
	                        onChange: this.changeCategories})
	                )
	            ), 
	            React.createElement("div", null, 
	                React.createElement("label", null, 
	                    "Scale (y):", ' ', 
	                    React.createElement("input", {
	                        type: "text", 
	                        onChange: this.changeScale, 
	                        defaultValue: this.props.scaleY})
	                )
	            ), 
	            React.createElement("div", null, 
	                React.createElement("label", null, 
	                    "Max y:", ' ', 
	                    React.createElement("input", {
	                        type: "text", 
	                        ref: "maxY", 
	                        onChange: this.changeMax, 
	                        defaultValue: this.props.maxY})
	                )
	            ), 
	            canChangeSnaps && React.createElement("div", null, 
	                React.createElement("label", null, 
	                    "Snaps per line:", ' ', 
	                    React.createElement("input", {
	                        type: "text", 
	                        onChange: this.changeSnaps, 
	                        defaultValue: this.props.snapsPerLine})
	                ), 
	                React.createElement(InfoTip, null, 
	                    React.createElement("p", null, "Creates the specified number of divisions between the" + ' ' +
	                    "horizontal lines. Fewer snaps between lines makes the graph" + ' ' +
	                    "easier for the student to create correctly.")
	                )
	            ), 
	            React.createElement("div", null, 
	                "Editing values:", ' ', 
	                _.map(["correct", "starting"], function(editing) {
	                    return React.createElement("label", {key: editing}, 
	                        React.createElement("input", {
	                            type: "radio", 
	                            name: "editing", 
	                            checked: this.state.editing === editing, 
	                            onChange: _.partial(this.changeEditing, editing)}), 
	                        editing
	                    );
	                }, this), 
	                React.createElement(InfoTip, null, React.createElement("p", null, 
	                    "Use this toggle to switch between editing the correct" + ' ' +
	                    "answer (what the student will be graded on) and the" + ' ' +
	                    "starting values (what the student will see plotted when" + ' ' +
	                    "they start the problem). Note: These cannot be the same."
	                ))
	            ), 
	            React.createElement(Plotter, React.__spread({}, 
	                this.props, 
	                {starting: this.props[this.state.editing], 
	                onChange: this.handlePlotterChange}))
	        );
	    },

	    handleChangeTickStep: function(value) {
	        this.setState({
	            tickStep: value
	        });
	    },

	    handleChangeRange: function(newValue) {
	        this.setState({
	            minX: newValue[0],
	            maxX: newValue[1]
	        });
	    },

	    changeLabelInterval: function(value) {
	        this.props.onChange({
	            labelInterval: value
	        });
	    },

	    handlePlotterChange: function(newProps) {
	        var props = {};
	        props[this.state.editing] = newProps.values;
	        this.props.onChange(props);
	    },

	    changeType: function(type) {
	        var categories;
	        if (type === HISTOGRAM) {
	            // Switching to histogram, add a label (0) to the left
	            categories = [formatNumber(0)].concat(this.props.categories);
	            this.props.onChange({type: type, categories: categories});
	        } else if (this.props.type === HISTOGRAM) {
	            // Switching from histogram, remove a label from the left
	            categories = this.props.categories.slice(1);
	            this.props.onChange({type: type, categories: categories});
	        } else {
	            this.props.onChange({type: type});
	        }

	        if (categories) {
	            this.refs.categories.getDOMNode().value = categories.join(", ");
	        }
	    },

	    changeLabel: function(i, e) {
	        var labels = _.clone(this.props.labels);
	        labels[i] = e.target.value;
	        this.props.onChange({labels: labels});
	    },

	    changePicUrl: function(value) {
	        // We don't need the labels and other data in the plotter, so just
	        // extract the raw image and use that.
	        // TODO(emily): Maybe indicate that such a change has happened?
	        var url = SvgImage.getRealImageUrl(value);

	        this.props.onChange({picUrl: url});
	    },

	    changeCategories: function(categories) {
	        var n = categories.length;
	        if (this.props.type === HISTOGRAM) {
	            // Histograms with n labels/categories have n - 1 buckets
	            n--;
	        }
	        var value = this.props.scaleY;

	        this.props.onChange({
	            categories: categories,
	            correct: padArray(this.props.correct, n, value),
	            starting: padArray(this.props.starting, n, value)
	        });
	    },

	    changeScale: function(e) {
	        var oldScale = this.props.scaleY;
	        var newScale = +e.target.value || editorDefaults.scaleY;

	        var scale = function(value) {
	            return value * newScale / oldScale;
	        };

	        var maxY = scale(this.props.maxY);

	        this.props.onChange({
	            scaleY: newScale,
	            maxY: maxY,
	            correct: _.map(this.props.correct, scale),
	            starting: _.map(this.props.starting, scale)
	        });

	        this.refs.maxY.getDOMNode().value = maxY;
	    },

	    changeMax: function(e) {
	        this.props.onChange({
	            maxY: +e.target.value || editorDefaults.maxY
	        });
	    },

	    changeSnaps: function(e) {
	        this.props.onChange({
	            snapsPerLine: +e.target.value || editorDefaults.snapsPerLine
	        });
	    },

	    changeEditing: function(editing) {
	        this.setState({editing: editing});
	    },

	    setCategoriesFromScale: function() {
	        var scale = this.state.tickStep || 1;
	        var min = this.state.minX || 0;
	        var max = this.state.maxX || 0;
	        var length = Math.floor((max - min) / scale) * scale;

	        var categories;
	        if (this.props.type === HISTOGRAM || this.props.type === DOTPLOT) {
	            // Ranges for histogram and dotplot labels should start at zero
	            categories = _.range(0, length + scale, scale);
	        } else {
	            categories = _.range(scale, length + scale, scale);
	        }

	        categories = _.map(categories, function(num)  {return num + min;});
	        categories = _.map(categories, formatNumber);

	        this.changeCategories(categories);

	        this.refs.categories.getDOMNode().value = categories.join(", ");
	    },

	    serialize: function() {
	        var json = _.pick(this.props, "correct", "starting", "type", "labels",
	            "categories", "scaleY", "maxY", "snapsPerLine", "labelInterval");

	        if (this.props.type === PIC) {
	            json.picUrl = this.props.picUrl;
	        }

	        return json;
	    }
	});

	module.exports = {
	    name: "plotter",
	    displayName: "Plotter",
	    widget: Plotter,
	    editor: PlotterEditor
	};


/***/ },
/* 38 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);
	var _ = __webpack_require__(60);

	var Changeable = __webpack_require__(66);
	var ApiClassNames = __webpack_require__(12).ClassNames;

	var Editor = __webpack_require__(7);
	var PropCheckBox = __webpack_require__(58);
	var Renderer = __webpack_require__(10);
	var PassageRef = __webpack_require__(35);
	var Util = __webpack_require__(3);

	var InfoTip = __webpack_require__(69);

	var shuffle = __webpack_require__(3).shuffle;
	var seededRNG = __webpack_require__(3).seededRNG;
	var captureScratchpadTouchStart =
	        __webpack_require__(3).captureScratchpadTouchStart;

	var cx = React.addons.classSet;

	var BaseRadio = React.createClass({displayName: 'BaseRadio',
	    propTypes: {
	        labelWrap: React.PropTypes.bool,
	        multipleSelect: React.PropTypes.bool,
	        onCheckedChange: React.PropTypes.func,
	        showClues: React.PropTypes.bool,
	        onePerLine: React.PropTypes.bool,
	        apiOptions: React.PropTypes.object,
	        reviewModeRubric: React.PropTypes.object,
	        deselectEnabled: React.PropTypes.bool,
	    },

	    getDefaultProps: function() {
	        return {
	            onePerLine: true,
	        };
	    },

	    render: function() {
	        var radioGroupName = _.uniqueId("perseus_radio_");
	        var inputType = this.props.multipleSelect ? "checkbox" : "radio";
	        var rubric = this.props.reviewModeRubric;

	        return React.createElement("ul", {className: "perseus-widget-radio " +
	                "above-scratchpad blank-background"}, 
	            this.props.multipleSelect &&
	                React.createElement("div", {className: "instructions"}, 
	                    $_(null, "Select all that apply.")
	                ), 
	            this.props.choices.map(function(choice, i) {

	                var content = React.createElement("div", null, 
	                        choice.content
	                    );

	                var classSet = {
	                    "inline": !this.props.onePerLine
	                };
	                classSet[ApiClassNames.RADIO.OPTION] = true;
	                classSet[ApiClassNames.INTERACTIVE] =
	                    !this.props.apiOptions.readOnly;
	                classSet[ApiClassNames.RADIO.SELECTED] = choice.checked;
	                if (rubric) {
	                    classSet[ApiClassNames.CORRECT] =
	                        rubric.choices[i].correct;
	                    classSet[ApiClassNames.INCORRECT] =
	                        !rubric.choices[i].correct;
	                }
	                var className = cx(classSet);

	                return React.createElement("li", {className: className, key: i, 
	                            onTouchStart: !this.props.labelWrap ?
	                                null : captureScratchpadTouchStart, 
	                            
	                            onClick: !this.props.labelWrap ? null : function(e)  {
	                                // Don't send this to the scratchpad
	                                e.preventDefault();
	                                if (!this.props.apiOptions.readOnly) {
	                                    var shouldToggle =
	                                        this.props.multipleSelect ||
	                                        this.props.deselectEnabled;
	                                    this.checkOption(
	                                        i,
	                                        shouldToggle ? !choice.checked : true);
	                                }
	                            }.bind(this)}, 
	                    React.createElement("div", null, 
	                        React.createElement("span", {className: "checkbox"}, 
	                            React.createElement("input", {
	                                ref: "radio" + i, 
	                                type: inputType, 
	                                name: radioGroupName, 
	                                checked: choice.checked, 
	                                onClick: function(e)  {
	                                    // Avoid sending this to the parent
	                                    e.stopPropagation();
	                                }, 
	                                onChange: function(e)  {
	                                    this.checkOption(i, e.target.checked);
	                                }.bind(this), 
	                                disabled: this.props.apiOptions.readOnly})
	                        ), 
	                        /* A pseudo-label. <label> is slightly broken on iOS,
	                            so this works around that. Unfortunately, it is
	                            simplest to just work around that everywhere. */
	                        React.createElement("span", {
	                                className: 
	                                    ApiClassNames.RADIO.OPTION_CONTENT + " " +
	                                    ApiClassNames.INTERACTIVE, 
	                                
	                                style: {
	                                    cursor: "default",
	                                }}, 
	                            content
	                        ), 
	                        Exercises.cluesEnabled && this.props.showClues &&
	                            choice.checked &&
	                            React.createElement("div", {className: "perseus-radio-clue"}, 
	                                choice.clue
	                            )
	                    )
	                );

	            }, this)
	        );
	    },

	    checkOption: function(radioIndex, shouldBeChecked) {
	        var newChecked;
	        if (this.props.multipleSelect) {
	            // When multipleSelect is on, clicking an index toggles the
	            // selection of just that index.
	            newChecked = _.map(this.props.choices, function(choice, i)  {
	                return (i === radioIndex) ? shouldBeChecked : choice.checked;
	            });
	        } else {
	            // When multipleSelect is turned off we always unselect everything
	            // that wasn't clicked.
	            newChecked = _.map(this.props.choices, function(choice, i)  {
	                return i === radioIndex && shouldBeChecked;
	            });
	        }

	        // We send just the array of [true/false] checked values here;
	        // onCheckedChange reconstructs the new choices to send to
	        // this.props.onChange
	        this.props.onCheckedChange(newChecked);
	    },

	    focus: function(i) {
	        this.refs["radio" + (i || 0)].getDOMNode().focus();
	        return true;
	    }
	});

	var Radio = React.createClass({displayName: 'Radio',
	    getDefaultProps: function() {
	        return {
	            choices: [{}],
	            displayCount: null,
	            multipleSelect: false,
	            deselectEnabled: false,
	        };
	    },

	    getInitialState: function() {
	        return {
	            showClues: false
	        };
	    },

	    render: function() {
	        var choices = this.props.choices;
	        var values = this.props.values || _.map(choices, function()  {return false;});
	        var revealNoneOfTheAbove = this._shouldRevealNoneOfTheAbove(choices,
	                                                                    values);
	        choices = _.map(choices, function(choice, i)  {
	            var content;
	            if (choice.isNoneOfTheAbove && !revealNoneOfTheAbove) {
	                content = "None of the above";
	            } else {
	                content = choice.content;
	            }
	            return {
	                // We need to make a copy, which _.pick does
	                content: this._renderRenderer(content),
	                checked: values[i],
	                clue: this._renderRenderer(choice.clue),
	            };
	        }.bind(this));
	        choices = this.enforceOrdering(choices);

	        return React.createElement(BaseRadio, {
	            ref: "baseRadio", 
	            labelWrap: true, 
	            onePerLine: this.props.onePerLine, 
	            multipleSelect: this.props.multipleSelect, 
	            showClues: this.state.showClues, 
	            choices: choices.map(function(choice) {
	                return _.pick(choice, "content", "checked", "clue");
	            }), 
	            onCheckedChange: this.onCheckedChange, 
	            reviewModeRubric: this.props.reviewModeRubric, 
	            deselectEnabled: this.props.deselectEnabled, 
	            apiOptions: this.props.apiOptions});
	    },

	    _renderRenderer: function(content) {
	        content = content || "";

	        var nextPassageRefId = 1;
	        var widgets = {};

	        var modContent = content.replace(
	            /\{\{passage-ref (\d+) (\d+)\}\}/g,
	            function(match, passageNum, refNum)  {
	                var widgetId = "passage-ref " + nextPassageRefId;
	                nextPassageRefId++;

	                widgets[widgetId] = {
	                    type: "passage-ref",
	                    graded: false,
	                    options: {
	                        passageNumber: parseInt(passageNum),
	                        referenceNumber: parseInt(refNum),
	                    },
	                    version: PassageRef.version
	                };

	                return "[[" + Util.snowman + " " + widgetId + "]]";
	            }
	        );

	        // alwaysUpdate={true} so that passage-refs interwidgets
	        // get called when the outer passage updates the renderer
	        // TODO(aria): This is really hacky
	        return React.createElement(Renderer, {
	                content: modContent, 
	                widgets: widgets, 
	                interWidgets: this._interWidgets, 
	                alwaysUpdate: true});
	    },

	    _interWidgets: function(filterCriterion, localResults) {
	        // If local results are not found, forward interwidgets
	        // calls to our parent renderer.
	        // For passage-refs to communicate with their passages.
	        if (localResults.length) {
	            return localResults;
	        } else {
	            return this.props.interWidgets(filterCriterion);
	        }
	    },

	    _shouldRevealNoneOfTheAbove: function(choices, values) {
	        // We reveal when 'None of the above' is the correct choice
	        // and the entire question is completed. If 'None of the above' isn't
	        // selected and the question is completed, then it's the wrong choice
	        // and not worth revealing.
	        var noneOfTheAboveSelected = _.any(choices, function(choice, i)  {
	            return choice.isNoneOfTheAbove && values[i];
	        });
	        return this.props.questionCompleted && this.props.noneOfTheAbove &&
	                    noneOfTheAboveSelected;
	    },

	    focus: function(i) {
	        return this.refs.baseRadio.focus(i);
	    },

	    onCheckedChange: function(checked) {
	        this.setState({showClues: false});
	        this.props.onChange({
	            values: checked
	        });
	    },

	    getUserInput: function() {
	        // Return checked inputs in the form {values: [bool]}. (Dear future
	        // timeline implementers: this used to be {value: i} before multiple
	        // select was added)
	        if (this.props.values) {
	            var noneOfTheAboveIndex = null;
	            var noneOfTheAboveSelected = false;

	            var values = this.props.values.slice();

	            for (var i = 0; i < this.props.values.length; i++) {
	                var index = this.props.choices[i].originalIndex;
	                values[index] = this.props.values[i];

	                if (this.props.choices[i].isNoneOfTheAbove) {
	                    noneOfTheAboveIndex = index;

	                    if (values[i]) {
	                        noneOfTheAboveSelected = true;
	                    }
	                }
	            }

	            return _.extend({}, this.props, {
	                values: values,
	                noneOfTheAboveIndex: noneOfTheAboveIndex,
	                noneOfTheAboveSelected: noneOfTheAboveSelected
	              }
	            );
	        } else {
	            // Nothing checked
	            return {
	                values: _.map(this.props.choices, function()  {return false;})
	            };
	        }
	    },

	    simpleValidate: function(rubric) {
	        this.setState({showClues: true});
	        return Radio.validate(this.getUserInput(), rubric);
	    },

	    enforceOrdering: function(choices) {
	        var content = _.pluck(choices, "content");
	        if (_.isEqual(content, [$._("False"), $._("True")]) ||
	            _.isEqual(content, [$._("No"), $._("Yes")])) {
	            return ([choices[1]]).concat([choices[0]]);
	        }
	        return choices;
	    },

	    statics: {
	        displayMode: "block"
	    }
	});

	_.extend(Radio, {
	    validate: function(state, rubric) {
	        var numSelected = _.reduce(state.values, function(sum, selected) {
	            return sum + ((selected) ? 1 : 0); }
	        , 0);

	        if (numSelected === 0) {
	            return {
	                type: "invalid",
	                message: null
	            };
	        // If NOTA and some other answer are checked, ...
	        } else if (state.noneOfTheAboveSelected && numSelected > 1) {
	            return {
	                type: "invalid",
	                message: $._("'None of the above' may not be selected " +
	                                    "when other answers are selected.")
	             };
	        } else {
	            /* jshint -W018 */
	            var correct = _.all(state.values, function(selected, i) {
	                var isCorrect;
	                if (state.noneOfTheAboveIndex === i) {
	                    isCorrect = _.all(rubric.choices, function(choice, j) {
	                        return i === j || !choice.correct;
	                    });
	                } else {
	                    isCorrect = !!rubric.choices[i].correct;
	                }
	                return isCorrect === selected;
	            });
	            /* jshint +W018 */

	            return {
	                type: "points",
	                earned: correct ? 1 : 0,
	                total: 1,
	                message: null
	            };
	        }
	    }
	});

	var RadioEditor = React.createClass({displayName: 'RadioEditor',
	    mixins: [Changeable],

	    propTypes: {
	        choices: React.PropTypes.arrayOf(React.PropTypes.shape({
	            content: React.PropTypes.string,
	            clue: React.PropTypes.string,
	            correct: React.PropTypes.bool
	        })),
	        displayCount: React.PropTypes.number,
	        randomize: React.PropTypes.bool,
	        noneOfTheAbove: React.PropTypes.bool,
	        multipleSelect: React.PropTypes.bool,
	        onePerLine: React.PropTypes.bool,
	        deselectEnabled: React.PropTypes.bool,
	    },

	    getDefaultProps: function() {
	        return {
	            choices: [{}, {}],
	            displayCount: null,
	            randomize: false,
	            noneOfTheAbove: false,
	            multipleSelect: false,
	            onePerLine: true,
	            deselectEnabled: false,
	        };
	    },

	    render: function() {
	        return React.createElement("div", null, 
	            React.createElement("div", {className: "perseus-widget-row"}, 

	                React.createElement("div", null, 
	                    React.createElement("div", {className: "perseus-widget-left-col"}, 
	                        React.createElement(PropCheckBox, {label: "One answer per line", 
	                                      labelAlignment: "right", 
	                                      onePerLine: this.props.onePerLine, 
	                                      onChange: this.props.onChange})
	                    ), 
	                    React.createElement(InfoTip, null, 
	                        React.createElement("p", null, 
	                            "Use one answer per line unless your question has" + ' ' +
	                            "images that might cause the answers to go off the" + ' ' +
	                            "page."
	                        )
	                    )
	                ), 

	                React.createElement("div", {className: "perseus-widget-left-col"}, 
	                    React.createElement(PropCheckBox, {label: "Multiple selections", 
	                                  labelAlignment: "right", 
	                                  multipleSelect: this.props.multipleSelect, 
	                                  onChange: this.onMultipleSelectChange})
	                ), 

	                React.createElement("div", {className: "perseus-widget-right-col"}, 
	                    React.createElement(PropCheckBox, {label: "Randomize order", 
	                                  labelAlignment: "right", 
	                                  randomize: this.props.randomize, 
	                                  onChange: this.props.onChange})
	                ), 
	                React.createElement("div", {className: "perseus-widget-left-col"}, 
	                    React.createElement(PropCheckBox, {label: "Auto-none of the above", 
	                                  labelAlignment: "right", 
	                                  noneOfTheAbove: this.props.noneOfTheAbove, 
	                                  onChange: this.props.onChange})
	                ), 
	                React.createElement("div", {className: "perseus-widget-right-col"}, 
	                    React.createElement(PropCheckBox, {label: "Radio deselect enabled", 
	                                  labelAlignment: "right", 
	                                  deselectEnabled: this.props.deselectEnabled, 
	                                  onChange: this.props.onChange})
	                )
	            ), 

	            React.createElement(BaseRadio, {
	                ref: "baseRadio", 
	                multipleSelect: this.props.multipleSelect, 
	                onePerLine: true, 
	                labelWrap: false, 
	                apiOptions: this.props.apiOptions, 
	                choices: this.props.choices.map(function(choice, i) {
	                    var checkedClass = choice.correct ?
	                        "correct" :
	                        "incorrect";
	                    var editor = React.createElement(Editor, {
	                        ref: "editor" + i, 
	                        content: choice.content || "", 
	                        widgetEnabled: false, 
	                        placeholder: "Type a choice here...", 
	                        onChange: function(newProps)  {
	                            if ("content" in newProps) {
	                                this.onContentChange(i, newProps.content);
	                            }
	                        }.bind(this)}
	                    );
	                    var clueEditor = React.createElement(Editor, {
	                        ref: "clue-editor-" + i, 
	                        content: choice.clue || "", 
	                        widgetEnabled: false, 
	                        placeholder: $._("Why is this choice " +
	                            checkedClass + "?"), 
	                        onChange: function(newProps)  {
	                            if ("content" in newProps) {
	                                this.onClueChange(i, newProps.content);
	                            }
	                        }.bind(this)}
	                    );
	                    var deleteLink = React.createElement("a", {href: "#", 
	                            className: "simple-button orange delete-choice", 
	                            title: "Remove this choice", 
	                            onClick: this.onDelete.bind(this, i)}, 
	                        React.createElement("span", {className: "icon-trash"})
	                    );
	                    return {
	                        content: React.createElement("div", {className: "choice-clue-editors"}, 
	                            React.createElement("div", {className: "choice-editor " + checkedClass}, 
	                                editor
	                            ), 
	                            /* TODO(eater): Remove this condition after clues
	                                            are fully launched. */
	                            (!window.KA || window.KA.allowEditingClues) &&
	                                React.createElement("div", {className: "clue-editor"}, 
	                                    clueEditor
	                                ), 
	                            
	                            this.props.choices.length >= 2 && deleteLink
	                        ),
	                        checked: choice.correct
	                    };
	                }, this), 
	                onCheckedChange: this.onCheckedChange}), 

	            React.createElement("div", {className: "add-choice-container"}, 
	                React.createElement("a", {href: "#", className: "simple-button orange", 
	                        onClick: this.addChoice}, 
	                    React.createElement("span", {className: "icon-plus"}), 
	                    ' ', "Add a choice", ' '
	                )
	            )

	        );
	    },

	    onMultipleSelectChange: function(allowMultiple) {
	        allowMultiple = allowMultiple.multipleSelect;

	        var numSelected = _.reduce(this.props.choices,
	                function(memo, choice) {
	            return choice.correct ? memo + 1 : memo;
	        }, 0);

	        if (!allowMultiple && numSelected > 1) {
	            var choices = _.map(this.props.choices, function(choice) {
	                return _.defaults({
	                    correct: false
	                }, choice);
	            });
	            this.props.onChange({
	                multipleSelect: allowMultiple,
	                choices: choices
	            });

	        } else {
	            this.props.onChange({
	                multipleSelect: allowMultiple
	            });
	        }
	    },

	    onCheckedChange: function(checked) {
	        var choices = _.map(this.props.choices, function(choice, i) {
	            return _.extend({}, choice, {correct: checked[i]});
	        });
	        this.props.onChange({choices: choices});
	    },

	    onContentChange: function(choiceIndex, newContent) {
	        var choices = this.props.choices.slice();
	        choices[choiceIndex] = _.extend({}, choices[choiceIndex], {
	            content: newContent
	        });
	        this.props.onChange({choices: choices});
	    },

	    onClueChange: function(choiceIndex, newClue) {
	        var choices = this.props.choices.slice();
	        choices[choiceIndex] = _.extend({}, choices[choiceIndex], {
	            clue: newClue
	        });
	        if (newClue === "") {
	            delete choices[choiceIndex].clue;
	        }
	        this.props.onChange({choices: choices});
	    },

	    onDelete: function(choiceIndex, e) {
	        e.preventDefault();
	        var choices = this.props.choices.slice();
	        choices.splice(choiceIndex, 1);
	        this.props.onChange({choices: choices});
	    },

	    addChoice: function(e) {
	        e.preventDefault();

	        var choices = this.props.choices;
	        this.props.onChange({choices: choices.concat([{}])}, function()  {
	            this.refs["editor" + choices.length].focus();
	        }.bind(this));
	    },

	    setDisplayCount: function(num){
	        this.props.onChange({displayCount: num});
	    },

	    focus: function() {
	        this.refs.editor0.focus();
	        return true;
	    },

	    getSaveWarnings: function() {
	        if (!_.some(_.pluck(this.props.choices, "correct"))) {
	            return ["Warning: No choice is marked as correct."];
	        }
	        return [];
	    },

	    serialize: function() {
	        return _.pick(this.props, "choices", "randomize",
	            "multipleSelect", "displayCount", "noneOfTheAbove", "onePerLine",
	            "deselectEnabled");
	    }
	});

	var choiceTransform = function(editorProps, problemNum)  {

	    var randomize = function(array) {
	        if (editorProps.randomize) {
	            return shuffle(array, problemNum);
	        } else {
	            return array;
	        }
	    };

	    var addNoneOfAbove = function(array) {
	        // Pick a random choice to replace with 'None of the above'
	        if (!editorProps.randomize && editorProps.noneOfTheAbove) {
	            // Seed RNG with problemNum
	            var rand = seededRNG(problemNum)();
	            var randomIndex = Math.floor(rand * array.length);
	            var itemToBeReplaced = array[randomIndex];

	            // Shift array left so that 'None of the above' is last
	            array.splice(randomIndex, 1);
	            array.push(itemToBeReplaced);
	        }

	        array[array.length - 1].isNoneOfTheAbove = editorProps.noneOfTheAbove;
	        return array;
	    };

	    // Add meta-information to choices
	    var choices = editorProps.choices.slice();
	    choices = _.map(choices, function(choice, i) {
	        return _.extend({}, _.omit(choice, "correct"),
	            { originalIndex: i, isNoneOfTheAbove: false }
	        );
	    });

	    // Randomize and add 'None of the above'
	    choices = addNoneOfAbove(randomize(choices));

	    editorProps = _.extend({}, editorProps, { choices: choices });
	    return _.pick(editorProps, "choices", "noneOfTheAbove", "onePerLine",
	        "multipleSelect", "correctAnswer", "deselectEnabled");
	};

	module.exports = {
	    name: "radio",
	    displayName: "Multiple choice",
	    widget: Radio,
	    editor: RadioEditor,
	    transform: choiceTransform
	};


/***/ },
/* 39 */
/***/ function(module, exports, __webpack_require__) {

	var ApiOptions = __webpack_require__(12).Options;
	var Changeable   = __webpack_require__(66);
	var Editor = __webpack_require__(7);
	var Renderer = __webpack_require__(10);
	var Util = __webpack_require__(3);

	var StepControlButton = React.createClass({displayName: 'StepControlButton',
	    render: function() {
	        return React.createElement("a", {
	                href: "#", 
	                className: 
	                    "step-control-button " +
	                    "simple-button " +
	                    "simple-button--small " +
	                    "orange", 
	                
	                onClick: function(e)  {
	                    e.preventDefault();
	                    this.props.onClick();
	                }.bind(this)}, 
	            React.createElement("span", {className: this.props.icon})
	        );
	    }
	});

	var Sequence = React.createClass({displayName: 'Sequence',
	    mixins: [Changeable],

	    propTypes: {
	        json:  React.PropTypes.arrayOf(React.PropTypes.shape({
	            content: React.PropTypes.string,
	            widgets: React.PropTypes.object,
	            images: React.PropTypes.object
	        })),
	        apiOptions: ApiOptions.propTypes
	    },

	    getDefaultProps: function() {
	        return {
	            json: [{
	                content: "",
	                widgets: {},
	                images: {},
	            }]
	        };
	    },

	    getInitialState: function() {
	        return {
	            visible: 1
	        };
	    },

	    shouldComponentUpdate: function(nextProps, nextState) {
	        return nextProps !== this.props || nextState !== this.state;
	    },

	    render: function() {
	        var icon = React.createElement("div", {className: "icon-ok", style: {color: "green"}});

	        var content = _.chain(this.props.json)
	                .first(this.state.visible)
	                .map(function(step, i)  {
	                    return "[[" + Util.snowman + " group " + i + "]]";
	                })
	                .join("\n\n")
	                .value();
	        
	        var widgets = {};
	        _.each(this.props.json, function(step, i)  {
	            var widgetId = "group " + i;
	            widgets[widgetId] = {
	                type: "group",
	                graded: true,
	                version: {major: 0, minor: 0},
	                options: _.extend({}, step, {
	                    onInteractWithWidget: _.partial(
	                            this._handleInteraction, i),
	                    icon: i < this.state.visible - 1 ? icon : null
	                })
	            };
	        }.bind(this));

	        return React.createElement("div", {className: "perseus-sequence"}, 
	            React.createElement(Renderer, {
	                ref: "renderer", 
	                content: content, 
	                widgets: widgets, 
	                apiOptions: this.props.apiOptions, 
	                enabledFeatures: this.props.enabledFeatures})
	        );
	    },

	    _handleInteraction: function(step) {
	        if (step === this.state.visible - 1) {
	            var widget = this.refs.renderer.getWidgetInstance("group " + step);
	            var score = widget.simpleValidate();

	            if (score.type === "points" && score.total === score.earned) {
	                this.setState({
	                    visible: this.state.visible + 1
	                });
	            }
	        }
	    },

	    statics: {
	        displayMode: "block"
	    }
	});


	var SequenceEditor = React.createClass({displayName: 'SequenceEditor',
	    propTypes: {
	        json:  React.PropTypes.arrayOf(React.PropTypes.shape({
	            content: React.PropTypes.string,
	            widgets: React.PropTypes.object,
	            images: React.PropTypes.object
	        })),
	        apiOptions: ApiOptions.propTypes,
	        onChange: React.PropTypes.func.isRequired,
	    },

	    getDefaultProps: function() {
	        return {
	            json: [{
	                content: "",
	                widgets: {},
	                images: {},
	            }]
	        };
	    },

	    render: function() {
	        return React.createElement("div", {className: "perseus-sequence-editor"}, 
	            _.map(this.props.json, function(json, i)  {
	                return React.createElement("div", null, 
	                    "Step ", i+1, 
	                    React.createElement("div", {style: {
	                        display: "inline-block",
	                        float: "right"
	                    }}, 
	                        (i + 1 < this.props.json.length) &&
	                            React.createElement(StepControlButton, {
	                                icon: "icon-circle-arrow-down", 
	                                onClick: function()  {
	                                    this._handleMoveStepLater(i);
	                                }.bind(this)}), 
	                        
	                        (i > 0) &&
	                            React.createElement(StepControlButton, {
	                                icon: "icon-circle-arrow-up", 
	                                onClick: function()  {
	                                    this._handleMoveStepEarlier(i);
	                                }.bind(this)}), 
	                        
	                        React.createElement(StepControlButton, {
	                            icon: "icon-trash", 
	                            onClick: function()  {
	                                var msg = "Are you sure you " +
	                                    "want to remove step " +
	                                    (i + 1) + "?";
	                                if (confirm(msg)) {
	                                    this._handleRemoveStep(i);
	                                }
	                            }.bind(this)}), 
	                        React.createElement(StepControlButton, {
	                            icon: "icon-plus", 
	                            onClick: function()  {
	                                this._handleAddStepAfter(i);
	                            }.bind(this)})
	                    ), 
	                    React.createElement(Editor, {
	                        ref: "editor" + i, 
	                        content: json.content, 
	                        widgets: json.widgets, 
	                        images: json.images, 
	                        widgetEnabled: true, 
	                        immutableWidgets: false, 
	                        onChange: _.partial(this._handleEditorChange, i)})
	                );
	            }.bind(this))
	        );
	    },

	    _handleEditorChange: function(i, newProps) {
	        var steps = _.clone(this.props.json);
	        steps[i] = _.extend({}, steps[i], newProps);
	        this.props.onChange({json: steps});
	    },

	    serialize: function() {
	        return {
	            json: _.times(this.props.json.length, function(i)  {
	                return this.refs["editor" + i].serialize();
	            }.bind(this))
	        };
	    },

	    _handleMoveStepEarlier: function(i) {
	        if (i === 0) {
	            return;
	        }
	        var steps = _.clone(this.props.json);
	        var step = steps[i];
	        steps.splice(i, 1);
	        steps.splice(i - 1, 0, step);
	        this.props.onChange({
	            json: steps
	        });
	    },

	    _handleMoveStepLater: function(i) {
	        var steps = _.clone(this.props.json);
	        if (i + 1 === steps.length) {
	            return;
	        }
	        var step = steps[i];
	        steps.splice(i, 1);
	        steps.splice(i + 1, 0, step);
	        this.props.onChange({
	            json: steps
	        });
	    },

	    _handleAddStepAfter: function(i) {
	        // We do a full serialization here because we
	        // might be copying widgets:
	        var steps = _.clone(this.props.json);
	        // Here we do magic to allow you to copy-paste
	        // things from the previous section into the new
	        // section while preserving widgets.
	        // To enable this, we preserve the widgets
	        // object for the new section, but wipe out
	        // the content.
	        var newStep = (i >= 0) ? {
	            widgets: steps[i].widgets
	        } : {};
	        steps.splice(i + 1, 0, newStep);
	        this.props.onChange({
	            json: steps
	        });
	    },

	    _handleRemoveStep: function(i) {
	        var steps = _.clone(this.props.json);
	        steps.splice(i, 1);
	        this.props.onChange({
	            json: steps
	        });
	    },
	});

	module.exports = {
	    name: "sequence",
	    displayName: "Sequence",
	    widget: Sequence,
	    editor: SequenceEditor,
	    hidden: true,
	};


/***/ },
/* 40 */
/***/ function(module, exports, __webpack_require__) {

	var InfoTip      = __webpack_require__(69);
	var _ = __webpack_require__(60);

	var Changeable   = __webpack_require__(66);
	var EditorJsonify = __webpack_require__(67);

	var ApiOptions = __webpack_require__(12).Options;
	var assert = __webpack_require__(89).assert;

	var Graphie = __webpack_require__(72);
	var $__0=
	    
	    
	    
	    
	    
	    
	    
	  Graphie,Path=$__0.Path,Arc=$__0.Arc,Circle=$__0.Circle,Label=$__0.Label,Line=$__0.Line,MovablePoint=$__0.MovablePoint,MovableLine=$__0.MovableLine;
	var NumberInput  = __webpack_require__(85);
	var MathOutput  = __webpack_require__(88);
	var seededRNG    = __webpack_require__(3).seededRNG;
	var Util         = __webpack_require__(3);
	var knumber      = __webpack_require__(103).number;

	var defaultBoxSize = 400;
	var maxSampleSize = 1000;
	var maxTrials = 5000;

	var Histogram = React.createClass({displayName: 'Histogram',
	    propTypes: {
	        data: React.PropTypes.arrayOf(React.PropTypes.number),
	        xAxisLabel: React.PropTypes.string,
	        yAxisLabel: React.PropTypes.string,
	        box: React.PropTypes.arrayOf(React.PropTypes.number)
	    },

	    getDefaultProps: function() {
	        return {
	            data: null,
	            xAxisLabel: "Proportion (%)",
	            yAxisLabel: "Number of times seen",
	            box: [defaultBoxSize, defaultBoxSize]
	        };
	    },

	    getInitialState: function() {
	        return {
	            threshold: this._getInitialThreshold(this._range())
	        };
	    },

	    componentWillReceiveProps: function(nextProps) {
	        // Reset the threshold if the range has changed
	        var oldRange = this._range();
	        var nextRange = this._range(nextProps);
	        if (!Util.deepEq(oldRange, nextRange)) {
	            this.setState({
	                threshold: this._getInitialThreshold(nextRange)
	            });
	        }
	    },

	    /* Renders the vertical line that users can drag across the histogram. */
	    _renderThresholdLine: function() {
	        // Recall the the y-range goes from [-1, yMax] to allow for ticks on
	        // the x-axis.
	        var yRange = [0, this._range()[1][1]];
	        var coords = _.map(yRange, function(y)  {return [this.state.threshold, y];}.bind(this));

	        // Returns an inivisble, placeholder coord that anchors the line
	        var invisiblePointForCoord = function(coord)  {
	            return React.createElement(MovablePoint, {
	                static: true, 
	                coord: coord, 
	                normalStyle: {stroke: "none", fill: "none"}});
	        };

	        return React.createElement(MovableLine, {onMove: this.handleMouseInteraction}, 
	            _.map(coords, invisiblePointForCoord)
	        );
	    },

	    /* Renders the shaded circle in the top right. */
	    _renderCircle: function() {
	        var data = this.props.data;

	        // Get proportion of results below threshold
	        var total = _.reduce(data, function(sum, next)  {
	            return sum + next;
	        }, 0);
	        var numBelow = _.reduce(data, function(sum, next, i)  {
	            if (this.state.threshold != null &&
	                    i <= this.state.threshold) {
	                return sum + next;
	            } else {
	                return sum;
	            }
	        }.bind(this), 0);
	        var proportionBelow = numBelow / total;

	        // This is a hack around the arc taking angles modulo 360.
	        // TODO(charlie): Find a better way around this.
	        var epsilon = 1e-5;
	        var radius = 20;
	        var center = [this.props.box[0] - 1.5 * radius, 1.5 * radius];

	        // Plot little circle
	        var plotBelowCircle = function()  {
	            var options = {
	                center: center,
	                radius: radius,
	                startAngle: 0,
	                endAngle: (proportionBelow < 1) ? 360 * proportionBelow
	                                                : 360 - epsilon,
	                sector: (proportionBelow !== 1),
	                unscaled: true,
	                style: {
	                    fill: KhanUtil.LIGHT_RED,
	                    stroke: KhanUtil.RED
	                }
	            };

	            return Arc(options);
	        };
	        var plotAboveCircle = function()  {
	            var options = {
	                center: center,
	                radius: radius,
	                startAngle: (proportionBelow > 0) ? 360 * proportionBelow
	                                                  : epsilon,
	                endAngle: 360,
	                sector: (proportionBelow !== 0),
	                unscaled: true,
	                style: {
	                    fill: KhanUtil.LIGHT_BLUE,
	                    stroke: KhanUtil.BLUE
	                },
	            };

	            return Arc(options);
	        };

	        // Plot the label below the circle
	        var xRange = this._range()[0];
	        var formattedThreshold = Math.min(
	            Math.max(this.state.threshold, xRange[0]), xRange[1]).toFixed(2);
	        var plotLabel = function()  {
	            var options = {
	                coord: [center[0], center[1] + 1.5 * radius],
	                text: numBelow + " of " + total + " results below " +
	                    formattedThreshold + "%",
	                direction: "center",
	                tex: false,
	                unscaled: true,
	                style: {
	                    fontSize: "12px"
	                }
	            };
	            return Label(options);
	        };

	        return [
	            proportionBelow > 0 && plotBelowCircle(),
	            proportionBelow < 1 && plotAboveCircle(),
	            plotLabel()
	        ];
	    },

	    /* Renders the actual bars of the histogram. */
	    _renderData: function() {
	        var data = this.props.data;
	        var range = this._range();

	        // Plot bars
	        var barWidth = 1;
	        var pathForData = function(count, i)  {
	            // Avoid plotting bars of height 0, else you get a thick blue line
	            // over the x-axis. We don't filter these out of the data passed in
	            // to this function, however, to preserve absolute indices.
	            if (!count) {
	                return;
	            }

	            var isBelow = this.state.threshold != null &&
	                    i <= this.state.threshold;
	            var style = {
	                fill: (isBelow) ?  KhanUtil.LIGHT_RED : KhanUtil.LIGHT_BLUE,
	                stroke: (isBelow) ? KhanUtil.RED : KhanUtil.BLUE
	            };
	            var coords = [
	                [i, 0],
	                [i, count],
	                [i + barWidth, count],
	                [i + barWidth, 0]
	            ];
	            return React.createElement(Path, {coords: coords, style: style});
	        }.bind(this);

	        return _.map(data, pathForData);
	    },

	    render: function() {
	        var data = this.props.data;
	        var range = this._range();

	        var options = {
	            xAxisLabel: this.props.xAxisLabel,
	            yAxisLabel: this.props.yAxisLabel,
	            box: this.props.box,
	            range: range,
	            data: data,
	            scale: [Util.scaleFromExtent(range[0], this.props.box[0]),
	                        Util.scaleFromExtent(range[1], this.props.box[1])]
	        };

	        var axisStyle = {
	            stroke: "#000",
	            strokeWidth: 1,
	            opacity: 1.0
	        };
	        var origin = [range[0][0], 0];
	        var bottomRight = [range[0][1], 0];

	        return React.createElement(Graphie, {box: options.box, 
	                        range: options.range, 
	                        options: options, 
	                        setup: this._setupGraphie, 
	                        onMouseMove: this.handleMouseInteraction, 
	                        onMouseDown: this.handleMouseInteraction}, 
	            React.createElement(Line, {start: origin, end: bottomRight, style: axisStyle}), 
	            /* Only plot these cool extra features if there's data */
	            data && [
	                this._renderData(),
	                this._renderCircle(),
	                this._renderThresholdLine()
	            ]
	        );
	    },

	    _setupGraphie: function(graphie, options) {
	        var data = options.data;
	        var range = options.range;
	        var scale = options.scale;

	        /* Plot the bars that run parallel to the x-axis. */
	        var xWidth = range[0][1] - range[0][0];
	        var yWidth = range[1][1] - 0;

	        var maxYAxisEntities = 20;
	        var ySkip = Math.ceil(yWidth / maxYAxisEntities);
	        _.each(_.range(0, range[1][1], ySkip), function(y)  {

	            // If there's no data, we don't label the axes
	            if (data) {
	                graphie.label(
	                    [range[0][0], y],
	                    KhanUtil.roundToApprox(y, 2),
	                    "left",
	                    /* isTeX */ true /* for the \approx symbol */
	                );
	            }

	            graphie.line([range[0][0], y], [range[0][1], y], {
	                stroke: "#000",
	                strokeWidth: 1,
	                opacity: 0.3
	            });
	        });


	        // If there's no data, we don't label the x-axis at all
	        if (data) {
	            // Plot the labels below the bars
	            var maxXAxisEntities = 15;
	            var xSkip = Math.ceil(xWidth / maxXAxisEntities);
	            _.each(_.range(range[0][0], range[0][1], xSkip), function(x)  {
	                graphie.label([x, 0], knumber.round(x, 2), "below", true);

	                var tickHeight = 8;
	                graphie.line([x, 0], [x, - tickHeight / scale[1]], {
	                        stroke: "#000",
	                        strokeWidth: 1
	                    }
	                );
	            });
	        }

	        // Add y axis (x axis is added later to overlap the bars)
	        var axisStyle = {
	            stroke: "#000",
	            strokeWidth: 2,
	            opacity: 1.0
	        };
	        var origin = [range[0][0], 0];
	        var topLeft = [range[0][0], range[1][1]];
	        graphie.line(origin, topLeft, axisStyle);

	        // Add axis labels
	        var xMid = range[0][0] + (xWidth / 2);
	        var xOffset = (data) ? 25 : 0;
	        graphie.label([xMid, - xOffset / scale[1]],
	            options.xAxisLabel,
	            "below", false)
	            .css("font-weight", "bold");

	        var yMid = 0 + (yWidth / 2);
	        var yOffset = (data) ? 55 : 28;
	        graphie.label([range[0][0] - yOffset / scale[0], yMid],
	            options.yAxisLabel,
	            "center", false)
	            .css("font-weight", "bold")
	            .css("-webkit-transform", "rotate(-90deg)");
	    },

	    handleMouseInteraction: function(point) {
	        this.setState({
	            threshold: point[0]
	        });
	    },

	    /* Convenience functions that help calculate props based on other props. */
	    _range: function(props) {
	        var defaultRange = [[0, 100], [-1, 10]];
	        props = props || this.props;
	        return (props.data) ? this._getRangeForData(props.data) : defaultRange;
	    },

	    _getRangeForData: function(data) {
	        // Find first/last non-zero entry and add some padding
	        var padding = 10;
	        var firstIndex = _.indexOf(data, _.find(data, function(n)  {return n > 0;}));
	        var xMin = Math.max(0, firstIndex - padding);
	        var lastIndex = _.lastIndexOf(data, _.last(
	            _.filter(data, function(n)  {return n > 0;})));
	        var xMax = Math.min(100 + 1, (lastIndex + 1) + padding);

	        // The y-axis is bounded above by largest value, and below by 0.
	        // However, the 'range' of the y-axis goes as low as -1 to allow
	        // Graphie to draw ticks on the x-Axis that extend vertically below
	        // y = 0.
	        var yMin = -1;
	        var yMax = _.max(data);

	        return [[xMin, xMax], [yMin, yMax]];
	    },

	    _getInitialThreshold: function(range) {
	        // We pick a pretty-looking threshold, 1/3 of the way along the axis
	        var xRange = range[0];
	        return xRange[0] + (xRange[1] - xRange[0]) / 3;
	    }
	});

	var Simulator = React.createClass({displayName: 'Simulator',
	    mixins: [Changeable],

	    propTypes: {
	        data: React.PropTypes.arrayOf(React.PropTypes.number),
	        userProportion: React.PropTypes.number,
	        sampleSize: React.PropTypes.number,
	        numTrials: React.PropTypes.number,
	        randomSeed: React.PropTypes.number,
	        xAxisLabel: React.PropTypes.string,
	        yAxisLabel: React.PropTypes.string,
	        proportionLabel: React.PropTypes.string,
	        proportionOrPercentage: React.PropTypes.string,
	        apiOptions: ApiOptions.propTypes
	    },

	    getInitialState: function() {
	        return {
	            invalidInput: false
	        };
	    },

	    getDefaultProps: function() {
	        return {
	            data: null,
	            userProportion: null,
	            sampleSize: null,
	            numTrials: null,
	            randomSeed: 0,
	            xAxisLabel: "Proportion (%)",
	            yAxisLabel: "Number of times seen",
	            proportionLabel: "Underlying proportion",
	            proportionOrPercentage: "proportion",
	            apiOptions: ApiOptions.defaults
	        };
	    },

	    componentWillMount: function() {
	        if (this.props.randomSeed != null) {
	            this.generateNumber = Util.seededRNG(this.props.randomSeed);
	        }
	    },

	    componentWillReceiveProps: function(nextProps) {
	        if (nextProps.randomSeed !== this.props.randomSeed) {
	            this.generateNumber = Util.seededRNG(nextProps.randomSeed);
	        }
	    },

	    render: function() {
	        var inputStyle = {
	            marginLeft: "5px"
	        };

	        var highlight = "0px 0px 9px 2px rgba(255, 165, 0, 1)";
	        var highlightStyle = _.extend({}, inputStyle, {
	            WebkitBoxShadow: highlight,
	            MozBoxShadow: highlight,
	            boxShadow: highlight,
	            transition: "all 0.15s"
	        });
	        var unhighlightStyle = _.extend({}, inputStyle, {
	            transition: "all 0.15s"
	        });
	        var style = (this.state.invalidInput) ? highlightStyle
	                                              : unhighlightStyle;

	        var InputComponent = this.props.apiOptions.staticRender ? MathOutput
	                                                                : NumberInput;

	        var proportionInput = React.createElement("div", null, 
	            React.createElement(InputComponent, {
	                ref: "userProportion", 
	                style: style, 
	                value: this.calculateDisplayProportion(), 
	                checkValidity: this.checkProportionValidity, 
	                onChange: this.handleUserProportionChange, 
	                onFocus: function()  {return this.props.onFocus(["userProportion"]);}.bind(this), 
	                onBlur: function()  {return this.props.onBlur(["userProportion"]);}.bind(this)}), 
	            React.createElement(InfoTip, null, 
	                React.createElement("p", null, "This controls the proportion or percentage that will be used" + ' ' +
	                   "in your simulation.")
	            )
	        );

	        var sampleSizeInput = React.createElement("div", null, 
	            React.createElement(InputComponent, {
	                ref: "sampleSize", 
	                style: style, 
	                value: this.props.sampleSize, 
	                checkValidity: function(val)  {return val >= 0;}, 
	                onChange: this.handleSampleSizeChange, 
	                onFocus: function()  {return this.props.onFocus(["sampleSize"]);}.bind(this), 
	                onBlur: function()  {return this.props.onBlur(["sampleSize"]);}.bind(this)}), 
	            React.createElement(InfoTip, null, 
	                React.createElement("p", null, "This controls the sample size that will be used in your" + ' ' +
	                   "simulation. For example, if you set this to 100, then for" + ' ' +
	                   "each trial, responses from 100 participants will be" + ' ' +
	                   "simulated.")
	            )
	        );

	        var numTrialsDisplay = React.createElement("div", {style: {float: "right"}}, 
	            React.createElement("b", null, this.props.numTrials), 
	            React.createElement(InfoTip, null, 
	                React.createElement("p", null, "This is the number of trials used in the simulation. For" + ' ' +
	                   "example, if set to 50, then the survey will be conducted 50" + ' ' +
	                   "times.")
	            )
	        );

	        // Generates a table from a set of titles and values.
	        var generateTable = function(contents)  {
	            var header = React.createElement("thead", null, 
	                React.createElement("tr", null, 
	                    React.createElement("th", null, "Parameter"), 
	                    React.createElement("th", null, "Value")
	                )
	            );

	            var body = React.createElement("tbody", null, 
	                _.map(contents, function(row, i)  {
	                    return React.createElement("tr", {key: i}, 
	                        React.createElement("td", null, row.title), 
	                        React.createElement("td", null, row.value)
	                    );
	                })
	            );

	            return React.createElement("table", null, 
	                header, 
	                body
	            );
	        };

	        // Contents for the table to-be generated
	        var contents = [
	            {
	                title: this.props.proportionLabel + ":",
	                value: proportionInput,
	            },
	            {
	                title: "Sample size:",
	                value: sampleSizeInput
	            },
	            {
	                title: "Number of trials:",
	                value: numTrialsDisplay
	            }
	        ];

	        // The 'Run Simulation' button
	        var buttonStyle = {
	            margin: "20px 0"
	        };
	        var startButton = React.createElement("button", {
	                className: "simple-button", 
	                style: buttonStyle, 
	                onClick: this.handleRunSimulation}, 
	            $_(null, "Run simulation")
	        );

	        // When we plot data, ticks on the x-axis require some vertical padding
	        var histogramStyle = {
	            paddingBottom: (this.props.data) ? 40 : 0
	        };
	        var histogram = React.createElement("div", {style: histogramStyle}, 
	            React.createElement(Histogram, {data: this.props.data, 
	                       xAxisLabel: this.props.xAxisLabel, 
	                       yAxisLabel: this.props.yAxisLabel})
	        );

	        return React.createElement("div", null, 
	            generateTable(contents), 
	            startButton, 
	            histogram
	        );
	    },

	    calculateDisplayProportion: function() {
	        var userProportion = this.props.userProportion;

	        // If we want to display as a percentage, multiply proportion by 100.0.
	        if (this.props.proportionOrPercentage === "percentage") {
	            return Math.round(100 * userProportion);
	        } else {
	            return userProportion;
	        }
	    },

	    checkProportionValidity: function(value) {
	        return value >= 0.0 &&
	            (this.props.proportionOrPercentage === "proportion" &&
	                value <= 1.0) ||
	            (this.props.proportionOrPercentage === "percentage" &&
	                value <= 100.0);
	    },

	    handleUserProportionChange: function(value, cb) {
	        var userProportion;

	        // If "percentage" mode is enabled, user will have entered value as
	        // a percentage. However, we always store as a proportion, so we cast.
	        if (this.props.proportionOrPercentage === "percentage") {
	            userProportion = value / 100.0;
	        } else {
	            userProportion = value;
	        }

	        // If they entered a number, we may need to cap it
	        if (userProportion != null) {
	            userProportion = Math.min(1.0, Math.max(0.0, userProportion));
	        }
	        this.props.onChange({
	            userProportion: userProportion
	        }, cb);
	    },

	    handleSampleSizeChange: function(sampleSize, cb) {
	        if (sampleSize != null) {
	            sampleSize = Math.min(maxSampleSize,
	                Math.max(0, Math.floor(sampleSize)));
	        }
	        this.props.onChange({
	            sampleSize: sampleSize
	        }, cb);
	    },

	    handleRunSimulation: function() {
	        // If they haven't filled out a parameter field, highlight it.
	        if (this.props.numTrials == null ||
	                this.props.userProportion == null ||
	                this.props.sampleSize == null) {
	            this.setState({
	                invalidInput: true
	            });
	            return;
	        } else {
	            this.setState({
	                invalidInput: false
	            });
	        }
	        this.props.onChange({
	            data: this.generateData()
	        });
	    },

	    generateData: function(props) {
	        props = props || this.props;
	        var getSampleDistribution = function(sampleSize, numTrials, proportion)  {
	            var draw = function()  {
	                return this.generateNumber() < proportion;
	            }.bind(this);
	            var sampleDistribution = _.times(100 + 1, function()  {return 0;});
	            _.times(numTrials, function()  {
	                var results = _.times(sampleSize, draw);
	                var count = _.filter(results, _.identity).length;
	                var normalizedCount = Math.floor(100 * count / sampleSize);
	                sampleDistribution[normalizedCount]++;
	            });
	            return sampleDistribution;
	        }.bind(this);
	        return getSampleDistribution(props.sampleSize, props.numTrials,
	            props.userProportion);
	    },

	    /* InputPath API */
	    getInputPaths: function() {
	        return [["userProportion"], ["sampleSize"]];
	    },

	    focus: function() {
	        var path = _.head(this.getInputPaths());
	        this.focusInputPath(path);
	        return true;
	    },

	    focusInputPath: function(path) {
	        assert(path.length > 0);
	        var inputID = _.head(path);
	        var inputComponent = this.refs[inputID];
	        inputComponent.focus();
	    },

	    blurInputPath: function(path) {
	        assert(path.length > 0);
	        var inputID = _.head(path);
	        var inputComponent = this.refs[inputID];
	        inputComponent.blur();
	    },

	    getDOMNodeForPath: function(path) {
	        assert(path.length > 0);
	        var inputID = _.head(path);
	        return this.refs[inputID].getDOMNode();
	    },

	    getGrammarTypeForPath: function(path) {
	        assert(path.length > 0);
	        return "number";
	    },

	    setInputValue: function(path, newValue, cb) {
	        assert(path.length > 0);
	        var inputID = _.head(path);
	        var capitalizedID = inputID.charAt(0).toUpperCase() + inputID.slice(1);
	        var functionName = "handle" + capitalizedID + "Change";
	        this[functionName](newValue, cb);
	    },

	    getUserInput: function() {
	        return null;
	    },

	    simpleValidate: function(rubric) {
	        return Simulator.validate(this.getUserInput(), rubric);
	    },

	    statics: {
	        displayMode: "block"
	    }
	});

	_.extend(Simulator, {
	    validate: function(state, rubric) {
	        return {
	            type: "points",
	            earned: 0,
	            total: 0,
	            message: null
	        };
	    }
	});

	var SimulatorEditor = React.createClass({displayName: 'SimulatorEditor',
	    mixins: [Changeable, EditorJsonify],

	    propTypes: {
	        xAxisLabel: React.PropTypes.string,
	        yAxisLabel: React.PropTypes.string,
	        numTrials: React.PropTypes.number,
	        proportionLabel: React.PropTypes.string,
	        proportionOrPercentage: React.PropTypes.string
	    },

	    getDefaultProps: function() {
	        return {
	            xAxisLabel: "Proportion (%)",
	            yAxisLabel: "Number of times seen",
	            numTrials: 100,
	            proportionLabel: "Underlying proportion",
	            proportionOrPercentage: "proportion"
	        };
	    },

	    render: function() {
	        return React.createElement("div", {className: "perseus-widget-simulator"}, 
	            React.createElement("div", null, 
	                $_(null, "X-Axis Label"), ":", 
	                React.createElement("input", {
	                    type: "text", 
	                    className: "graph-settings-axis-label", 
	                    value: this.props.xAxisLabel, 
	                    onChange: _.partial(this.handleTargetValueChange,
	                        "xAxisLabel")})
	            ), 
	            React.createElement("div", null, 
	                $_(null, "Y-Axis Label"), ":", 
	                React.createElement("input", {
	                    type: "text", 
	                    className: "graph-settings-axis-label", 
	                    value: this.props.yAxisLabel, 
	                    onChange: _.partial(this.handleTargetValueChange,
	                        "yAxisLabel")})
	            ), 
	            React.createElement("div", null, 
	                $_(null, "\"True Proportion\" Label"), ":", 
	                React.createElement("input", {
	                    type: "text", 
	                    className: "graph-settings-axis-label", 
	                    value: this.props.proportionLabel, 
	                    onChange: _.partial(this.handleTargetValueChange,
	                        "proportionLabel")}), 
	                React.createElement(InfoTip, null, 
	                    React.createElement("p", null, "This text will be displayed next to the box in which" + ' ' +
	                        "the user enters the sample proportion for their" + ' ' +
	                        "simulation. For example, if your question is about" + ' ' +
	                        "surveying for approval ratings, you might want this to" + ' ' +
	                        "say \"Sample approval rating\".")
	                )
	            ), 
	            React.createElement("div", null, 
	                $_(null, "Proportion or Percentage"), ":", 
	                React.createElement("select", {
	                    className: "perseus-widget-dropdown", 
	                    value: this.props.proportionOrPercentage, 
	                    onChange: _.partial(this.handleTargetValueChange,
	                        "proportionOrPercentage")}, 
	                        React.createElement("option", {key: "proportion", value: "proportion"}, 
	                            "Proportion"
	                        ), 
	                        React.createElement("option", {key: "percentage", value: "percentage"}, 
	                            "Percentage"
	                        )
	                ), 
	                React.createElement(InfoTip, null, 
	                    React.createElement("p", null, "Do you want the user to describe their simulation in" + ' ' +
	                        "terms of a proportion or a percentage?")
	                )
	            ), 
	            React.createElement("div", null, 
	                $_(null, "Number of trials"), ":", 
	                React.createElement(NumberInput, {
	                    value: this.props.numTrials, 
	                    checkValidity: function(val)  {
	                        return val >= 0 && val <= maxTrials;
	                    }, 
	                    onChange: this.change("numTrials")}), 
	                React.createElement(InfoTip, null, 
	                    React.createElement("p", null, "This controls the number of trials used in the" + ' ' +
	                       "simulation. For example, if you set this to 50, then the" + ' ' +
	                       "survey will be conducted 50 times. Warning: setting" + ' ' +
	                       "this too high (i.e., greater than 5000 or so) will" + ' ' +
	                       "freeze the page.")
	                )
	            )
	        );
	    },

	    handleTargetValueChange: function(propName, e) {
	        this.change(propName, e.target.value);
	    }
	});

	var propTransform = function(editorProps)  {
	    var widgetProps = _.clone(editorProps);
	    widgetProps.randomSeed = editorProps.problemNum;
	    return widgetProps;
	};

	module.exports = {
	    name: "simulator",
	    displayName: "Simulator",
	    widget: Simulator,
	    editor: SimulatorEditor,
	    transform: propTransform
	};


/***/ },
/* 41 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);
	var _ = __webpack_require__(60);

	var Changeable = __webpack_require__(66);
	var EditorJsonify = __webpack_require__(67);

	var PerseusMarkdown = __webpack_require__(54);
	var mdParse = PerseusMarkdown.parse;
	var mdOutput = PerseusMarkdown.testOutput;

	var TextArea = React.createClass({displayName: 'TextArea',
	    render: function() {
	        return React.createElement("textarea", {
	            ref: "input", 
	            value: this.props.value || "", 
	            onChange: this.changeValue});
	    },

	    focus: function() {
	        this.refs.input.focus();
	        return true;
	    },

	    changeValue: function(e) {
	        // Translating from the js event e to the value
	        // of the textbox to send to onChange
	        this.props.onChange(e.target.value);
	    }
	});

	var SimpleMarkdownTester = React.createClass({displayName: 'SimpleMarkdownTester',
	    propTypes: {
	        value: React.PropTypes.string
	    },

	    getDefaultProps: function() {
	        return {
	            value: ""
	        };
	    },

	    mixins: [Changeable],

	    toJSON: function() {
	        return {};
	    },

	    render: function() {
	        var parsed = mdParse(this.props.value);
	        var output = mdOutput(parsed);
	        return React.createElement("div", null, 
	            output
	        );
	    },

	    /**
	     * Widgets that are focusable should add a focus method that returns
	     * true if focusing succeeded. The first such widget found will be
	     * focused on page load.
	     */
	    focus: function() {
	        this.refs.input.focus();
	        return true;
	    },

	    /**
	     * simpleValidate is called for grading. Rubric is the result of calling
	     * toJSON() on the editor that created this widget.
	     *
	     * Should return an object representing the grading result, such as
	     * {
	     *     type: "points",
	     *     earned: 1,
	     *     total: 1,
	     *     message: null
	     * }
	     */
	    simpleValidate: function(rubric) {
	        return SimpleMarkdownTester.validate(this.toJSON(), rubric);
	    },

	    statics: {
	        displayMode: "block"
	    }
	});


	/**
	 * This is the widget's grading function
	 */
	_.extend(SimpleMarkdownTester, {
	    /**
	     * simpleValidate generally defers to this function
	     *
	     * state is usually the result of toJSON on the widget
	     * rubric is the result of calling toJSON() on the editor
	     */
	    validate: function(state, rubric) {
	        return {
	            type: "points",
	            earned: 0,
	            total: 0,
	            message: null
	        };
	    }
	});


	/**
	 * This is the widget's editor. This is what shows up on the left side
	 * of the screen in test.html. Only the question writer sees this.
	 */
	var SimpleMarkdownTesterEditor = React.createClass({displayName: 'SimpleMarkdownTesterEditor',
	    mixins: [Changeable, EditorJsonify],

	    getDefaultProps: function() {
	        return {
	            value: ""
	        };
	    },

	    render: function() {
	        return React.createElement("div", null, 
	            React.createElement("label", null, 
	                React.createElement("div", null, "Simple markdown contents:"), 
	                React.createElement("div", null, 
	                    React.createElement(TextArea, {
	                        value: this.props.value, 
	                        onChange: this.change("value"), 
	                        ref: "input"})
	                )
	            )
	        );
	    },

	    focus: function() {
	        this.refs.input.focus();
	        return true;
	    }
	});


	/**
	 * For this widget to work, we must require() this file in src/all-widgets.js
	 */
	module.exports = {
	    name: "simple-markdown-tester",
	    displayName: "Simple Markdown Tester",
	    hidden: true,   // Hides this widget from the Perseus.Editor widget select
	    widget: SimpleMarkdownTester,
	    editor: SimpleMarkdownTesterEditor,
	    transform: _.identity
	};


/***/ },
/* 42 */
/***/ function(module, exports, __webpack_require__) {

	var React          = __webpack_require__(61);
	var InfoTip        = __webpack_require__(69);
	var _ = __webpack_require__(60);

	var PropCheckBox   = __webpack_require__(58);
	var Sortable       = __webpack_require__(90);
	var TextListEditor = __webpack_require__(70);

	var shuffle = __webpack_require__(3).shuffle;

	var HORIZONTAL = "horizontal",
	    VERTICAL = "vertical";

	var Sorter = React.createClass({displayName: 'Sorter',
	    propTypes: {
	        correct: React.PropTypes.array,
	        layout: React.PropTypes.oneOf([HORIZONTAL, VERTICAL]),
	        padding: React.PropTypes.bool,
	        problemNum: React.PropTypes.number,
	        onChange: React.PropTypes.func
	    },

	    getDefaultProps: function() {
	        return {
	            correct: [],
	            layout: HORIZONTAL,
	            padding: true,
	            problemNum: 0,
	            onChange: function() {}
	        };
	    },

	    render: function() {
	        var options = shuffle(
	            this.props.correct,
	            this.props.problemNum,
	            /* ensurePermuted */ true
	        );

	        return React.createElement("div", {className: "perseus-widget-sorter ui-helper-clearfix"}, 
	            React.createElement(Sortable, {
	                options: options, 
	                layout: this.props.layout, 
	                padding: this.props.padding, 
	                onChange: this.props.onChange, 
	                ref: "sortable"})
	        );
	    },

	    getUserInput: function() {
	        return {options: this.refs.sortable.getOptions()};
	    },

	    simpleValidate: function(rubric) {
	        return Sorter.validate(this.getUserInput(), rubric);
	    },

	    statics: {
	        displayMode: "block"
	    }
	});


	_.extend(Sorter, {
	    validate: function(state, rubric) {
	        var correct = _.isEqual(state.options, rubric.correct);

	        return {
	            type: "points",
	            earned: correct ? 1 : 0,
	            total: 1,
	            message: null
	        };
	    }
	});


	var SorterEditor = React.createClass({displayName: 'SorterEditor',
	    propTypes: {
	        correct: React.PropTypes.array,
	        layout: React.PropTypes.oneOf([HORIZONTAL, VERTICAL]),
	        padding: React.PropTypes.bool
	    },

	    getDefaultProps: function() {
	        return {
	            correct: ["$x$", "$y$", "$z$"],
	            layout: HORIZONTAL,
	            padding: true
	        };
	    },

	    render: function() {
	        var editor = this;

	        return React.createElement("div", null, 
	            React.createElement("div", null, 
	                ' ', "Correct answer:", ' ', 
	                React.createElement(InfoTip, null, React.createElement("p", null, 
	                    "Enter the correct answer (in the correct order) here. The" + ' ' +
	                    "preview on the right will have the cards in a randomized" + ' ' +
	                    "order, which is how the student will see them."
	                ))
	            ), 
	            React.createElement(TextListEditor, {
	                options: this.props.correct, 
	                onChange: function(options, cb) {
	                    editor.props.onChange({correct: options}, cb);
	                }, 
	                layout: this.props.layout}), 
	            React.createElement("div", null, 
	                React.createElement("label", null, 
	                    ' ', "Layout:", ' ', 
	                    React.createElement("select", {value: this.props.layout, 
	                            onChange: this.onLayoutChange}, 
	                        React.createElement("option", {value: HORIZONTAL}, "Horizontal"), 
	                        React.createElement("option", {value: VERTICAL}, "Vertical")
	                    )
	                ), 
	                React.createElement(InfoTip, null, 
	                    React.createElement("p", null, "Use the horizontal layout for short text and small" + ' ' +
	                    "images. The vertical layout is best for longer text and" + ' ' +
	                    "larger images.")
	                )
	            ), 
	            React.createElement("div", null, 
	                React.createElement(PropCheckBox, {
	                    label: "Padding:", 
	                    padding: this.props.padding, 
	                    onChange: this.props.onChange}), 
	                React.createElement(InfoTip, null, 
	                    React.createElement("p", null, "Padding is good for text, but not needed for images.")
	                )
	            )
	        );
	    },

	    onLayoutChange: function(e) {
	        this.props.onChange({layout: e.target.value});
	    },

	    serialize: function() {
	        return _.pick(this.props, "correct", "layout", "padding");
	    }
	});

	module.exports = {
	    name: "sorter",
	    displayName: "Sorter",
	    widget: Sorter,
	    editor: SorterEditor
	};


/***/ },
/* 43 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);
	var _ = __webpack_require__(60);

	var Editor = __webpack_require__(7);
	var InfoTip = __webpack_require__(69);
	var MathOutput = __webpack_require__(88);
	var NumberInput  = __webpack_require__(85);
	var Renderer = __webpack_require__(10);
	var Util = __webpack_require__(3);

	var ApiOptions = __webpack_require__(12).Options;

	var assert = __webpack_require__(89).assert;

	/* Input handling: Maps a (row, column) pair to a unique ref used by React,
	 * and extracts (row, column) pairs from input paths, used to allow outsiders
	 * to focus, blur, set input values, etc. */
	var getInputPath = function(row, column) {
	    return ["" + row, "" + column];
	};

	var getDefaultPath = function() {
	    return getInputPath(0, 0);
	};

	var getRowFromPath = function(path) {
	    // 'path' should be a (row, column) pair
	    assert(_.isArray(path) && path.length === 2);
	    return +path[0];
	};

	var getColumnFromPath = function(path) {
	    // 'path' should be a (row, column) pair
	    assert(_.isArray(path) && path.length === 2);
	    return +path[1];
	};

	var getRefForPath = function(path) {
	    var row = getRowFromPath(path);
	    var column = getColumnFromPath(path);
	    return "answer" + row + "," + column;
	};

	var Table = React.createClass({displayName: 'Table',
	    propTypes: {
	        editableHeaders: React.PropTypes.bool,
	        headers: React.PropTypes.arrayOf(React.PropTypes.string),
	        answers: React.PropTypes.arrayOf(
	            React.PropTypes.arrayOf(
	                React.PropTypes.string
	            )
	        )
	    },

	    getDefaultProps: function() {
	        var defaultRows = 4;
	        var defaultColumns = 1;
	        var blankAnswers = _(defaultRows).times(function() {
	            return Util.stringArrayOfSize(defaultColumns);
	        });
	        return {
	            apiOptions: ApiOptions.defaults,
	            headers: [""],
	            editableHeaders: false,
	            rows: defaultRows,
	            columns: defaultColumns,
	            answers: blankAnswers,
	        };
	    },

	    _getRows: function() {
	        return this.props.answers.length;
	    },

	    _getColumns: function() {
	        return this.props.answers[0].length;
	    },

	    render: function() {
	        var rows = this._getRows();
	        var columns = this._getColumns();
	        var headers = this.props.headers;

	        var InputComponent;
	        if (this.props.apiOptions.staticRender) {
	            InputComponent = MathOutput;
	        } else {
	            InputComponent = "input";
	        }

	        return React.createElement("table", {className: "perseus-widget-table-of-values non-markdown"}, 
	            React.createElement("thead", null, 
	                React.createElement("tr", null, 
	                    _.map(headers, function(header, i)  {
	                        if (this.props.editableHeaders) {
	                            return React.createElement("th", {key: i}, 
	                                React.createElement(Editor, {
	                                    ref: "columnHeader" + i, 
	                                    content: header, 
	                                    widgetEnabled: false, 
	                                    onChange: 
	                                        _.partial(this.onHeaderChange, i)
	                                    }
	                                )
	                            );
	                        } else {
	                            return React.createElement("th", {key: i}, 
	                                React.createElement(Renderer, {content: header})
	                            );
	                        }
	                    }.bind(this))
	                
	                )
	            ), 
	            React.createElement("tbody", null, 
	                _(rows).times(function(r)  {
	                    return React.createElement("tr", {key: r}, 
	                        _(columns).times(function(c)  {
	                            return React.createElement("td", {key: c}, 
	                                React.createElement(InputComponent, {
	                                    ref: getRefForPath(getInputPath(r, c)), 
	                                    type: "text", 
	                                    value: this.props.answers[r][c], 
	                                    onFocus: _.partial(
	                                        this._handleFocus, getInputPath(r, c)
	                                    ), 
	                                    onBlur: _.partial(
	                                        this._handleBlur, getInputPath(r, c)
	                                    ), 
	                                    onChange: 
	                                        _.partial(this.onValueChange, r, c)
	                                    })
	                            );
	                        }.bind(this))
	                    );
	                }.bind(this))
	            
	            )
	        );
	    },

	    getUserInput: function() {
	        return _.map(this.props.answers, _.clone);
	    },

	    onValueChange: function(row, column, e) {
	        var answers = _.map(this.props.answers, _.clone);
	        answers[row][column] = e.target.value;
	        this.props.onChange({
	            answers: answers
	        });
	    },

	    onHeaderChange: function(index, e) {
	        var headers = this.props.headers.slice();
	        headers[index] = e.content;
	        this.props.onChange({
	            headers: headers
	        });
	    },

	    simpleValidate: function(rubric) {
	        return Table.validate(this.getUserInput(), rubric);
	    },

	    _handleFocus: function(inputPath) {
	        this.props.onFocus(inputPath);
	    },

	    _handleBlur: function(inputPath) {
	        this.props.onBlur(inputPath);
	    },

	    focus: function() {
	        this.focusInputPath(getDefaultPath());
	        return true;
	    },

	    focusInputPath: function(path) {
	        var inputID = getRefForPath(path);
	        var inputComponent = this.refs[inputID];
	        if (this.props.apiOptions.staticRender) {
	            inputComponent.focus();
	        } else {
	            inputComponent.getDOMNode().focus();
	        }
	    },

	    blurInputPath: function(path) {
	        var inputID = getRefForPath(path);
	        var inputComponent = this.refs[inputID];
	        if (this.props.apiOptions.staticRender) {
	            inputComponent.blur();
	        } else {
	            inputComponent.getDOMNode().blur();
	        }
	    },

	    getDOMNodeForPath: function(path) {
	        var inputID = getRefForPath(path);
	        return this.refs[inputID].getDOMNode();
	    },

	    getInputPaths: function() {
	        var rows = this._getRows();
	        var columns = this._getColumns();
	        var inputPaths = [];
	        _(rows).times(function(r)  {
	            _(columns).times(function(c)  {
	                var inputPath = getInputPath(r, c);
	                inputPaths.push(inputPath);
	            });
	        });
	        return inputPaths;
	    },

	    getGrammarTypeForPath: function(inputPath) {
	        return "number";
	    },

	    setInputValue: function(path, newValue, cb) {
	        // Extract row, column information
	        var row = getRowFromPath(path);
	        var column = getColumnFromPath(path);

	        var answers = _.map(this.props.answers, _.clone);
	        answers[row][column] = newValue;
	        this.props.onChange({
	            answers: answers
	        }, cb);
	    },

	    statics: {
	        displayMode: "block"
	    }
	});

	_.extend(Table, {
	    validate: function(state, rubric) {
	        var filterNonEmpty = function (table) {
	            return _.filter(table, function (row) {

	                // Check if row has a cell that is nonempty
	                return _.some(row, _.identity);
	            });
	        };
	        var solution = filterNonEmpty(rubric.answers);
	        var supplied = filterNonEmpty(state);
	        var hasEmptyCell = _.some(supplied, function(row) {
	            return _.some(row, function(cell) {
	                return cell === "";
	            });
	        });
	        if (hasEmptyCell || !supplied.length) {
	            return {
	                type: "invalid",
	                message: null
	            };
	        }
	        if (supplied.length !== solution.length) {
	            return {
	                type: "points",
	                earned: 0,
	                total: 1,
	                message: null
	            };
	        }
	        var createValidator = Khan.answerTypes
	                                  .number.createValidatorFunctional;
	        var message = null;
	        var allCorrect = _.every(solution, function (rowSolution) {
	            var i;
	            for (i = 0; i < supplied.length; i++) {
	                var rowSupplied = supplied[i];
	                var correct = _.every(rowSupplied, function (cellSupplied, i) {
	                    var cellSolution = rowSolution[i];
	                    var validator = createValidator(
	                            cellSolution, {
	                                simplify: true
	                            });
	                    var result = validator(cellSupplied);
	                    if (result.message) {
	                        message = result.message;
	                    }
	                    return result.correct;
	                });
	                if (correct) {
	                    supplied.splice(i, 1);
	                    return true;
	                }
	            }
	            return false;
	        });
	        return {
	            type: "points",
	            earned: allCorrect ? 1 : 0,
	            total: 1,
	            message: message
	        };
	    }
	});

	var TableEditor = React.createClass({displayName: 'TableEditor',
	    propTypes: {
	        rows: React.PropTypes.number,
	        columns: React.PropTypes.number,
	        headers: React.PropTypes.arrayOf(React.PropTypes.string),
	        answers: React.PropTypes.arrayOf(
	            React.PropTypes.arrayOf(
	                React.PropTypes.string
	            )
	        )
	    },

	    getDefaultProps: function() {
	        var defaultRows = 4;
	        var defaultColumns = 1;
	        var blankAnswers = _(defaultRows).times(function() {
	            return Util.stringArrayOfSize(defaultColumns);
	        });
	        return {
	            headers: [""],
	            rows: defaultRows,
	            columns: defaultColumns,
	            answers: blankAnswers
	        };
	    },

	    focus: function() {
	        this.refs.numberOfColumns.getDOMNode().focus();
	    },

	    render: function() {
	        var rows = this.props.rows;
	        var cols = this.props.columns;

	        var tableProps = _.pick(this.props, "headers", "answers", "onChange");
	        _.extend(tableProps, {
	            editableHeaders: true
	        });

	        return React.createElement("div", null, 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                React.createElement(NumberInput, {
	                    label: "Number of columns:", 
	                    ref: "numberOfColumns", 
	                    value: this.props.columns, 
	                    onChange: function(val)  {
	                        if (val) {
	                            this.onSizeInput(this.props.rows, val);
	                        }
	                    }.bind(this), 
	                    useArrowKeys: true})
	            ), 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                React.createElement(NumberInput, {
	                    label: "Number of rows:", 
	                    ref: "numberOfRows", 
	                    value: this.props.rows, 
	                    onChange: function(val)  {
	                        if (val) {
	                            this.onSizeInput(val, this.props.columns);
	                        }
	                    }.bind(this), 
	                    useArrowKeys: true})
	            ), 
	            React.createElement("div", null, 
	                ' ', "Table of answers:", ' ', 
	                React.createElement(InfoTip, null, 
	                    React.createElement("p", null, "The student has to fill out all cells in the" + ' ' +
	                    "table.  For partially filled tables create a table" + ' ' +
	                    "using the template, and insert text input boxes" + ' ' +
	                    "as desired.")
	                )
	            ), 
	            React.createElement("div", null, 
	                React.createElement(Table, React.__spread({},  tableProps))
	            )
	        );
	    },

	    onSizeInput: function(numRawRows, numRawColumns) {
	        var rows = +numRawRows || 0;
	        var columns = +numRawColumns || 0;
	        rows = Math.min(Math.max(1, rows), 30);
	        columns = Math.min(Math.max(1, columns), 6);
	        var oldColumns = this.props.columns;
	        var oldRows = this.props.rows;

	        var answers = this.props.answers;
	        // Truncate if necessary; else, append
	        if (rows <= oldRows) {
	            answers.length = rows;
	        } else {
	            _(rows - oldRows).times(function() {
	                answers.push(Util.stringArrayOfSize(oldColumns));
	            });
	        }

	        function fixColumnSizing(array) {
	            // Truncate if necessary; else, append
	            if (columns <= oldColumns) {
	                array.length = columns;
	            } else {
	                _(columns - oldColumns).times(function() {
	                    array.push("");
	                });
	            }
	        }

	        var headers = this.props.headers;
	        fixColumnSizing(headers);
	        _.each(answers, fixColumnSizing);

	        this.props.onChange({
	            rows: rows,
	            columns: columns,
	            answers: answers,
	            headers: headers
	        });
	    },

	    serialize: function() {
	        var json = _.pick(this.props, "headers", "rows", "columns");

	        return _.extend({}, json, {
	            answers: _.map(this.props.answers, _.clone)
	        });
	    }
	});

	var propTransform = function(editorProps)  {
	    // Remove answers before passing to widget
	    var rows = editorProps.answers.length;
	    var columns = editorProps.answers[0].length;
	    var blankAnswers = _(rows).times(function() {
	        return Util.stringArrayOfSize(columns);
	    });
	    return _.extend({}, editorProps, {
	        answers: blankAnswers
	    });
	};

	module.exports = {
	    name: "table",
	    displayName: "Table of values",
	    widget: Table,
	    editor: TableEditor,
	    transform: propTransform
	};


/***/ },
/* 44 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);
	var _ = __webpack_require__(60);

	var Graph         = __webpack_require__(87);
	var GraphSettings = __webpack_require__(79);
	var InfoTip       = __webpack_require__(69);
	var NumberInput   = __webpack_require__(85);
	var MathOutput    = __webpack_require__(88);
	var PropCheckBox  = __webpack_require__(58);
	var TeX           = __webpack_require__(65);

	var ApiOptions = __webpack_require__(12).Options;

	var ROTATE_SNAP_DEGREES = 15;
	var DEGREE_SIGN = "\u00B0";
	var RENDER_TRANSFORM_DELAY_IN_MS = 300;
	var ROTATE_HANDLE_DIST = 1.5;
	var REFLECT_ROTATE_HANDLE_DIST = 2;
	var REFLECT_BUTTON_SIZE = 1;

	var deepEq = __webpack_require__(3).deepEq;
	var getGridStep = __webpack_require__(3).getGridStep;
	var captureScratchpadTouchStart =
	        __webpack_require__(3).captureScratchpadTouchStart;

	var knumber = __webpack_require__(103).number;
	var kvector = __webpack_require__(103).vector;
	var kpoint = __webpack_require__(103).point;
	var kray = __webpack_require__(103).ray;
	var kline = __webpack_require__(103).line;

	var assert = __webpack_require__(89).assert;

	var defaultBoxSize = 400;
	var defaultBackgroundImage = {
	    url: null,
	    scale: 1,
	    bottom: 0,
	    left: 0,
	};

	function arraySum(array) {
	    return _.reduce(array, function(memo, arg) { return memo + arg; }, 0);
	}

	/* Does a pluck on keys inside objects in an object
	 *
	 * Ex:
	 * tools = {
	 *     translation: {
	 *         enabled: true
	 *     },
	 *     rotation: {
	 *         enabled: false
	 *     }
	 * };
	 * pluckObject(tools, "enabled") returns {
	 *     translation: true
	 *     rotation: false
	 * }
	 */
	function pluckObject(object, subKey) {
	    return _.object(_.map(object, function (value, key) {
	        return [key, value[subKey]];
	    }));
	}


	var defaultGraphProps = function(setProps, boxSize) {
	    setProps = setProps || {};
	    var labels = setProps.labels || ["x", "y"];
	    var range = setProps.range || [[-10, 10], [-10, 10]];
	    var step = setProps.step || [1, 1];
	    var gridStep = setProps.gridStep ||
	               getGridStep(range, step, boxSize);
	    return {
	        box: [boxSize, boxSize],
	        labels: labels,
	        range: range,
	        step: step,
	        gridStep: gridStep,
	        valid: true,
	        backgroundImage: defaultBackgroundImage,
	        markings: "grid",
	        showProtractor: false
	    };
	};

	var defaultTransformerProps = {
	    apiOptions: ApiOptions.defaults,
	    gradeEmpty: false,
	    graphMode: "interactive",
	    listMode: "dynamic",
	    graph: {},
	    tools: {
	        translation: {
	            enabled: true,
	            required: false,
	            constraints: {}
	        },
	        rotation: {
	            enabled: true,
	            required: false,
	            constraints: {
	                fixed: false
	            },
	            coord: [1, 6]
	        },
	        reflection: {
	            enabled: true,
	            required: false,
	            constraints: {
	                fixed: false
	            },
	            coords: [[2, -4], [2, 2]]
	        },
	        dilation: {
	            enabled: true,
	            required: false,
	            constraints: {
	                fixed: false
	            },
	            coord: [6, 6]
	        }
	    },
	    drawSolutionShape: true,
	    starting: {
	        shape: {
	            type: "polygon-3",
	            coords: [[2, 2], [2, 6], [7, 2]],
	        },
	        transformations: []
	    },
	    correct: {
	        shape: {
	            type: "polygon-3",
	            coords: [[2, 2], [2, 6], [7, 2]],
	        },
	        transformations: []
	    }
	};

	function colorForTool(tool) {
	    return tool.constraints.fixed ? KhanUtil.DYNAMIC
	                                  : KhanUtil.INTERACTIVE;
	}


	/* Scales a distance from the default range of
	 * [-10, 10] to a given props.range pair
	 *
	 * Used for sizing various transformation tools
	 * (rotation handle, dilation circle)
	 */
	function scaleToRange(dist, range) {
	    var spreadX = range[0][1] - range[0][0];
	    var spreadY = range[1][1] - range[1][0];

	    return dist * Math.max(spreadX, spreadY) / 20;
	}

	function dilatePointFromCenter(point, dilationCenter, scale) {
	    var pv = kvector.subtract(point, dilationCenter);
	    var pvScaled = kvector.scale(pv, scale);
	    var transformedPoint = kvector.add(dilationCenter, pvScaled);
	    return transformedPoint;
	}

	// TODO(jack): i18nize this
	function stringFromDecimal(number) {
	    return String(KhanUtil.roundTo(9, number));
	}

	function stringFromFraction(number) {
	    var frac = KhanUtil.toFraction(number, knumber.DEFAULT_TOLERANCE);
	    if (frac[1] === 1) {
	        return stringFromDecimal(number);
	    } else {
	        return stringFromDecimal(frac[0]) + "/" +
	                stringFromDecimal(frac[1]);
	    }
	}

	function texFromPoint(point) {
	    return [
	        React.createElement(TeX, null, "("),
	        stringFromDecimal(point[0]),
	        React.createElement(TeX, null, ", {}"),
	        stringFromDecimal(point[1]),
	        React.createElement(TeX, null, ")")
	    ];
	}

	function texFromVector(vector) {
	    return [
	        React.createElement(TeX, null, "\\langle"),
	        stringFromDecimal(vector[0]),
	        React.createElement(TeX, null, ", {}"),
	        stringFromDecimal(vector[1]),
	        React.createElement(TeX, null, "\\rangle")
	    ];
	}

	function texFromAngleDeg(angleDeg) {
	    return stringFromDecimal(angleDeg) + DEGREE_SIGN;
	}

	function orderInsensitiveCoordsEqual(coords1, coords2) {
	    coords1 = _.clone(coords1).sort(kpoint.compare);
	    coords2 = _.clone(coords2).sort(kpoint.compare);
	    return _.all(_.map(coords1, function(coord1, i) {
	        var coord2 = coords2[i];
	        return kpoint.equal(coord1, coord2);
	    }));
	}



	/* Perform operations on raw transform objects */
	var TransformOps = {
	    apply: function(transform) {
	        // Any transformation with empty text boxes is a no-op until
	        // filled out (these show up as nulls in transform.vector/line/etc).
	        // TODO (jack): Merge this just into reflections now that other
	        // transforms are always valid (after merging transformation
	        // collapsing, which may use isValid)
	        if (!Transformations[transform.type].isValid(transform)) {
	            return _.identity;  // do not transform the coord
	        } else {
	            return Transformations[transform.type].apply(transform);
	        }
	    },

	    append: function(transformList, newTransform) {
	        // Append newTransform to transformList, and collapse the last
	        // two transforms if they are collapsable
	        var results = TransformOps._appendAndCollapseLastTwo(
	            transformList,
	            newTransform
	        );
	        // Collapse any no-ops at the end of the transformation list
	        return TransformOps._collapseFinalNoOps(results);
	    },

	    _collapseFinalNoOps: function(transforms) {
	        // Collapse no-op transformations at the end of the list
	        if (transforms.length && TransformOps.isNoOp(_.last(transforms))) {
	            return _.initial(transforms);
	        } else {
	            return transforms;
	        }
	    },

	    _appendAndCollapseLastTwo: function(transformList, newTransform) {
	        if (!transformList.length) {
	            return [newTransform];
	        } else {
	            var collapsed = TransformOps.collapse(
	                _.last(transformList),
	                newTransform
	            );
	            return _.initial(transformList).concat(collapsed);
	        }
	    },

	    isNoOp: function(transform) {
	        return Transformations[transform.type].isNoOp(transform);
	    },

	    collapse: function(transform1, transform2) {
	        // We can only collapse transforms that have the same type
	        if (transform1.type !== transform2.type) {
	            return [transform1, transform2];
	        }

	        // Clicking the button again removes empty transformations
	        if (TransformOps.isEmpty(transform1) &&
	                TransformOps.isEmpty(transform2)) {
	            return [];
	        }

	        // Don't collapse invalid transformations otherwise
	        if (!TransformOps.isValid(transform1) ||
	                !TransformOps.isValid(transform2)) {
	            return [transform1, transform2];
	        }

	        return TransformOps._collapseValidMonotypedTransforms(
	            transform1,
	            transform2
	        );
	    },

	    isValid: function(transform) {
	        return Transformations[transform.type].isValid(transform);
	    },

	    isEmpty: function(transform) {
	        return Transformations[transform.type].isEmpty(transform);
	    },

	    _collapseValidMonotypedTransforms: function(transform1, transform2) {
	        var collapsed = Transformations[transform1.type].collapse(
	            transform1,
	            transform2
	        );
	        if (collapsed) {
	            // Force all answers into an array
	            if (!_.isArray(collapsed)) {
	                collapsed = [collapsed];
	            }
	            // Add types to all transforms in the answer
	            _.each(collapsed, function(transform) {
	                transform.type = transform1.type;
	            });
	            return collapsed;
	        } else {
	            // These transforms can't be collapsed together
	            return [transform1, transform2];
	        }
	    },

	    toTeX: function(transform) {
	        return Transformations[transform.type].toTeX(transform);
	    },

	    /* A react representation of this transform object */
	    ListItem: React.createClass({displayName: 'ListItem',
	        render: function() {
	            if (this.props.mode === "dynamic") {
	                return React.createElement("div", null, 
	                    TransformOps.toTeX(this.props.transform)
	                );
	            } else if (this.props.mode === "interactive") {
	                var transformClass =
	                        Transformations[this.props.transform.type].Input;
	                return transformClass(_.extend({
	                    ref: "transform",
	                    onChange: this.handleChange,
	                    onFocus: this.props.onFocus,
	                    onBlur: this.props.onBlur,
	                    apiOptions: this.props.apiOptions
	                }, this.props.transform));
	            } else {
	                throw new Error("Invalid mode: " + this.props.mode);
	            }
	        },
	        value: function() {
	            if (this.props.mode === "interactive") {
	                return _.extend({
	                    type: this.props.transform.type,
	                }, this.refs.transform.value());
	            } else {
	                return this.props.transform;
	            }
	        },
	        handleChange: _.debounce(function(callback) {
	            this.props.onChange(this.value(), callback);
	        }, RENDER_TRANSFORM_DELAY_IN_MS),

	        /* InputPath API: depending on the API call, this could involve simply
	         * navigating to the right ref and calling the function on that
	         * component, or threading the call down and returning the result. */
	        _getComponentAtPath: function(path) {
	            var transform = this.refs.transform;
	            var ref = _.head(path);
	            return transform.refs[ref];
	        },
	        focus: function() {
	            var transform = this.refs.transform;
	            var path = _.head(transform.getInputPaths());
	            if (path) {
	                this.focusInputPath(path);
	            }
	        },
	        focusInputPath: function(path) {
	            this._getComponentAtPath(path).focus();
	        },
	        blurInputPath: function(path) {
	            this._getComponentAtPath(path).blur();
	        },
	        getDOMNodeForPath: function(path) {
	            return this._getComponentAtPath(path).getDOMNode();
	        },
	        getGrammarTypeForPath: function(path) {
	            return "number";
	        },
	        setInputValue: function(path, value, cb) {
	            this.refs.transform.setInputValue(path, value, cb);
	        },
	        getInputPaths: function() {
	            return this.refs.transform.getInputPaths();
	        },

	        statics: {
	            displayMode: "block"
	        }
	    })
	};

	var Transformations = {
	    translation: {
	        // I18N: As in the command, "Translate the polygon"
	        verbName: $._("Translate"),
	        nounName: $._("Translation"),
	        lowerNounName: $._("translation"),
	        apply: function(transform) {
	            return function(coord) {
	                return kvector.add(coord, transform.vector);
	            };
	        },
	        isValid: function(transform) {
	            return _.isFinite(transform.vector[0]) &&
	                _.isFinite(transform.vector[1]);
	        },
	        isEmpty: function(transform) {
	            return transform.vector[0] === null &&
	                transform.vector[1] === null;
	        },
	        isNoOp: function(transform) {
	            return kvector.equal(transform.vector, [0, 0]);
	        },
	        collapse: function(transform1, transform2) {
	            return {
	                vector: kvector.add(
	                    transform1.vector,
	                    transform2.vector
	                )
	            };
	        },
	        toTeX: function(transform) {
	            // I18N: As in the command, "Translation by <3, 1>"
	            return $_({vector: texFromVector(transform.vector)}, 
	                "Translation by %(vector)s"
	            );
	        },
	        Input: React.createClass({displayName: 'Input',
	            getInitialState: function() {
	                return {
	                    vector: this.props.vector || [null, null]
	                };
	            },
	            componentDidUpdate: function(prevProps) {
	                if (!deepEq(this.props, prevProps)) {
	                    this.setState({vector: this.props.vector});
	                }
	            },
	            render: function() {
	                var InputComponent = (this.props.apiOptions.staticRender) ?
	                        MathOutput :
	                        NumberInput;
	                var vector = [
	                    React.createElement(TeX, null, "\\langle"),
	                    React.createElement(InputComponent, {
	                        ref: "x", 
	                        placeholder: 0, 
	                        value: this.state.vector[0], 
	                        useArrowKeys: true, 
	                        onChange: function(val0)  {
	                            var val1 = this.state.vector[1];
	                            this.setState({vector: [val0, val1]}, function()  {
	                                this.props.onChange();
	                            }.bind(this));
	                        }.bind(this), 
	                        onFocus: _.partial(this.props.onFocus, "x"), 
	                        onBlur: _.partial(this.props.onBlur, "x")}),
	                    React.createElement(TeX, null, ", {}"),
	                    React.createElement(InputComponent, {
	                        ref: "y", 
	                        placeholder: 0, 
	                        value: this.state.vector[1], 
	                        useArrowKeys: true, 
	                        onChange: function(val1)  {
	                            var val0 = this.state.vector[0];
	                            this.setState({vector: [val0, val1]}, function()  {
	                                this.props.onChange();
	                            }.bind(this));
	                        }.bind(this), 
	                        onFocus: _.partial(this.props.onFocus, "y"), 
	                        onBlur: _.partial(this.props.onBlur, "y")}),
	                    React.createElement(TeX, null, "\\rangle")
	                ];
	                return React.createElement("div", null, 
	                    $_({vector: vector}, 
	                        "Translation by %(vector)s"
	                    )
	                );
	            },
	            value: function() {
	                var x = this.refs.x.getValue();
	                var y = this.refs.y.getValue();
	                return {
	                    vector: [x, y]
	                };
	            },
	            /* InputPath API */
	            setInputValue: function(path, value, cb) {
	                var id = _.first(path);
	                var vector = _.clone(this.state.vector);
	                if (id === "x") {
	                    vector[0] = value;
	                } else if (id === "y") {
	                    vector[1] = value;
	                }
	                this.setState({vector: vector}, function()  {
	                    this.props.onChange(cb);
	                }.bind(this));
	            },
	            getInputPaths: function() {
	                return [["x"], ["y"]];
	            }
	        })
	    },

	    rotation: {
	        // I18N: As in the command, "Rotate the polygon"
	        verbName: $._("Rotate"),
	        nounName: $._("Rotation"),
	        lowerNounName: $._("rotation"),
	        apply: function(transform) {
	            return function(coord) {
	                return kpoint.rotateDeg(coord, transform.angleDeg,
	                        transform.center);
	            };
	        },
	        isValid: function(transform) {
	            return _.isFinite(transform.angleDeg) &&
	                _.isFinite(transform.center[0]) &&
	                _.isFinite(transform.center[1]);
	        },
	        isEmpty: function(transform) {
	            return transform.angleDeg === null &&
	                transform.center[0] === null &&
	                transform.center[1] === null;
	        },
	        isNoOp: function(transform) {
	            return knumber.equal(transform.angleDeg, 0);
	        },
	        collapse: function(transform1, transform2) {
	            if (!kpoint.equal(transform1.center, transform2.center)) {
	                return false;
	            }
	            return {
	                center: transform1.center,
	                angleDeg: transform1.angleDeg + transform2.angleDeg
	            };
	        },
	        toTeX: function(transform) {
	            return $_({degrees: texFromAngleDeg(transform.angleDeg), 
	                       point: texFromPoint(transform.center)}, 
	                "Rotation by %(degrees)s about %(point)s"
	            );
	        },
	        Input: React.createClass({displayName: 'Input',
	            getInitialState: function() {
	                return {
	                    center: this.props.center || [null, null],
	                    angleDeg: this.props.angleDeg || null
	                };
	            },
	            componentDidUpdate: function(prevProps) {
	                if (!deepEq(this.props, prevProps)) {
	                    this.setState({
	                        center: this.props.center,
	                        angleDeg: this.props.angleDeg
	                    });
	                }
	            },
	            render: function() {
	                var InputComponent = (this.props.apiOptions.staticRender) ?
	                        MathOutput :
	                        NumberInput;
	                var point = [
	                    React.createElement(TeX, null, "("),
	                    React.createElement(InputComponent, {
	                        ref: "centerX", 
	                        placeholder: 0, 
	                        value: this.state.center[0], 
	                        useArrowKeys: true, 
	                        onChange: function(val0)  {
	                            var val1 = this.state.center[1];
	                            this.setState({center: [val0, val1]}, function()  {
	                                this.props.onChange();
	                            }.bind(this));
	                        }.bind(this), 
	                        onFocus: _.partial(this.props.onFocus, "centerX"), 
	                        onBlur: _.partial(this.props.onBlur, "centerX")}),
	                    React.createElement(TeX, null, ", {}"),
	                    React.createElement(InputComponent, {
	                        ref: "centerY", 
	                        placeholder: 0, 
	                        value: this.state.center[1], 
	                        useArrowKeys: true, 
	                        onChange: function(val1)  {
	                            var val0 = this.state.center[0];
	                            this.setState({center: [val0, val1]}, function()  {
	                                this.props.onChange();
	                            }.bind(this));
	                        }.bind(this), 
	                        onFocus: _.partial(this.props.onFocus, "centerY"), 
	                        onBlur: _.partial(this.props.onBlur, "centerY")}),
	                    React.createElement(TeX, null, ")")
	                ];
	                var degrees = [
	                    React.createElement(InputComponent, {
	                        ref: "angleDeg", 
	                        placeholder: 0, 
	                        value: this.state.angleDeg, 
	                        useArrowKeys: true, 
	                        onChange: function(val)  {
	                            this.setState({angleDeg: val}, function()  {
	                                this.props.onChange();
	                            }.bind(this));
	                        }.bind(this), 
	                        onFocus: _.partial(this.props.onFocus, "angleDeg"), 
	                        onBlur: _.partial(this.props.onBlur, "angleDeg")}),
	                    DEGREE_SIGN
	                ];
	                // I18N: %(point)s must come before %(degrees)s in this phrase
	                var text = $_({point: point, degrees: degrees}, 
	                    "Rotation about %(point)s by %(degrees)s"
	                );

	                return React.createElement("div", null, text);
	            },
	            value: function() {
	                var angleDeg = this.refs.angleDeg.getValue();
	                var centerX = this.refs.centerX.getValue();
	                var centerY = this.refs.centerY.getValue();
	                return {
	                    angleDeg: angleDeg,
	                    center: [centerX, centerY]
	                };
	            },
	            /* InputPath API */
	            setInputValue: function(path, value, cb) {
	                var id = _.first(path);
	                var angleDeg = _.clone(this.state.angleDeg);
	                var center = _.clone(this.state.center);
	                if (id === "angleDeg") {
	                    angleDeg = value;
	                } else if (id === "centerX") {
	                    center[0] = value;
	                } else if (id === "centerY") {
	                    center[1] = value;
	                }
	                this.setState({angleDeg: angleDeg, center: center}, function()  {
	                    this.props.onChange(cb);
	                }.bind(this));
	            },
	            getInputPaths: function() {
	                return [["centerX"], ["centerY"], ["angleDeg"]];
	            }
	        })
	    },

	    reflection: {
	        // I18N: As in the command, "Reflect the polygon"
	        verbName: $._("Reflect"),
	        nounName: $._("Reflection"),
	        lowerNounName: $._("reflection"),
	        apply: function(transform) {
	            return function(coord) {
	                return kpoint.reflectOverLine(
	                    coord,
	                    transform.line
	                );
	            };
	        },
	        isValid: function(transform) {
	            // A bit hacky, but we'll also define reflecting over a
	            // single point as a no-op, to avoid NaN fun.
	            return _.all(_.flatten(transform.line), _.isFinite) &&
	                    !kpoint.equal(transform.line[0], transform.line[1]);
	        },
	        isEmpty: function(transform) {
	            return _.all(_.flatten(transform.line), _.isNull);
	        },
	        isNoOp: function(transform) {
	            // Invalid transforms are implicitly no-ops, so we don't
	            // have to catch that case here.
	            return false;
	        },
	        collapse: function(transform1, transform2) {
	            if (!kline.equal(transform1.line, transform2.line)) {
	                return false;
	            }
	            return [];
	        },
	        toTeX: function(transform) {
	            var point1 = transform.line[0];
	            var point2 = transform.line[1];
	            return $_({point1: texFromPoint(point1), 
	                       point2: texFromPoint(point2)}, 
	                "Reflection over the line from %(point1)s to %(point2)s"
	            );
	        },
	        Input: React.createClass({displayName: 'Input',
	            getInitialState: function() {
	                return {
	                    line: this.props.line || [[null, null], [null, null]]
	                };
	            },
	            componentDidUpdate: function(prevProps) {
	                if (!deepEq(this.props, prevProps)) {
	                    this.setState({line: this.props.line});
	                }
	            },
	            render: function() {
	                var InputComponent = (this.props.apiOptions.staticRender) ?
	                        MathOutput :
	                        NumberInput;
	                var point1 = [React.createElement(TeX, null, "("),
	                    React.createElement(InputComponent, {
	                        ref: "x1", 
	                        value: this.state.line[0][0], 
	                        useArrowKeys: true, 
	                        onChange: this.changePoint.bind(this, 0, 0), 
	                        onFocus: _.partial(
	                            this.props.onFocus, "x1"
	                        ), 
	                        onBlur: _.partial(
	                            this.props.onBlur, "x1"
	                        )}),
	                    React.createElement(TeX, null, ", {}"),
	                    React.createElement(InputComponent, {
	                        ref: "y1", 
	                        value: this.state.line[0][1], 
	                        useArrowKeys: true, 
	                        onChange: this.changePoint.bind(this, 0, 1), 
	                        onFocus: _.partial(this.props.onFocus, "y1"), 
	                        onBlur: _.partial(this.props.onBlur, "y1")}),
	                    React.createElement(TeX, null, ")")
	                ];
	                var point2 = [React.createElement(TeX, null, "("),
	                    React.createElement(InputComponent, {
	                        ref: "x2", 
	                        value: this.state.line[1][0], 
	                        useArrowKeys: true, 
	                        onChange: this.changePoint.bind(this, 1, 0), 
	                        onFocus: _.partial(this.props.onFocus, "x2"), 
	                        onBlur: _.partial(this.props.onBlur, "x2")}),
	                    React.createElement(TeX, null, ", {}"),
	                    React.createElement(InputComponent, {
	                        ref: "y2", 
	                        value: this.state.line[1][1], 
	                        useArrowKeys: true, 
	                        onChange: this.changePoint.bind(this, 1, 1), 
	                        onFocus: _.partial(this.props.onFocus, "y2"), 
	                        onBlur: _.partial(this.props.onBlur, "y2")}),
	                    React.createElement(TeX, null, ")")
	                ];
	                return React.createElement("div", null, 
	                    $_({point1: point1, point2: point2}, 
	                        "Reflection over the line from %(point1)s to %(point2)s"
	                    )
	                );
	            },
	            changePoint: function(i, j, val, cb) {
	                var line = _.map(this.state.line, _.clone);
	                line[i][j] = val;
	                this.setState({line: line}, function()  {
	                    this.props.onChange(cb);
	                }.bind(this));
	            },
	            value: function() {
	                var x1 = this.refs.x1.getValue();
	                var y1 = this.refs.y1.getValue();
	                var x2 = this.refs.x2.getValue();
	                var y2 = this.refs.y2.getValue();
	                return {
	                    line: [[x1, y1], [x2, y2]]
	                };
	            },
	            /* InputPath API */
	            setInputValue: function(path, value, cb) {
	                var id = _.first(path);
	                var j;
	                if (id[0] === "x") {
	                    j = 0;
	                } else if (id[0] === "y") {
	                    j = 1;
	                }
	                var i;
	                if (id[1] === "1") {
	                    i = 0;
	                } else if (id[1] === "2") {
	                    i = 1;
	                }
	                this.changePoint(i, j, value, cb);
	            },
	            getInputPaths: function() {
	                return [["x1"], ["y1"], ["x2"], ["y2"]];
	            }
	        })
	    },

	    dilation: {
	        // I18N: As in the command, "Dilate the polygon"
	        verbName: $._("Dilate"),
	        nounName: $._("Dilation"),
	        lowerNounName: $._("dilation"),
	        apply: function(transform) {
	            return function(coord) {
	                return dilatePointFromCenter(coord, transform.center,
	                        transform.scale);
	            };
	        },
	        isValid: function(transform) {
	            return _.isFinite(transform.scale) &&
	                _.isFinite(transform.center[0]) &&
	                _.isFinite(transform.center[1]);
	        },
	        isEmpty: function(transform) {
	            return transform.scale === null &&
	                transform.center[0] === null &&
	                transform.center[1] === null;
	        },
	        isNoOp: function(transform) {
	            return knumber.equal(transform.scale, 1);
	        },
	        collapse: function(transform1, transform2) {
	            if (!kpoint.equal(transform1.center, transform2.center)) {
	                return false;
	            }
	            return {
	                center: transform1.center,
	                scale: transform1.scale * transform2.scale
	            };
	        },
	        toTeX: function(transform) {
	            var scaleString = stringFromFraction(transform.scale);
	            return $_({scale: scaleString, 
	                       point: texFromPoint(transform.center)}, 
	                "Dilation of scale %(scale)s about %(point)s"
	            );
	        },
	        Input: React.createClass({displayName: 'Input',
	            getInitialState: function() {
	                return {
	                    center: this.props.center || [null, null],
	                    scale: this.props.scale || null
	                };
	            },
	            componentDidUpdate: function(prevProps) {
	                if (!deepEq(this.props, prevProps)) {
	                    this.setState({
	                        center: this.props.center,
	                        scale: this.props.scale
	                    });
	                }
	            },
	            render: function() {
	                var InputComponent = (this.props.apiOptions.staticRender) ?
	                        MathOutput :
	                        NumberInput;
	                var point = [React.createElement(TeX, null, "("),
	                    React.createElement(InputComponent, {
	                        ref: "x", 
	                        placeholder: 0, 
	                        value: this.state.center[0], 
	                        useArrowKeys: true, 
	                        onChange: function(val0)  {
	                            var val1 = this.state.center[1];
	                            this.setState({center: [val0, val1]}, function()  {
	                                this.props.onChange();
	                            }.bind(this));
	                        }.bind(this), 
	                        onFocus: _.partial(this.props.onFocus, "x"), 
	                        onBlur: _.partial(this.props.onBlur, "x")}),
	                    React.createElement(TeX, null, ", {}"),
	                    React.createElement(InputComponent, {
	                        ref: "y", 
	                        placeholder: 0, 
	                        value: this.state.center[1], 
	                        useArrowKeys: true, 
	                        onChange: function(val1)  {
	                            var val0 = this.state.center[0];
	                            this.setState({center: [val0, val1]}, function()  {
	                                this.props.onChange();
	                            }.bind(this));
	                        }.bind(this), 
	                        onFocus: _.partial(this.props.onFocus, "y"), 
	                        onBlur: _.partial(this.props.onBlur, "y")}),
	                    React.createElement(TeX, null, ")")
	                ];
	                var scale = React.createElement(InputComponent, {
	                    ref: "scale", 
	                    placeholder: 1, 
	                    value: this.state.scale, 
	                    useArrowKeys: true, 
	                    onChange: function(val)  {
	                            this.setState({scale: val}, function()  {
	                                this.props.onChange();
	                            }.bind(this));
	                        }.bind(this), 
	                    onFocus: _.partial(this.props.onFocus, "scale"), 
	                    onBlur: _.partial(this.props.onBlur, "scale")});
	                return React.createElement("div", null, 
	                    $_({point: point, scale: scale}, 
	                        "Dilation about %(point)s by %(scale)s"
	                    )
	                );
	            },
	            value: function() {
	                var scale = this.refs.scale.getValue();
	                var x = this.refs.x.getValue();
	                var y = this.refs.y.getValue();
	                return {
	                    scale: scale,
	                    center: [x, y]
	                };
	            },
	            /* InputPath API */
	            setInputValue: function(path, value, cb) {
	                var id = _.first(path);
	                var scale = this.state.scale;
	                var center = _.clone(this.state.center);
	                if (id === "x") {
	                    center[0] = value;
	                } else if (id === "y") {
	                    center[1] = value;
	                } else if (id === "scale") {
	                    scale = value;
	                }
	                this.setState({scale: scale, center: center}, function()  {
	                    this.props.onChange(cb);
	                }.bind(this));
	            },
	            getInputPaths: function() {
	                return [["x"], ["y"], ["scale"]];
	            }
	        })
	    }
	};


	/* Various functions to deal with different shape types */
	var ShapeTypes = {
	    getPointCountForType: function(type) {
	        var splitType = type.split("-");
	        if (splitType[0] === "polygon") {
	            return splitType[1] || 3;
	        } else if (splitType[0] === "line" ||
	                splitType[0] === "lineSegment") {
	            return 2;
	        } else if (splitType[0] === "angle") {
	            return 3;
	        } else if (splitType[0] === "circle") {
	            return 2;
	        } else if (splitType[0] === "point") {
	            return 1;
	        }
	    },

	    addMovableShape: function(graphie, options) {
	        if (options.editable && options.translatable) {
	            throw new Error("It doesn't make sense to have a movable shape " +
	                    "where you can stretch the points and translate them " +
	                    "simultaneously. options: " + JSON.stringify(options));
	        }

	        var shape;
	        var points = _.map(options.shape.coords, function(coord) {
	            var currentPoint;
	            var isMoving = false;
	            var previousCoord = coord;

	            var onMove = function(x, y) {
	                if (!isMoving) {
	                    previousCoord = currentPoint.coord;
	                    isMoving = true;
	                }

	                var moveVector = kvector.subtract(
	                    [x, y],
	                    currentPoint.coord
	                );

	                // Translate from (x, y) semantics to (dX, dY) semantics
	                // This is more useful for translations on multiple points,
	                // where we care about how the points moved, not where any
	                // individual point ended up
	                if (options.onMove) {
	                    moveVector = options.onMove(moveVector[0],
	                            moveVector[1]);
	                }

	                // Perform a translation on all points in this shape when
	                // any point moves
	                if (options.translatable) {
	                    _.each(points, function(point) {
	                        // The point itself will be updated by the
	                        // movablePoint class, so only translate the other
	                        // points
	                        if (point !== currentPoint) {
	                            point.setCoord(kvector.add(
	                                point.coord,
	                                moveVector
	                            ));
	                        }
	                    });
	                }

	                // Update our shape and our currentPoint
	                // Without this, some shapes (circles, angles) appear
	                // "bouncy" as they are updated with currentPoint at the
	                // current mouse coordinate (oldCoord), rather than newCoord
	                var oldCoord = currentPoint.coord;
	                var newCoord = kvector.add(
	                    currentPoint.coord,
	                    moveVector
	                );
	                // Temporarily change our coordinate so that
	                // shape.update() sees the new coordinate
	                currentPoint.coord = newCoord;
	                shape.update();
	                // ...But don't break onMove, which assumes it
	                // is the only thing changing our coord
	                currentPoint.coord = oldCoord;
	                return newCoord;
	            };

	            var onMoveEnd = function() {
	                // onMove isn't guaranteed to be called before onMoveEnd, so
	                // we have to take into account that we may not have moved and
	                // set previousCoord.
	                if (options.onMoveEnd && isMoving) {
	                    isMoving = false;
	                    // We don't use the supplied x and y parameters here
	                    // because MovablePoint's onMoveEnd semantics suck.
	                    // It returns the mouseX, mouseY without processing them
	                    // through onMove, leaving us with weird fractional moves
	                    var change = kvector.subtract(
	                        currentPoint.coord,
	                        previousCoord
	                    );
	                    options.onMoveEnd(change[0], change[1]);
	                }
	                shape.update();
	            };

	            currentPoint = graphie.addMovablePoint({
	                coord: coord,
	                normalStyle: options.normalPointStyle,
	                highlightStyle: options.highlightPointStyle,
	                constraints: {
	                    fixed: !options.translatable && !options.editable
	                },
	                visible: options.showPoints,
	                snapX: options.snap && options.snap[0] || 0,
	                snapY: options.snap && options.snap[1] || 0,
	                bounded: false, // Don't bound it when placing it on the graph
	                onMove: onMove,
	                onMoveEnd: onMoveEnd
	            });

	            // Bound it when moving
	            // We can't set this earlier, because doing so would mean any
	            // points outside of the graph would be moved into a moved into
	            // a position that doesn't preserve the shape
	            currentPoint.bounded = true;

	            return currentPoint;
	        });

	        shape = ShapeTypes.addShape(graphie, options, points);
	        var removeShapeWithoutPoints = shape.remove;
	        shape.remove = function() {
	            removeShapeWithoutPoints.apply(shape);
	            _.invoke(points, "remove");
	        };
	        return shape;
	    },

	    addShape: function(graphie, options, points) {
	        points = points || options.shape.coords;

	        var types = ShapeTypes._typesOf(options.shape);
	        var typeOptions = options.shape.options ||
	                ShapeTypes.defaultOptions(types);

	        var shapes = ShapeTypes._mapTypes(types, points,
	                function(type, points, i) {
	            var shapeOptions = _.extend({}, options, typeOptions[i]);
	            return ShapeTypes._addType(graphie, type, points, shapeOptions);
	        });

	        var updateFuncs = _.filter(_.pluck(shapes, "update"), _.identity);
	        var update = function() {
	            _.invoke(updateFuncs, "call");
	        };

	        var removeFuncs = _.filter(_.pluck(shapes, "remove"), _.identity);
	        var remove = function() {
	            _.invoke(removeFuncs, "call");
	        };

	        var getOptions = function() {
	            return _.map(shapes, function(shape) {
	                if (shape.getOptions) {
	                    return shape.getOptions();
	                } else {
	                    return {};
	                }
	            });
	        };

	        var toJSON = function() {
	            var coords = _.map(points, function(pt) {
	                if (_.isArray(pt)) {
	                    return pt;
	                } else {
	                    return pt.coord;
	                }
	            });
	            return {
	                type: types,
	                coords: coords,
	                options: getOptions()
	            };
	        };

	        return {
	            type: types,
	            points: points,
	            update: update,
	            remove: remove,
	            toJSON: toJSON,
	            getOptions: getOptions
	        };
	    },

	    equal: function(shape1, shape2) {
	        var types1 = ShapeTypes._typesOf(shape1);
	        var types2 = ShapeTypes._typesOf(shape2);
	        if (types1.length !== types2.length) {
	            return false;
	        }
	        var shapes1 = ShapeTypes._mapTypes(types1, shape1.coords,
	                ShapeTypes._combine);
	        var shapes2 = ShapeTypes._mapTypes(types2, shape2.coords,
	                ShapeTypes._combine);
	        return _.all(_.map(shapes1, function(partialShape1, i) {
	            var partialShape2 = shapes2[i];
	            if (partialShape1.type !== partialShape2.type) {
	                return false;
	            }
	            return ShapeTypes._forType(partialShape1.type).equal(
	                partialShape1.coords,
	                partialShape2.coords
	            );
	        }));
	    },

	    _typesOf: function(shape) {
	        var types = shape.type;
	        if (!_.isArray(types)) {
	            types = [types];
	        }
	        return _.map(types, function(type) {
	            if (type === "polygon") {
	                return "polygon-3";
	            } else {
	                return type;
	            }
	        });
	    },

	    defaultOptions: function(types) {
	        return _.map(types, function(type) {
	            var typeDefaultOptions = ShapeTypes._forType(type).defaultOptions;
	            return _.extend({}, typeDefaultOptions);
	        });
	    },

	    _forType: function(type) {
	        var baseType = type.split("-")[0];
	        return ShapeTypes[baseType];
	    },

	    _mapTypes: function(types, points, func, context) {
	        return _.map(types, function(type, i) {
	            var pointCount = ShapeTypes.getPointCountForType(type);
	            var currentPoints = _.first(points, pointCount);
	            points = _.rest(points, pointCount);
	            return func.call(context, type, currentPoints, i);
	        });
	    },

	    _addType: function(graphie, type, points, options) {
	        var lineCoords = _.isArray(points[0]) ? {
	            coordA: points[0],
	            coordZ: points[1],
	        } : {
	            pointA: points[0],
	            pointZ: points[1],
	        };

	        type = type.split("-")[0];
	        if (type === "polygon") {
	            var polygon = graphie.addMovablePolygon(_.extend({}, options, {
	                fixed: !options.editable,
	                snapX: options.snap && options.snap[0] || 0,
	                snapY: options.snap && options.snap[1] || 0,
	                points: points,
	                constrainToGraph: false
	            }));
	            return {
	                update: polygon.transform.bind(polygon),
	                remove: polygon.remove.bind(polygon)
	            };
	        } else if (type === "line" || type === "lineSegment") {
	            var line = graphie.addMovableLineSegment(
	                    _.extend({}, options, lineCoords, {
	                movePointsWithLine: true,
	                fixed: true,
	                constraints: {
	                    fixed: true
	                },
	                extendLine: (type === "line")
	            }));

	            // TODO(jack): Hide points on uneditable lines when translation
	            // is a vector.
	            // We can't just remove the points yet, because they are the
	            // translation handle for the line.
	            return {
	                update: line.transform.bind(line, true),
	                remove: line.remove.bind(line)
	            };
	        } else if (type === "angle") {
	            // If this angle is editable, we want to be able to make angles
	            // both larger and smaller than 180 degrees.
	            // If this angle is not editable, it should always maintain
	            // it's angle measure, even if it is reflected (causing the
	            // clockwise-ness of the points to change)
	            var shouldChangeReflexivity = options.editable ? null : false;

	            var angle = graphie.addMovableAngle({
	                angleLabel: "$deg0",
	                fixed: true,
	                points: points,
	                normalStyle: options.normalStyle,
	                reflex: options.reflex
	            });

	            // Hide non-vertex points on uneditable angles
	            if (!_.isArray(points[0]) && !options.editable) {
	                points[0].remove();
	                points[2].remove();
	            }
	            return {
	                update: angle.update.bind(angle, shouldChangeReflexivity),
	                remove: angle.remove.bind(angle),
	                getOptions: function() {
	                    return {
	                        reflex: angle.isReflex()
	                    };
	                }
	            };
	        } else if (type === "circle") {
	            var perimeter = {
	                // temporary object for the first removal
	                remove: _.identity
	            };
	            var redrawPerim = function() {
	                var coord0 = points[0].coord || points[0];
	                var coord1 = points[1].coord || points[1];
	                var radius = kpoint.distanceToPoint(coord0, coord1);
	                perimeter.remove();
	                perimeter = graphie.circle(coord0, radius, _.extend({
	                    stroke: KhanUtil.DYNAMIC,
	                    "stroke-width": 2,
	                }, options.normalStyle));
	            };

	            redrawPerim();
	            if (points[1].remove && !options.editable) {
	                points[1].remove();
	            }

	            return {
	                update: redrawPerim,
	                remove: function() {
	                    // Not _.bind because the remove function changes
	                    // when the perimeter is redrawn
	                    perimeter.remove();
	                }
	            };
	        } else if (type === "point") {
	            // do nothing
	            return {
	                update: null,
	                remove: null
	            };
	        } else {
	            throw new Error("Invalid shape type " + type);
	        }
	    },

	    _combine: function(type, coords) {
	        return {
	            type: type,
	            coords: coords
	        };
	    },

	    polygon: {
	        equal: orderInsensitiveCoordsEqual
	    },

	    line: {
	        equal: kline.equal
	    },

	    lineSegment: {
	        equal: orderInsensitiveCoordsEqual
	    },

	    angle: {
	        equal: function(points1, points2) {
	            if (!kpoint.equal(points1[1], points2[1])) {
	                return false;
	            }

	            var line1_0 = [points1[1], points1[0]];
	            var line1_2 = [points1[1], points1[2]];
	            var line2_0 = [points2[1], points2[0]];
	            var line2_2 = [points2[1], points2[2]];

	            var equalUnflipped = kray.equal(line1_0, line2_0) &&
	                    kray.equal(line1_2, line2_2);
	            var equalFlipped = kray.equal(line1_0, line2_2) &&
	                    kray.equal(line1_2, line2_0);

	            return equalUnflipped || equalFlipped;
	        },

	        defaultOptions: {
	            reflex: false
	        }
	    },

	    circle: {
	        equal: function(points1, points2) {
	            var radius1 = kpoint.distanceToPoint(points1[0], points1[1]);
	            var radius2 = kpoint.distanceToPoint(points2[0], points2[1]);
	            return kpoint.equal(points1[0], points2[0]) &&
	                knumber.equal(radius1, radius2);
	        }
	    },

	    point: {
	        equal: kpoint.equal
	    }
	};


	var ToolSettings = React.createClass({displayName: 'ToolSettings',
	    getDefaultProps: function() {
	        return {
	            allowFixed: true
	        };
	    },

	    render: function() {
	        return React.createElement("div", null, 
	            this.props.name, ":", ' ', 
	            " ", 
	            React.createElement(PropCheckBox, {
	                label: "enabled:", 
	                enabled: this.props.settings.enabled, 
	                onChange: this.props.onChange}), 
	            " ", 
	            this.props.settings.enabled &&
	                React.createElement(PropCheckBox, {
	                    label: "required:", 
	                    required: this.props.settings.required, 
	                    onChange: this.props.onChange}), 
	            
	            this.props.settings.enabled &&
	                React.createElement(InfoTip, null, 
	                    "'Required' will only grade the answer as correct if the" + ' ' +
	                    "student has used at least one such transformation."
	                ), 
	            
	            " ", 
	            this.props.allowFixed && this.props.settings.enabled &&
	                React.createElement(PropCheckBox, {
	                    label: "fixed:", 
	                    fixed: this.props.settings.constraints.fixed, 
	                    onChange: this.changeConstraints}), 
	            
	            this.props.allowFixed && this.props.settings.enabled &&
	                React.createElement(InfoTip, null, 
	                    "Enable 'fixed' to prevent the student from repositioning" + ' ' +
	                    "the tool. The tool will appear in the position at which it" + ' ' +
	                    "is placed in the editor below."
	                )
	            
	        );
	    },

	    changeConstraints: function(changed) {
	        var newConstraints = _.extend({}, this.props.constraints, changed);
	        this.props.onChange({
	            constraints: newConstraints
	        });
	    }
	});


	var TransformationExplorerSettings = React.createClass({displayName: 'TransformationExplorerSettings',
	    render: function() {

	        return React.createElement("div", {className: "transformer-settings"}, 
	            React.createElement("div", null, 
	                ' ', "Mode:", ' ', 
	                React.createElement("select", {value: this.getMode(), 
	                        onChange: this.changeMode}, 
	                    React.createElement("option", {value: "interactive,dynamic"}, 
	                        ' ', "Exploration with text", ' '
	                    ), 
	                    React.createElement("option", {value: "interactive,static"}, 
	                        ' ', "Exploration without text", ' '
	                    ), 
	                    React.createElement("option", {value: "dynamic,interactive"}, 
	                        ' ', "Formal with movement", ' '
	                    ), 
	                    React.createElement("option", {value: "static,interactive"}, 
	                        ' ', "Formal without movement", ' '
	                    )
	                ), 
	                React.createElement(InfoTip, null, 
	                    React.createElement("ul", null, 
	                        React.createElement("li", null, 
	                            React.createElement("b", null, "Exploration:"), " Students create" + ' ' +
	                            "transformations with tools on the graph.", ' '
	                        ), 
	                        React.createElement("li", null, 
	                            React.createElement("b", null, "Formal with movement:"), " Students specify" + ' ' +
	                            "transformations mathematically in the" + ' ' +
	                            "transformation list. Graph shows the results of" + ' ' +
	                            "these transformations.", ' '
	                        ), 
	                        React.createElement("li", null, 
	                            React.createElement("b", null, "Formal without movement:"), " Students specify" + ' ' +
	                            "transformations mathematically in the" + ' ' +
	                            "transformation list. Graph does not update.", ' '
	                        )
	                    )
	                )
	            ), 
	            React.createElement(ToolSettings, {
	                    name: "Translations", 
	                    settings: this.props.tools.translation, 
	                    allowFixed: false, 
	                    onChange: this.changeHandlerFor("translation")}), 
	            React.createElement(ToolSettings, {
	                    name: "Rotations", 
	                    settings: this.props.tools.rotation, 
	                    onChange: this.changeHandlerFor("rotation")}), 
	            React.createElement(ToolSettings, {
	                    name: "Reflections", 
	                    settings: this.props.tools.reflection, 
	                    onChange: this.changeHandlerFor("reflection")}), 
	            React.createElement(ToolSettings, {
	                    name: "Dilations", 
	                    settings: this.props.tools.dilation, 
	                    onChange: this.changeHandlerFor("dilation")}), 
	            React.createElement(PropCheckBox, {
	                    label: "Draw Solution:", 
	                    drawSolutionShape: this.props.drawSolutionShape, 
	                    onChange: this.props.onChange})
	        );
	    },

	    getMode: function() {
	        return this.props.graphMode + "," + this.props.listMode;
	    },

	    changeMode: function(e) {
	        var selected = e.target.value;
	        var modes = selected.split(",");

	        this.props.onChange({
	            graphMode: modes[0],
	            listMode: modes[1]
	        });
	    },

	    changeHandlerFor: function(toolName) {
	        return function(change)  {
	            var newTools = _.clone(this.props.tools);
	            newTools[toolName] = _.extend({}, this.props.tools[toolName],
	                    change);

	            this.props.onChange({
	                tools: newTools
	            });
	        }.bind(this);
	    }
	});


	var TransformationsShapeEditor = React.createClass({displayName: 'TransformationsShapeEditor',
	    render: function() {
	        return React.createElement("div", null, 
	            React.createElement(Graph, {
	                ref: "graph", 
	                box: this.props.graph.box, 
	                range: this.props.graph.range, 
	                labels: this.props.graph.labels, 
	                step: this.props.graph.step, 
	                gridStep: this.props.graph.gridStep, 
	                markings: this.props.graph.markings, 
	                backgroundImage: this.props.graph.backgroundImage, 
	                onNewGraphie: this.setupGraphie}), 
	            React.createElement("select", {
	                    key: "type-select", 
	                    value: this.getTypeString(this.props.shape.type), 
	                    onChange: this.changeType}, 
	                React.createElement("option", {value: "polygon-3"}, "Triangle"), 
	                React.createElement("option", {value: "polygon-4"}, "Quadrilateral"), 
	                React.createElement("option", {value: "polygon-5"}, "Pentagon"), 
	                React.createElement("option", {value: "polygon-6"}, "Hexagon"), 
	                React.createElement("option", {value: "line"}, "Line"), 
	                React.createElement("option", {value: "line,line"}, "2 lines"), 
	                React.createElement("option", {value: "lineSegment"}, "Line segment"), 
	                React.createElement("option", {value: "lineSegment,lineSegment"}, 
	                    ' ', "2 line segments", ' '
	                ), 
	                React.createElement("option", {value: "angle"}, "Angle"), 
	                React.createElement("option", {value: "circle"}, "Circle")
	            )
	        );
	    },

	    /* Return the option string for a given type */
	    getTypeString: function(type) {
	        if (_.isArray(type)) {
	            return _.map(type, this.getTypeString).join(",");
	        } else if (type === "polygon") {
	            return "polygon-" + this.props.shape.coords.length;
	        } else {
	            return type;
	        }
	    },

	    /* Change the type on the window event e
	     *
	     * e.target.value is the new type string
	     */
	    changeType: function(e) {
	        var types = String(e.target.value).split(",");
	        var pointCount = arraySum(_.map(
	                types,
	                ShapeTypes.getPointCountForType
	        ));

	        var radius = scaleToRange(4, this.refs.graph.props.range);
	        var offset = (1 / 2 - 1 / pointCount) * 180;
	        var coords = _.times(pointCount, function(i) {
	            return kpoint.rotateDeg([radius, 0],
	                360 * i / pointCount + offset);
	        });

	        this.props.onChange({
	            shape: {
	                type: types,
	                coords: coords,
	                options: ShapeTypes.defaultOptions(types)
	            }
	        });
	    },

	    componentDidUpdate: function(prevProps) {
	        if (!deepEq(prevProps.shape, this.props.shape)) {
	            this.refs.graph.reset();
	        }
	    },

	    updateCoords: function() {
	        this.props.onChange({
	            shape: this.shape.toJSON()
	        });
	    },

	    setupGraphie: function(graphie) {
	        this.shape = ShapeTypes.addMovableShape(graphie, {
	            editable: true,
	            snap: graphie.snap,
	            shape: this.props.shape,
	            onMoveEnd: this.updateCoords
	        });
	    },

	});

	var TransformationListItem = TransformOps.ListItem;

	var TransformationList = React.createClass({displayName: 'TransformationList',
	    render: function() {
	        if (this.props.mode === "static") {
	            return React.createElement("span", null);  // don't render anything
	        }

	        var transformationList = _.map(
	            this.props.transformations,
	            function(transform, i) {
	                return React.createElement(TransformationListItem, {
	                            ref: "transformation" + i, 
	                            key: "transformation" + i, 
	                            transform: transform, 
	                            mode: this.props.mode, 
	                            onChange: this.handleChange, 
	                            onFocus: _.partial(this.props.onFocus, "" + i), 
	                            onBlur: _.partial(this.props.onBlur, "" + i), 
	                            apiOptions: this.props.apiOptions});
	            },
	            this
	        );

	        return React.createElement("div", {className: "perseus-transformation-list"}, 
	            transformationList
	        );
	    },

	    _transformationRefs: function() {
	        return _.times(this.props.transformations.length, function(i)  {
	            return this.refs["transformation" + i];
	        }.bind(this));
	    },

	    value: function() {
	        return _.invoke(this._transformationRefs(), "value");
	    },

	    handleChange: function(changed, callback) {
	        this.props.onChange(this.value(), callback);
	    },

	    focusLast: function() {
	        var transformationRefs = this._transformationRefs();
	        if (transformationRefs.length !== 0) {
	            _.last(transformationRefs).focus();
	        }
	    }
	});

	var ToolButton = React.createClass({displayName: 'ToolButton',
	    render: function() {
	        var classes = this.props.toggled ?
	            "simple-button exercise-orange toggled highlighted-tool-button" :
	            "simple-button";

	        return React.createElement("button", {
	                type: "button", 
	                className: classes, 
	                onClick: this.props.onClick, 
	                onTouchStart: captureScratchpadTouchStart}, 
	            this.props.children
	        );
	    }
	});

	var ToolsBar = React.createClass({displayName: 'ToolsBar',
	    getInitialState: function() {
	        return {
	            selected: null
	        };
	    },

	    render: function() {
	        var tools = _.map(Transformations, function(tool, type) {
	            if (this.props.enabled[type]) {
	                return React.createElement(ToolButton, {
	                        key: type, 
	                        toggled: this.state.selected === type, 
	                        onClick: this.changeSelected.bind(this, type)}, 
	                    tool.verbName
	                );
	            }
	        }, this);

	        return React.createElement("div", {className: "transformer-tools-bar"}, 
	            React.createElement("span", {className: "simple-button-group"}, 
	                tools
	            ), 
	            React.createElement("button", {
	                    className: "transformer-undo-button simple-button", 
	                    type: "button", 
	                    onClick: this.props.onUndoClick, 
	                    onTouchStart: captureScratchpadTouchStart}, 
	                React.createElement("span", {className: "icon-undo"}), 
	                " ", 
	                "Undo"
	            ), 
	            React.createElement("div", {className: "clear"})
	        );
	    },

	    changeSelected: function(tool) {
	        this.props.removeTool(this.state.selected);

	        if (!tool || tool === this.state.selected) {
	            this.setState({
	                selected: null
	            });
	        } else {
	            this.props.addTool(tool);
	            this.setState({
	                selected: tool
	            });
	        }
	    }
	});

	var AddTransformBar = React.createClass({displayName: 'AddTransformBar',
	    render: function() {
	        var tools = _.map(Transformations, function(tool, type) {
	            if (this.props.enabled[type]) {
	                return React.createElement(ToolButton, {
	                        key: type, 
	                        toggled: false, 
	                        onClick: this.changeSelected.bind(this, type)}, 
	                    React.createElement("span", {className: "icon-plus"}), 
	                    " ", 
	                    tool.nounName
	                );
	            }
	        }, this);

	        return React.createElement("div", {className: "transformer-tools-bar"}, 
	            tools, 
	            React.createElement("button", {
	                    className: "transformer-undo-button simple-button", 
	                    type: "button", 
	                    onClick: this.props.onUndoClick, 
	                    onTouchStart: captureScratchpadTouchStart}, 
	                React.createElement("span", {className: "icon-undo"}), 
	                " ", 
	                "Undo"
	            ), 
	            React.createElement("div", {className: "clear"})
	        );
	    },

	    changeSelected: function(tool) {
	        if (tool) {
	            this.props.addTool(tool);
	        }
	    }
	});

	var Transformer = React.createClass({displayName: 'Transformer',
	    getDefaultProps: function() {
	        return _.defaults({
	            transformations: []
	        }, defaultTransformerProps);
	    },

	    render: function() {
	        // Fill in any missing value in this.props.graph
	        // this can happen because the graph json doesn't include
	        // box, for example
	        var graph = _.extend(
	                defaultGraphProps(this.props.graph, defaultBoxSize),
	                this.props.graph
	        );

	        var interactiveToolsMode = this.props.graphMode === "interactive";

	        var ToolsBarClass = interactiveToolsMode ?
	                ToolsBar :
	                AddTransformBar;

	        // This style is applied inline because it is dependent on the
	        // size of the graph as set by the graph.box prop, and this also
	        // lets us specify it in the same place the graph's width is
	        // specified.
	        var toolsBar = React.createElement("div", {style: {width: graph.box[0]}}, 
	            React.createElement(ToolsBarClass, {
	                ref: "toolsBar", 
	                enabled: pluckObject(this.props.tools, "enabled"), 
	                addTool: this.addTool, 
	                removeTool: this.removeTool, 
	                onUndoClick: this.handleUndoClick})
	        );

	        return React.createElement("div", {className: "perseus-widget " +
	                        "perseus-widget-transformer"}, 
	            React.createElement(Graph, {
	                ref: "graph", 
	                box: graph.box, 
	                range: graph.range, 
	                labels: graph.labels, 
	                step: graph.step, 
	                gridStep: graph.gridStep, 
	                markings: graph.markings, 
	                backgroundImage: graph.backgroundImage, 
	                showProtractor: graph.showProtractor, 
	                onNewGraphie: this.setupGraphie}), 

	            !interactiveToolsMode && (
	                "Add transformations below:"
	            ), 

	            this.props.graphMode === "static" && [
	                React.createElement("br", {key: "static-br"}),
	                React.createElement("em", {key: "static-nomove"}, 
	                    ' ', "Note: For this question, the shape will not move.", ' '
	                )
	            ], 

	            interactiveToolsMode && toolsBar, 

	            React.createElement(TransformationList, {
	                ref: "transformationList", 
	                mode: this.props.listMode, 
	                transformations: this.props.transformations, 
	                onChange: this.setTransformationProps, 
	                onFocus: this._handleFocus, 
	                onBlur: this._handleBlur, 
	                apiOptions: this.props.apiOptions}), 

	            !interactiveToolsMode && toolsBar

	        );
	    },

	    componentDidUpdate: function(prevProps) {
	        if (this.shouldSetupGraphie(this.props, prevProps)) {
	            this.refs.graph.reset();
	        } else if (!deepEq(this.props.transformations,
	                this.transformations)) {
	            this.setTransformations(this.props.transformations);
	        }
	    },

	    shouldSetupGraphie: function(nextProps, prevProps) {
	        if (!deepEq(prevProps.starting, nextProps.starting)) {
	            return true;
	        } else if (prevProps.graphMode !== nextProps.graphMode) {
	            return true;
	        } else if (prevProps.listMode !== nextProps.listMode) {
	            return true;
	        } else if (prevProps.drawSolutionShape !==
	                nextProps.drawSolutionShape) {
	            return true;
	        } else if (nextProps.drawSolutionShape && !deepEq(
	                prevProps.correct.shape, nextProps.correct.shape)) {
	            return true;
	        } else if (!deepEq(this.tools, nextProps.tools)) {
	            return true;
	        } else {
	            return false;
	        }
	    },

	    graphie: function() {
	        return this.refs.graph.graphie();
	    },

	    setupGraphie: function() {
	        var self = this;

	        var graphie = this.graphie();

	        // A background image of our solution:
	        if (this.props.drawSolutionShape &&
	                this.props.correct.shape &&
	                this.props.correct.shape.coords) {
	            ShapeTypes.addShape(graphie, {
	                fixed: true,
	                shape: self.props.correct.shape,
	                normalStyle: {
	                    stroke: KhanUtil.GRAY,
	                    "stroke-dasharray": "",
	                    "stroke-width": 2
	                }
	            });
	        }

	        this.currentTool = null;
	        this.refs.toolsBar.changeSelected(null);
	        this.addTransformerShape(this.props.starting.shape,
	                /* translatable */ false);
	        this.setTransformations(this.props.transformations);

	        // Save a copy of our tools so that we can check future
	        // this.props.tools changes against them
	        // This seems weird, but gives us an easy way to tell whether
	        // props changes were self-inflicted (for which a graphie reset
	        // is not required, and is in fact a bad idea right now because
	        // of resetting the size of the dilation tool).
	        // TODO (jack): A deepClone method would be nice here
	        this.tools = {
	            translation: _.clone(this.props.tools.translation),
	            rotation: _.clone(this.props.tools.rotation),
	            reflection: _.clone(this.props.tools.reflection),
	            dilation: _.clone(this.props.tools.dilation)
	        };
	    },

	    /* Applies all transformations in `transformations`
	     * to the starting shape, and updates this.transformations
	     * to reflect this
	     *
	     * Usually called with this.props.transformations
	     */
	    setTransformations: function(transformations) {
	        this.resetCoords();
	        this.transformations = _.clone(transformations);
	        _.each(this.transformations, this.applyTransform);
	    },

	    // the polygon that we transform
	    addTransformerShape: function(shape, translatable) {
	        var self = this;
	        var graphie = this.graphie();

	        this.shape = ShapeTypes.addMovableShape(graphie, {
	            shape: shape,
	            editable: false,
	            showPoints: (this.props.graphMode !== "static"),
	            translatable: translatable,
	            onMove: function (dX, dY) {
	                dX = KhanUtil.roundToNearest(graphie.snap[0], dX);
	                dY = KhanUtil.roundToNearest(graphie.snap[1], dY);
	                self.addTransform({
	                    type: "translation",
	                    vector: [dX, dY]
	                });
	                return [dX, dY];
	            },
	            normalPointStyle: {
	                fill: (translatable ? KhanUtil.INTERACTIVE
	                                    : KhanUtil.DYNAMIC),
	                stroke: (translatable ? KhanUtil.INTERACTIVE
	                                      : KhanUtil.DYNAMIC)
	            },
	            highlightPointStyle: {
	                fill: KhanUtil.INTERACTING,
	                stroke: KhanUtil.INTERACTING
	            }
	        });
	    },

	    addTool: function(toolId) {
	        var self = this;

	        if (this.props.graphMode === "interactive") {
	            if (toolId === "translation") {
	                this.currentTool = this.addTranslationTool();
	            } else if (toolId === "rotation") {
	                this.currentTool = this.addRotationTool();
	            } else if (toolId === "reflection") {
	                this.currentTool = this.addReflectionTool();
	            } else if (toolId === "dilation") {
	                this.currentTool = this.addDilationTool();
	            } else {
	                throw new Error("Invalid tool id: " + toolId);
	            }
	        } else {
	            var transform;
	            if (toolId === "translation") {
	                transform = {
	                    type: toolId,
	                    vector: [null, null]
	                };
	            } else if (toolId === "rotation") {
	                transform = {
	                    type: toolId,
	                    center: [null, null],
	                    angleDeg: null
	                };
	            } else if (toolId === "reflection") {
	                // Reflections with nulls in them won't be applied until
	                // fills in the blanks
	                transform = {
	                    type: toolId,
	                    line: [[null, null], [null, null]]
	                };
	            } else if (toolId === "dilation") {
	                transform = {
	                    type: toolId,
	                    center: [null, null],
	                    scale: null
	                };
	            } else {
	                throw new Error("Invalid tool id: " + toolId);
	            }

	            this.doTransform(transform, function() {
	                self.refs.transformationList.focusLast();
	            });
	        }
	    },

	    removeTool: function(toolId) {
	        if (this.currentTool) {
	            this.currentTool.remove();
	        }
	        this.currentTool = null;
	    },

	    addTranslationTool: function() {
	        var self = this;
	        this.shape.remove();
	        this.addTransformerShape(this.shape.toJSON(),
	                /* translatable */ true);

	        return {
	            remove: function() {
	                self.shape.remove();
	                self.addTransformerShape(self.shape.toJSON(),
	                        /* translatable */ false);
	            }
	        };
	    },

	    // Snaps a coord to this.graphie()'s snap
	    snapCoord: function(coord) {
	        var graphie = this.graphie();
	        return _.map(coord, function (val, dim) {
	            return KhanUtil.roundToNearest(graphie.snap[dim], val);
	        });
	    },

	    // Normalize the coords into something that fits the new 45 degree
	    // reflection line.
	    normalizeReflectionCoords: function(messyCoords) {
	        var midpoint = this.snapCoord(kline.midpoint(messyCoords));
	        var origDirectionPolar = kvector.polarDegFromCart(
	            kvector.subtract(messyCoords[0], messyCoords[1])
	        );
	        var directionPolar = [
	            1,
	            KhanUtil.roundToNearest(45, origDirectionPolar[1])
	        ];
	        var direction = kvector.cartFromPolarDeg(directionPolar);
	        var coords = _.map([-1, 1], function(directionCoefficient) {
	            var coord = kvector.add(
	                midpoint,
	                kvector.scale(
	                    direction,
	                    directionCoefficient *
	                        this.scaleToCurrentRange(REFLECT_ROTATE_HANDLE_DIST)
	                )
	            );
	            return this.snapCoord(coord);
	        }, this);
	        return coords;
	    },

	    addReflectionTool: function() {
	        var options = this.props.tools.reflection;
	        if (!options.enabled) {
	            return;
	        }
	        var self = this;
	        var graphie = this.refs.graph.graphie();

	        var updateReflectionTool = function() {
	            self.changeTool("reflection", {
	                coords: _.pluck(reflectPoints, "coord")
	            });
	        };

	        var coords = this.normalizeReflectionCoords(options.coords);

	        // The points defining the line of reflection; hidden from the
	        // user.
	        var reflectPoints = _.map(coords, function(coord) {
	            return graphie.addMovablePoint({
	                coord: coord,
	                visible: false
	            });
	        }, this);

	        // the line of reflection
	        // TODO(jack): graphie.style here is a hack to prevent the dashed
	        // style from leaking into the rest of the shapes. Remove when
	        // graphie.addMovableLineSegment doesn't leak styles anymore.
	        var reflectLine;
	        var normalColor = colorForTool(options);
	        graphie.style({}, function() {
	            reflectLine = graphie.addMovableLineSegment({
	                fixed: options.constraints.fixed,
	                constraints: options.constraints,
	                pointA: reflectPoints[0],
	                pointZ: reflectPoints[1],
	                snapX: graphie.snap[0],
	                snapY: graphie.snap[1],
	                extendLine: true,
	                normalStyle: {
	                    "stroke": normalColor,
	                    "stroke-width": 2,
	                    "stroke-dasharray": "- "
	                },
	                highlightStyle: {
	                    "stroke": KhanUtil.INTERACTING,
	                    "stroke-width": 2,
	                    "stroke-dasharray": "- " // TODO(jack) solid doesn't
	                                             // work here, but would be
	                                             // nicer
	                },
	                movePointsWithLine: true,
	                onMoveEnd: updateReflectionTool
	            });
	        });

	        // the "button" point in the center of the line of reflection
	        var reflectButton = graphie.addReflectButton({
	            fixed: options.constraints.fixed,
	            line: reflectLine,
	            size: this.scaleToCurrentRange(REFLECT_BUTTON_SIZE),
	            onClick: function() {
	                self.doTransform({
	                    type: "reflection",
	                    line: _.pluck(reflectPoints, "coord")
	                });
	                if (reflectRotateHandle) {
	                    // flip the rotation handle
	                    reflectRotateHandle.setCoord(kvector.add(
	                        reflectButton.coord,
	                        kvector.subtract(
	                            reflectButton.coord,
	                            reflectRotateHandle.coord
	                        )
	                    ));
	                    reflectRotateHandle.update();
	                }
	            },
	            normalStyle: {
	                stroke: normalColor,
	                "stroke-width": 2,
	                fill: normalColor
	            },
	            highlightStyle: {
	                stroke: KhanUtil.INTERACTING,
	                "stroke-width": 3,
	                fill: KhanUtil.INTERACTING
	            },
	            onMoveEnd: updateReflectionTool
	        });

	        var reflectRotateHandle = null;
	        if (!options.constraints.fixed) {
	            // The rotation handle for rotating the line of reflection
	            var initRotateHandleAngle = kvector.polarDegFromCart(
	                kvector.subtract(
	                    reflectPoints[1].coord,
	                    reflectPoints[0].coord
	                )
	            )[1] + 90; // 90 degrees off of the line
	            reflectRotateHandle = graphie.addRotateHandle({
	                center: reflectButton,
	                radius: this.scaleToCurrentRange(REFLECT_ROTATE_HANDLE_DIST),
	                angleDeg: initRotateHandleAngle,
	                width: this.scaleToCurrentRange(0.24),
	                hoverWidth: this.scaleToCurrentRange(0.4),
	                lengthAngle: 17,
	                onMove: function(newAngle) {
	                    return KhanUtil.roundToNearest(45, newAngle);
	                },
	                onMoveEnd: updateReflectionTool
	            });
	        }

	        // Move the reflectButton and reflectRotateHandle with the line
	        $(reflectLine).on("move",
	                function() {
	            reflectButton.update();
	            $(reflectButton).trigger("move"); // update the rotation handle,
	                    // which watches for this in ke/utils/interactive.js.
	        });

	        // Update the line and reflect button when the reflectRotateHandle is
	        // rotated
	        if (reflectRotateHandle) {
	            $(reflectRotateHandle).on("move", function() {
	                var rotateHandleApprox = self.snapCoord(
	                    reflectRotateHandle.coord
	                );

	                var rotateVector = kvector.subtract(
	                    rotateHandleApprox,
	                    reflectButton.coord
	                );

	                var flipped = reflectButton.isFlipped() ? 1 : 0;
	                reflectPoints[flipped].setCoord(kvector.add(
	                    reflectButton.coord,
	                    kvector.rotateDeg(rotateVector, 90)
	                ));
	                reflectPoints[1 - flipped].setCoord(kvector.add(
	                    reflectButton.coord,
	                    kvector.rotateDeg(rotateVector, -90)
	                ));

	                reflectLine.transform(true);
	                reflectButton.update();
	            });
	        }

	        return {
	            remove: function() {
	                reflectButton.remove();
	                if (reflectRotateHandle) {
	                    reflectRotateHandle.remove();
	                }
	                reflectLine.remove();
	                reflectPoints[0].remove();
	                reflectPoints[1].remove();
	            }
	        };
	    },

	    /* Scales a distance from the default range of
	     * [-10, 10] to the current this.props.graph.range
	     *
	     * Used for sizing various transformation tools
	     * (rotation handle, dilation circle)
	     */
	    scaleToCurrentRange: function(dist) {
	        return scaleToRange(dist, this.refs.graph.props.range);
	    },

	    addRotationTool: function() {
	        var options = this.props.tools.rotation;
	        if (!options.enabled) {
	            return;
	        }
	        var self = this;
	        var graphie = this.refs.graph.graphie();

	        var pointColor = colorForTool(options);
	        // The center of our rotation, which can be moved to change the
	        // center of rotation
	        this.rotatePoint = graphie.addMovablePoint({
	            constraints: options.constraints,
	            coord: options.coord,
	            snapX: graphie.snap[0],
	            snapY: graphie.snap[1],
	            normalStyle: {               // ugh, this seems to be a global and
	                "stroke-dasharray": "",  // is set to dash above
	                stroke: pointColor,
	                fill: pointColor
	            },
	            highlightStyle: {
	                "stroke-dasharray": "",
	                stroke: KhanUtil.INTERACTING,
	                fill: KhanUtil.INTERACTING
	            }
	        });

	        // The point that we move around the center of rotation to actually
	        // cause rotations
	        this.rotateHandle = graphie.addRotateHandle({
	            center: this.rotatePoint,
	            radius: this.scaleToCurrentRange(ROTATE_HANDLE_DIST),
	            width: this.scaleToCurrentRange(0.24),
	            hoverWidth: this.scaleToCurrentRange(0.4),
	            onMove: function(newAngle, oldAngle) {
	                var transform = self.getRotationTransformFromAngle(
	                    self.rotatePoint.coord,
	                    newAngle - oldAngle
	                );

	                // Rotate polygon with rotateHandle
	                self.doTransform(transform);

	                return oldAngle + transform.angleDeg;
	            }
	        });

	        // Update tools.rotation.coord
	        this.rotatePoint.onMoveEnd = function(x, y) {
	            self.changeTool("rotation", {
	                coord: [x, y]
	            });
	        };


	        return {
	            remove: function() {
	                self.rotateHandle.remove();
	                self.rotatePoint.remove();
	            }
	        };
	    },

	    addDilationTool: function() {
	        var options = this.props.tools.dilation;
	        if (!options.enabled) {
	            return;
	        }
	        var self = this;
	        var graphie = this.refs.graph.graphie();

	        var pointColor = colorForTool(options);
	        // the circle for causing dilation transforms
	        self.dilationCircle = graphie.addCircleGraph({
	            centerConstraints: options.constraints,
	            center: options.coord,
	            radius: self.scaleToCurrentRange(2),
	            snapX: graphie.snap[0],
	            snapY: graphie.snap[1],
	            minRadius: self.scaleToCurrentRange(1),
	            snapRadius: self.scaleToCurrentRange(0.5),
	            onResize: function(newRadius, oldRadius) {
	                self.doTransform({
	                    type: "dilation",
	                    center: self.dilationCircle.centerPoint.coord,
	                    scale: newRadius/oldRadius
	                });
	            },
	            circleNormalStyle: {
	                "stroke": pointColor,
	                "stroke-width": 2,
	                "stroke-dasharray": "- ",
	                "fill-opacity": 0
	            },
	            circleHighlightStyle: {
	                "stroke": KhanUtil.INTERACTING,
	                "stroke-width": 2,
	                "stroke-dasharray": "",
	                "fill": KhanUtil.INTERACTING,
	                "fill-opacity": 0.05
	            },
	            centerNormalStyle: {
	                "stroke": pointColor,
	                "fill": pointColor,
	                "stroke-width": 2,
	                "stroke-dasharray": ""
	            },
	            centerHighlightStyle: {
	                "stroke": pointColor,
	                "fill": pointColor,
	                "stroke-width": 2,
	                "stroke-dasharray": ""
	            }
	        });

	        var origOnMoveEnd = this.dilationCircle.centerPoint.onMoveEnd;
	        this.dilationCircle.centerPoint.onMoveEnd = function() {
	            if (origOnMoveEnd) {
	                origOnMoveEnd.apply(this, _.toArray(arguments));
	            }
	            self.changeTool("dilation", {
	                coord: self.dilationCircle.centerPoint.coord
	            });
	        };

	        return {
	            remove: function() {
	                self.dilationCircle.remove();
	            }
	        };
	    },

	    // returns a transformation object representing a rotation
	    // rounds the angle to the nearest 15 degrees
	    getRotationTransformFromAngle: function(center, angleChanged) {
	        angleChanged = (angleChanged + 360) % 360;
	        if (angleChanged > 180) {
	            angleChanged -= 360;
	        }
	        var roundedAngle = Math.round(
	                angleChanged / ROTATE_SNAP_DEGREES
	            ) * ROTATE_SNAP_DEGREES;

	        return {
	            type: "rotation",
	            center: center,
	            angleDeg: roundedAngle
	        };
	    },

	    // apply and save a transform
	    doTransform: function(transform, callback) {
	        this.applyTransform(transform);
	        this.addTransform(transform, callback);
	    },

	    // apply a transform to our polygon (without modifying our transformation
	    // list)
	    applyTransform: function(transform) {
	        if (this.props.graphMode !== "static") {
	            var transformFunc = TransformOps.apply(transform);
	            this.applyCoordTransformation(transformFunc);
	        }
	    },

	    // transform our polygon by transforming each point using a given function
	    applyCoordTransformation: function(pointTransform) {
	        _.each(this.shape.points, function(point) {
	            var newCoord = pointTransform(point.coord);
	            point.setCoord(newCoord);
	        });
	        this.shape.update();
	    },

	    resetCoords: function() {
	        var startCoords = this.props.starting.shape.coords;
	        _.each(this.shape.points, function(point, i) {
	            point.setCoord(startCoords[i]);
	        });
	        this.shape.update();
	    },

	    // Remove the last transformation
	    handleUndoClick: function() {
	        this.refs.toolsBar.changeSelected(null);
	        if (this.props.transformations.length) {
	            this.props.onChange({
	                transformations: _.initial(this.props.transformations)
	            });
	        }
	    },

	    setTransformationProps: function(newTransfomationList, callback) {
	        this.props.onChange({
	            transformations: newTransfomationList
	        }, callback);
	    },

	    // add a transformation to our props list of transformation
	    addTransform: function(transform, callback) {
	        this.transformations = TransformOps.append(
	                this.transformations,
	                transform
	        );
	        this.props.onChange({
	            transformations: _.clone(this.transformations)
	        }, callback);
	    },

	    changeTool: function(tool, changes) {
	        var newTools = _.clone(this.props.tools);
	        newTools[tool] = _.extend({}, this.props.tools[tool], changes);
	        this.tools[tool] = _.clone(newTools[tool]);
	        this.props.onChange({
	            tools: newTools,
	        });
	    },

	    simpleValidate: function(rubric) {
	        return Transformer.validate(this.getUserInput(), rubric);
	    },

	    /**
	     * Calculate where the coordinates would be if they were
	     * moved, even if we're in formal mode with no movement
	     * (and thus the actual movablepoints may not have moved
	     */
	    getCoords: function() {
	        var startCoords = this.props.starting.shape.coords;
	        var transforms = this.props.transformations;
	        return _.reduce(transforms, function (coords, transform) {
	            return _.map(coords, TransformOps.apply(transform));
	        }, startCoords);
	    },

	    getEditorJSON: function() {
	        var json = _.pick(this.props, "grading", "starting", "graphMode",
	                "listMode", "tools", "drawSolutionShape", "gradeEmpty");
	        json.graph = this.refs.graph.toJSON();
	        json.version = 1.2; // Give us some safety to change the format
	                            // when we realize that I wrote
	                            // a horrible json spec for this widget

	        json.answer = this.getUserInput();
	        return json;
	    },

	    getUserInput: function() {
	        return {
	            transformations: this.props.transformations,
	            // This doesn't call this.shape.toJSON() because that doesn't
	            // handle coordinates in formal mode without movement, since
	            // the movablepoints never move
	            shape: {
	                type: this.shape.type,
	                coords: this.getCoords(),
	                options: this.shape.getOptions()
	            }
	        };
	    },

	    statics: {
	        displayMode: "block"
	    },

	    /* InputPath API */

	    _handleFocus: function() {
	        var path = Array.prototype.slice.call(arguments);
	        this.props.onFocus(path);
	    },

	    _handleBlur: function() {
	        var path = Array.prototype.slice.call(arguments);
	        this.props.onBlur(path);
	    },

	    _getTransformationForID: function(transformationID) {
	        // Returns the 'transformation' component corresponding to a given ID
	        var refPath = [
	            "transformationList",
	            "transformation" + transformationID
	        ];

	        // Follow the path of references
	        var component = this;
	        _.each(refPath, function(ref)  {
	            component = component.refs[ref];
	        });
	        return component;
	    },

	    getInputPaths: function() {
	        var inputPaths = [];
	        _.each(this.props.transformations, function(transformation, i)  {
	            var transformation = this._getTransformationForID(i);
	            var innerPaths = transformation.getInputPaths();
	            var fullPaths = _.map(innerPaths, function(innerPath)  {
	                return ["" + i].concat(innerPath);
	            });
	            inputPaths = inputPaths.concat(fullPaths);
	        }.bind(this));
	        return inputPaths;
	    },

	    _passToInner: function(functionName, path) {
	        if (!path || !path.length) {
	            return;
	        }

	        // First argument tells us which transformation will receive the call;
	        // remaining arguments are used within that transformation to identify
	        // a specific input.
	        var innerPath = _.rest(path);
	        var args = [innerPath].concat(_.rest(arguments, 2));

	        // Pass arguments down to appropriate 'transformation' component
	        var transformationID = _.head(path);
	        var caller = this._getTransformationForID(transformationID);
	        return caller[functionName].apply(caller, args);
	    },

	    focus: function() {
	        // Just focus the first showing input
	        var inputs = this.getInputPaths();
	        if (inputs.length > 0) {
	            this.focusInputPath(inputs[0]);
	            return true;
	        }
	        return false;
	    },

	    focusInputPath: function(path) {
	        assert(path.length >= 2);
	        return this._passToInner('focusInputPath', path);
	    },

	    blurInputPath: function(path) {
	        assert(path.length >= 2);
	        return this._passToInner('blurInputPath', path);
	    },

	    setInputValue: function(path, value, cb) {
	        assert(path.length >= 2);
	        return this._passToInner('setInputValue', path, value, cb);
	    },

	    getDOMNodeForPath: function(path) {
	        assert(path.length >= 2);
	        return this._passToInner('getDOMNodeForPath', path);
	    },

	    getGrammarTypeForPath: function(path) {
	        assert(path.length >= 2);
	        return this._passToInner('getGrammarTypeForPath', path);
	    }
	});

	_.extend(Transformer, {
	    validate: function (guess, rubric) {
	        // Check for any required transformations
	        for (var type in Transformations) {
	            if (rubric.tools[type].required) {
	                var isUsed = _.any(_.map(guess.transformations,
	                        function(transform) {
	                    // Required transformations must appear in the
	                    // transformation list, and must not be no-ops
	                    return (transform.type === type) &&
	                        !TransformOps.isEmpty(transform) &&
	                        !TransformOps.isNoOp(transform);
	                }));

	                if (!isUsed) {
	                    return {
	                        type: "invalid",
	                        message: $._("Your transformation must use a " +
	                                "%(type)s.", {
	                            type: Transformations[type].lowerNounName
	                        })
	                    };
	                }
	            }
	        }

	        // Compare shapes
	        if (ShapeTypes.equal(guess.shape,
	                rubric.correct.shape)) {
	            return {
	                type: "points",
	                earned: 1,
	                total: 1,
	                message: null
	            };
	        } else if (!rubric.gradeEmpty && deepEq(
	                    guess.shape.coords,
	                    rubric.starting.shape.coords
	                )) {
	            return {
	                type: "invalid",
	                message: $._("Use the interactive graph to define a " +
	                    "correct transformation.")
	            };
	        } else {
	            return {
	                type: "points",
	                earned: 0,
	                total: 1,
	                message: null
	            };
	        }
	    }
	});

	var TransformerEditor = React.createClass({displayName: 'TransformerEditor',
	    // TODO (jack): These should be refactored into a nice object at the top
	    // so that we don't have all this duplication
	    getDefaultProps: function() {
	        return defaultTransformerProps;
	    },

	    render: function() {
	        // Fill in any missing value in this.props.graph
	        // this can happen because the graph json doesn't include
	        // box, for example
	        var graph = _.extend(
	                defaultGraphProps(this.props.graph, 340),
	                this.props.graph
	        );

	        return React.createElement("div", null, 
	            React.createElement("div", null, 
	                React.createElement(PropCheckBox, {
	                    label: "Grade empty answers as wrong:", 
	                    gradeEmpty: this.props.gradeEmpty, 
	                    onChange: this.props.onChange}), 
	                React.createElement(InfoTip, null, 
	                    React.createElement("p", null, 
	                        "We generally do not grade empty answers. This usually" + ' ' +
	                        "works well, but sometimes can result in giving away" + ' ' +
	                        "part of an answer in a multi-part question."
	                    ), 
	                    React.createElement("p", null, 
	                        "If this is a multi-part question (there is another" + ' ' +
	                        "widget), you probably want to enable this option." + ' ' +
	                        "Otherwise, you should leave it disabled."
	                    ), 
	                    React.createElement("p", null, 
	                        "Confused? Talk to Elizabeth."
	                    )
	                )
	            ), 
	            React.createElement("div", null, "Graph settings:"), 
	            React.createElement(GraphSettings, {
	                box: graph.box, 
	                labels: graph.labels, 
	                range: graph.range, 
	                step: graph.step, 
	                gridStep: graph.gridStep, 
	                valid: graph.valid, 
	                backgroundImage: graph.backgroundImage, 
	                markings: graph.markings, 
	                showProtractor: graph.showProtractor, 
	                onChange: this.changeGraph}), 
	            React.createElement("div", null, "Transformation settings:"), 
	            React.createElement(TransformationExplorerSettings, {
	                ref: "transformationSettings", 
	                graphMode: this.props.graphMode, 
	                listMode: this.props.listMode, 
	                tools: this.props.tools, 
	                drawSolutionShape: this.props.drawSolutionShape, 
	                onChange: this.props.onChange}), 
	            React.createElement("div", null, "Starting location:"), 
	            React.createElement(TransformationsShapeEditor, {
	                ref: "shapeEditor", 
	                graph: graph, 
	                shape: this.props.starting.shape, 
	                onChange: this.changeStarting}), 
	            React.createElement("div", null, "Solution transformations:"), 
	            React.createElement(Transformer, {
	                ref: "explorer", 
	                graph: graph, 
	                graphMode: this.props.graphMode, 
	                listMode: this.props.listMode, 
	                gradeEmpty: this.props.gradeEmpty, 
	                tools: this.props.tools, 
	                drawSolutionShape: this.props.drawSolutionShape, 
	                starting: this.props.starting, 
	                correct: this.props.starting, 
	                transformations: this.props.correct.transformations, 
	                onChange: this.changeTransformer})
	        );
	    },

	    // propagate a props change on our graph settings to
	    // this.props.graph
	    changeGraph: function(graphChanges, callback) {
	        var newGraph = _.extend({}, this.props.graph, graphChanges);
	        this.props.onChange({
	            graph: newGraph
	        }, callback);
	    },

	    // propagate a props change on our starting graph to
	    // this.props.starting
	    changeStarting: function(startingChanges) {
	        var newStarting = _.extend({}, this.props.starting, startingChanges);
	        this.props.onChange({
	            starting: newStarting
	        });
	    },

	    // propagate a transformations change onto correct.transformations
	    changeTransformer: function(changes, callback) {
	        if (changes.transformations) {
	            changes.correct = {
	                transformations: changes.transformations
	            };
	            delete changes.transformations;
	        }
	        this.props.onChange(changes, callback);
	    },

	    serialize: function() {
	        var json = this.refs.explorer.getEditorJSON();
	        json.correct = json.answer;
	        delete json.answer;
	        return json;
	    }
	});


	module.exports = {
	    name: "transformer",
	    displayName: "Transformer",
	    widget: Transformer,
	    editor: TransformerEditor
	};


/***/ },
/* 45 */
/***/ function(module, exports, __webpack_require__) {

	// TODO(joel): teach KAS how to accept an answer only if it's expressed in
	// terms of a certain type.
	// TODO(joel): Allow sigfigs within a range rather than an exact expected
	// value?

	var _ = __webpack_require__(60);
	var lens = __webpack_require__(104);

	var ApiClassNames = __webpack_require__(12).ClassNames;
	var ApiOptions = __webpack_require__(12).Options;
	var Changeable = __webpack_require__(66);
	var EditorJsonify = __webpack_require__(67);
	var MathOutput   = __webpack_require__(88);
	var NumberInput = __webpack_require__(85);
	var $__0=     __webpack_require__(91),SignificantFigures=$__0.SignificantFigures,displaySigFigs=$__0.displaySigFigs;

	var ALL = "all";
	var SOME = "some";
	var MAX_SIGFIGS = 10;

	var countSigfigs = function(value) {
	    return new SignificantFigures(value).sigFigs;
	};

	var sigfigPrint = function(num, sigfigs) {
	    return displaySigFigs(num, sigfigs, -MAX_SIGFIGS, false);
	};

	/* I just wrote this, but it's old by analogy to `OldExpression`, in that it's
	 * the version that non-mathquill platforms get stuck with. Constructed with an
	 * <input>, a parser, popsicle sticks, and glue.
	 *
	 * In the same way as OldExpression, this parses continuously as you type, then
	 * shows and hides an error buddy. The error message is only shown after a
	* rolling two second delay, but hidden immediately on further typing.
	 */
	var OldUnitInput = React.createClass({displayName: 'OldUnitInput',
	    mixins: [Changeable],

	    propTypes: {
	        value: React.PropTypes.string,
	    },

	    getDefaultProps: function() {
	        return {
	            apiOptions: ApiOptions.defaults,
	            value: "",
	        };
	    },

	    // TODO(joel) think about showing the error buddy
	    render: function() {
	        var inputType = this.props.apiOptions.staticRender ?
	                React.createFactory(MathOutput) :
	                React.DOM.input;
	        var input = inputType({
	            onChange: this.handleChange,
	            ref: "input",
	            className: ApiClassNames.INTERACTIVE,
	            value: this.props.value,
	            onFocus: this.handleFocus,
	            onBlur: this.handleBlur,
	        });

	        return React.createElement("div", {className: "old-unit-input"}, 
	            input, 
	            React.createElement("div", {ref: "error", 
	                 className: "error", 
	                 style: {display: "none"}}, 
	                $_(null, "I don't understand that")
	            )
	        );
	    },

	    _errorTimeout: null,

	    _showError: function() {
	        if (this.props.value === "") {
	            return;
	        }

	        var $error = $(this.refs.error.getDOMNode());
	        if (!$error.is(":visible")) {
	            $error.css({ top: 50, opacity: 0.1 }).show()
	                .animate({ top: 0, opacity: 1.0 }, 300);
	        }
	    },

	    _hideError: function() {
	        var $error = $(this.refs.error.getDOMNode());
	        if ($error.is(":visible")) {
	            $error.animate({ top: 50, opacity: 0.1 }, 300, function() {
	                $(this).hide();
	            });
	        }
	    },

	    componentDidUpdate: function() {
	        clearTimeout(this._errorTimeout);
	        if (KAS.unitParse(this.props.value).parsed) {
	            this._hideError();
	        } else {
	            this._errorTimeout = setTimeout(this._showError, 2000);
	        }
	    },

	    componentWillUnmount: function() {
	        clearTimeout(this._errorTimeout);
	    },

	    handleBlur: function() {
	        this.props.onBlur([]);
	        clearTimeout(this._errorTimeout);
	        if (!KAS.unitParse(this.props.value).parsed) {
	            this._showError();
	        }
	    },

	    handleChange: function(event) {
	        this._hideError();
	        this.props.onChange({ value: event.target.value });
	    },

	    simpleValidate: function(rubric, onInputError) {
	        onInputError = onInputError || function() {};
	        return OldUnitInput.validate(this.getUserInput(), rubric);
	    },

	    getUserInput: function() {
	        return this.props.value;
	    },

	    // begin mobile stuff

	    getInputPaths: function() {
	        // The widget itself is an input, so we return a single empty list to
	        // indicate this.
	        return [[]];
	    },

	    focusInputPath: function(inputPath) {
	        this.refs.input.getDOMNode().focus();
	    },

	    handleFocus: function() {
	        this.props.onFocus([]);
	    },

	    blurInputPath: function(inputPath) {
	        this.refs.input.getDOMNode().blur();
	    },

	    setInputValue: function(path, newValue, cb) {
	        this.props.onChange({
	            value: newValue
	        }, cb);
	    },

	    getDOMNodeForPath: function() {
	        return this.refs.input.getDOMNode();
	    },

	    getGrammarTypeForPath: function(inputPath) {
	        return "unit";
	    },

	    // end mobile stuff

	    statics: {
	        displayMode: "inline-block",
	    }
	});

	// Extract the primitive units from a unit expression. This first simplifies
	// `expr` to a `Mul` like "5 kg m / s^2" then removes the first term.
	var primUnits = function(expr) {
	    return expr.simplify().asMul().partition()[1].flatten().simplify();
	};

	_.extend(OldUnitInput, {
	    validate: function(state, rubric) {
	        var answer = KAS.unitParse(rubric.value).expr;
	        var guess = KAS.unitParse(state);
	        if (!guess.parsed) {
	            return  {
	                type: "invalid",
	                message: $._("I couldn't understand those units."),
	            };
	        }

	        // Note: we check sigfigs, then numerical correctness, then units, so
	        // the most significant things come last, that way the user will see
	        // the most important message.
	        var message = null;

	        // did the user specify the right number of sigfigs?
	        // TODO(joel) - add a grading mode where the wrong number of sigfigs
	        // isn't marked wrong
	        var sigfigs = rubric.sigfigs;
	        var sigfigsCorrect = countSigfigs(guess.coefficient) === sigfigs;
	        if (!sigfigsCorrect) {
	            message = $._("Check your significant figures.");
	        }

	        // now we need to check that the answer is correct to the precision we
	        // require.
	        var numericallyCorrect;
	        try {
	            var x = new KAS.Var("x");
	            var equality = new KAS.Eq(
	                answer.simplify(),
	                "=",
	                new KAS.Mul(x, guess.expr.simplify())
	            );

	            var conversion = equality.solveLinearEquationForVariable(x);

	            // Make sure the conversion factor between the user's input answer
	            // and the canonical answer is 1, to sigfig places.
	            // TODO(joel) is this sound?
	            numericallyCorrect =
	                Number(conversion.eval()).toPrecision(sigfigs) ===
	                Number(1).toPrecision(sigfigs);
	        } catch (e) {
	            numericallyCorrect = false;
	        }

	        if (!numericallyCorrect) {
	            message = $._("That answer is numerically incorrect.");
	        }

	        var kasCorrect;
	        var guessUnit = primUnits(guess.expr.simplify());
	        var answerUnit = primUnits(answer.simplify());

	        if (rubric.accepting === ALL) {
	            // We're accepting all units - KAS does the hard work of figuring
	            // out if the user's unit is equivalent to the author's unit.
	            kasCorrect = KAS.compare(
	                guessUnit,
	                answerUnit
	            ).equal;
	        } else {
	            // Are any of the accepted units the same as what the user entered?
	            kasCorrect = _(rubric.acceptingUnits).any(function(unit)  {
	                var thisAnswerUnit = primUnits(
	                    KAS.unitParse(unit).unit.simplify()
	                );
	                return KAS.compare(
	                    thisAnswerUnit,
	                    guessUnit
	                    // TODO(joel) - make this work as intended.
	                    // { form: true }
	                ).equal;
	            });
	        }
	        if (!kasCorrect) {
	            var message = $._("Check your units.");
	        }

	        var correct = kasCorrect && numericallyCorrect && sigfigsCorrect;

	        return {
	            type: "points",
	            earned: correct ? 1 : 0,
	            total: 1,
	            message:message,
	        };
	    }
	});


	// Show the name of a unit and whether it's recognized by KAS.
	//
	// In the future I plan for this to show an example of a thing that would be
	// accepted in that unit.
	var UnitExample = React.createClass({displayName: 'UnitExample',
	    render: function() {
	        var icon;
	        if (this.state.valid) {
	            icon = React.createElement("span", null, 
	                React.createElement("i", {className: "icon-ok unit-example-okay"}), 
	                this.state.solvedExample
	            );
	        } else {
	            icon = React.createElement("i", {className: "icon-remove unit-example-not-okay"});
	        }

	        return React.createElement("div", null, 
	            icon, " ", this.props.name
	        );
	    },

	    componentWillReceiveProps: function(nextProps) {
	        this._checkValidity(nextProps);
	    },

	    componentWillMount: function() {
	        this._checkValidity(this.props);
	    },

	    _checkValidity: function($__0    ) {var name=$__0.name,original=$__0.original,sigfigs=$__0.sigfigs;
	        var parseResult = KAS.unitParse(name);
	        var solvedExample = "";

	        // A unit is valid if it parses and is equivalent to the original.
	        var valid = true;

	        if (parseResult.parsed && original) {
	            var x = new KAS.Var("x");
	            var $__1=    parseResult,unit=$__1.unit;
	            var equality = new KAS.Eq(
	                original,
	                "=",
	                new KAS.Mul(x, unit)
	            );
	            try {
	                var answer = equality.solveLinearEquationForVariable(x);

	                // The third parameter is the least significant decimal place.
	                // I.e. the index of the last place you care about
	                // (543210.(-1)(-2)(-3) etc). We use -10 because that should
	                // always be safe since we only care up to maximum 10 decimal
	                // places.
	                solvedExample = sigfigPrint(answer.eval(), sigfigs);

	                valid = KAS.compare(
	                    primUnits(original),
	                    primUnits(unit)
	                ).equal;
	            } catch (e) {
	                valid = false;
	            }
	        } else {
	            valid = false;
	        }

	        this.setState({
	            valid:valid,
	            solvedExample:solvedExample,
	        });
	    },
	});

	var UnitInputEditor = React.createClass({displayName: 'UnitInputEditor',
	    mixins: [Changeable, EditorJsonify],

	    propTypes: {
	        value: React.PropTypes.string,
	        acceptingUnits: React.PropTypes.arrayOf(React.PropTypes.string),
	        accepting: React.PropTypes.oneOf([ALL, SOME]),
	        sigfigs: React.PropTypes.number,
	    },

	    getDefaultProps: function() {
	        return {
	            value: "5x10^5 kg m / s^2",
	            accepting: ALL,
	            sigfigs: 3
	        };
	    },

	    render: function() {
	        var $__0=     this.props,acceptingUnits=$__0.acceptingUnits,accepting=$__0.accepting;
	        acceptingUnits = acceptingUnits || [];
	        var acceptingElem = null;
	        if (accepting === SOME) {
	            var unitsArr = acceptingUnits.map(function(name) 
	                {return React.createElement(UnitExample, {name: name, 
	                             original: this.original || null, 
	                             sigfigs: this.props.sigfigs});}.bind(this)
	            );

	            acceptingElem = React.createElement("div", null, 
	                React.createElement("input", {
	                    type: "text", 
	                    defaultValue: acceptingUnits.join(", "), 
	                    onChange: this.handleAcceptingUnitsChange}
	                ), 
	                " ", "(comma-separated)", 
	                unitsArr
	            );
	        }

	        return React.createElement("div", {className: "unit-editor"}, 
	            React.createElement("div", null, 
	                React.createElement("input", {value: this.props.value, 
	                       className: "unit-editor-canonical", 
	                       onBlur: this._handleBlur, 
	                       onKeyPress: this._handleBlur, 
	                       onChange: this.onChange}), 
	                " ", 
	                this.parsed ?
	                    React.createElement("i", {className: "icon-ok unit-example-okay"}) :
	                    React.createElement("i", {className: "icon-remove unit-example-not-okay"})
	                
	            ), 

	            React.createElement("div", null, 
	                "Significant Figures:", " ", 
	                React.createElement(NumberInput, {value: this.props.sigfigs, 
	                             onChange: this.handleSigfigChange, 
	                             checkValidity: this._checkSigfigValidity, 
	                             useArrowKeys: true})
	            ), 

	            React.createElement("div", null, 
	                React.createElement("label", null, 
	                    React.createElement("input", {type: "radio", 
	                           name: this.groupId, 
	                           onChange: function()  {return this._setAccepting(ALL);}.bind(this), 
	                           checked: this.props.accepting === ALL}), 
	                    " Any equivalent unit "
	                ), 
	                React.createElement("label", null, 
	                    React.createElement("input", {type: "radio", 
	                           name: this.groupId, 
	                           onChange: function()  {return this._setAccepting(SOME);}.bind(this), 
	                           checked: this.props.accepting === SOME}), 
	                    " Only these units "
	                )
	            ), 

	            acceptingElem
	        );
	    },

	    handleAcceptingUnitsChange: function(event) {
	        var acceptingUnits = event.target.value
	            .split(",")
	            .map(function(str)  {return str.trim();})
	            .filter(function(str)  {return str !== "";});
	        this.change({ acceptingUnits:acceptingUnits });
	    },

	    handleSigfigChange: function(sigfigs) {
	        this.change({ sigfigs:sigfigs });
	    },

	    _checkSigfigValidity: function(sigfigs) {
	        return sigfigs > 0 && sigfigs <= MAX_SIGFIGS;
	    },

	    _setAccepting: function(val) {
	        this.change({ accepting: val });
	    },

	    componentWillMount: function() {
	        this.groupId = _.uniqueId("accepting");
	        this._doOriginal(this.props);
	    },

	    componentWillReceiveProps: function(nextProps) {
	        this._doOriginal(nextProps);
	    },

	    _doOriginal: function(props) {
	        var tryParse = KAS.unitParse(props.value);
	        this.parsed = false;

	        // Only update this state if the unit parsed *and* it has a magnitude
	        // attached to it. KAS can also parse units without magnitudes ("1.2
	        // g" vs "g").
	        if (tryParse.parsed && tryParse.type === "unitMagnitude") {
	            this.original = tryParse.expr;
	            this.parsed = true;
	        }
	    },

	    onChange: function(event) {
	        this.props.onChange({ value: event.target.value });
	    },

	    getSaveWarnings: function() {
	        var $__0=      this.props,value=$__0.value,accepting=$__0.accepting,acceptingUnits=$__0.acceptingUnits;
	        var warnings = [];

	        var tryParse = KAS.unitParse(value);
	        if (!tryParse.parsed) {
	            warnings.push("answer did not parse");
	        }

	        if (accepting === SOME && acceptingUnits.length === 0) {
	            warnings.push("there are no accepted units");
	        }

	        return warnings;
	    },
	});

	module.exports = {
	    name: "unit-input",
	    displayName: "Unit",
	    getWidget: function(enabledFeatures)  {
	        // Allow toggling between the two versions of the widget
	        return OldUnitInput;
	    },
	    editor: UnitInputEditor,
	    transform: function(x)  {return lens(x).del(["value"]).freeze();},
	    version: { major: 0, minor: 1 },
	    countSigfigs:countSigfigs,
	    sigfigPrint:sigfigPrint,
	    hidden: true,
	};


/***/ },
/* 46 */
/***/ function(module, exports, __webpack_require__) {

	module.exports = {
		"apiVersion": {
			"major": 2,
			"minor": 0
		},
		"itemDataVersion": {
			"major": 0,
			"minor": 1
		}
	}

/***/ },
/* 47 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);

	var QuestionParagraph = React.createClass({displayName: 'QuestionParagraph',
	    render: function() {
	        var className = (this.props.className) ?
	            "paragraph " + this.props.className :
	            "paragraph";
	        return React.createElement("div", {className: className}, 
	            this.props.children
	        );
	    }
	});

	module.exports = QuestionParagraph;


/***/ },
/* 48 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);
	var cx = React.addons.classSet;

	var WidgetContainer = React.createClass({displayName: 'WidgetContainer',
	    propTypes: {
	        shouldHighlight: React.PropTypes.bool.isRequired,
	        type: React.PropTypes.func,
	        initialProps: React.PropTypes.object.isRequired,
	    },

	    getInitialState: function() {
	        return {widgetProps: this.props.initialProps};
	    },

	    render: function() {
	        var className = cx({
	            "perseus-widget-container": true,
	            "widget-highlight": this.props.shouldHighlight,
	            "widget-nohighlight": !this.props.shouldHighlight,
	        });

	        var WidgetType = this.props.type;
	        if (WidgetType == null) {
	            // Just give up on invalid widget types
	            return React.createElement("div", {className: className});
	        }

	        if (WidgetType.displayMode == null) {
	            throw new Error("You didn't specify a displayMode in the " +
	                          "statics for " + WidgetType.displayName + ".");
	        }

	        return React.createElement("div", {className: className, 
	            style: {
	                display: WidgetType.displayMode
	            }}, 
	            React.createElement(WidgetType, React.__spread({},  this.state.widgetProps, {ref: "widget"}))
	        );
	    },

	    componentWillReceiveProps: function(nextProps) {
	        if (this.props.type !== nextProps.type) {
	            throw new Error(
	                "WidgetContainer can't change widget type; set a different " +
	                "key instead to recreate the container."
	            );
	        }
	    },

	    shouldComponentUpdate: function(nextProps, nextState) {
	        return (
	            this.props.shouldHighlight !== nextProps.shouldHighlight ||
	            this.props.type !== nextProps.type ||
	            this.state.widgetProps !== nextState.widgetProps
	        );
	    },

	    getWidget: function() {
	        return this.refs.widget;
	    },

	    replaceWidgetProps: function(newWidgetProps) {
	        this.setState({widgetProps: newWidgetProps});
	    }
	});

	module.exports = WidgetContainer;


/***/ },
/* 49 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);

	module.exports = {
	    propTypes: React.PropTypes.shape({
	        toolTipFormats: React.PropTypes.bool.isRequired,
	        useMathQuill: React.PropTypes.bool.isRequired
	    }).isRequired,

	    defaults: {
	        // TODO(jack): Remove this two options
	        toolTipFormats: true,
	        useMathQuill: false
	    }
	};


/***/ },
/* 50 */
/***/ function(module, exports, __webpack_require__) {

	var JsonEditor = React.createClass({displayName: 'JsonEditor',

	    getInitialState: function() {
	        return {
	            currentValue: JSON.stringify(this.props.value, null, 4),
	            valid: true
	        };
	    },

	    componentWillReceiveProps: function(nextProps) {
	        var shouldReplaceContent = !this.state.valid ||
	            !_.isEqual(
	                nextProps.value,
	                JSON.parse(this.state.currentValue)
	            );

	        if (shouldReplaceContent) {
	            this.setState(this.getInitialState());
	        }
	    },

	    render: function() {
	        var classes = "perseus-json-editor " +
	            (this.state.valid ? "valid" : "invalid");

	        return React.createElement("textarea", {
	            className: classes, 
	            value: this.state.currentValue, 
	            onChange: this.handleChange, 
	            onKeyDown: this.handleKeyDown, 
	            onBlur: this.handleBlur});
	    },

	    handleKeyDown: function(e) {
	        // This handler allows the tab character to be entered by pressing
	        // tab, instead of jumping to the next (non-existant) field
	        if (e.key === "Tab") {
	            var cursorPos = e.target.selectionStart;
	            var v = e.target.value;
	            var textBefore = v.substring(0, cursorPos);
	            var textAfter = v.substring(cursorPos, v.length);
	            e.target.value = textBefore+ "    " +textAfter;
	            e.target.selectionStart = textBefore.length + 4;
	            e.target.selectionEnd = textBefore.length + 4;

	            e.preventDefault();
	            this.handleChange(e);
	        }
	    },

	    handleChange: function(e) {
	        var nextString = e.target.value;
	        try {
	            var json = JSON.parse(nextString);
	            // Some extra handling to allow copy-pasting from /api/vi
	            if (_.isString(json)) {
	                json = JSON.parse(json);
	            }
	            // This callback unfortunately causes multiple renders,
	            // but seems to be necessary to avoid componentWillReceiveProps
	            // being called before setState has gone through
	            this.setState({
	                currentValue: nextString,
	                valid: true
	            }, function() {
	                this.props.onChange(json);
	            });
	        } catch (ex) {
	            this.setState({
	                currentValue: nextString,
	                valid: false
	            });
	        }
	    },

	    // You can type whatever you want as you're typing, but if it's not valid
	    // when you blur, it will revert to the last valid value.
	    handleBlur: function(e) {
	        var nextString = e.target.value;
	        try {
	            var json = JSON.parse(nextString);
	            // Some extra handling to allow copy-pasting from /api/vi
	            if (_.isString(json)) {
	                json = JSON.parse(json);
	            }
	            // This callback unfortunately causes multiple renders,
	            // but seems to be necessary to avoid componentWillReceiveProps
	            // being called before setState has gone through
	            this.setState({
	                currentValue: JSON.stringify(json, null, 4),
	                valid: true
	            }, function() {
	                this.props.onChange(json);
	            });
	        } catch (ex) {
	            this.setState({
	                currentValue: JSON.stringify(this.props.value, null, 4),
	                valid: true
	            });
	        }
	    }
	});

	module.exports = JsonEditor;


/***/ },
/* 51 */
/***/ function(module, exports, __webpack_require__) {

	/* Collection of classes for rendering the hint editor area,
	 * hint editor boxes, and hint previews
	 */

	var React = __webpack_require__(61);
	var _ = __webpack_require__(60);

	var Editor = __webpack_require__(7);
	var HintRenderer = __webpack_require__(53);
	var InfoTip = __webpack_require__(69);

	/* Renders a hint editor box
	 *
	 * This includes:
	 *  ~ A "Hint" title
	 *  ~ the textarea for the hint
	 *  ~ the "remove this hint" box
	 *  ~ the move hint up/down arrows
	 */
	var HintEditor = React.createClass({displayName: 'HintEditor',
	    propTypes: {
	        imageUploader: React.PropTypes.func
	    },

	    getDefaultProps: function() {
	        return {
	            content: ""
	        };
	    },

	    render: function() {
	        return React.createElement("div", {className: "perseus-hint-editor perseus-editor-left-cell"}, 
	            React.createElement("div", {className: "pod-title"}, "Hint"), 
	            React.createElement(Editor, {ref: "editor", 
	                    widgets: this.props.widgets, 
	                    content: this.props.content, 
	                    images: this.props.images, 
	                    placeholder: "Type your hint here...", 
	                    imageUploader: this.props.imageUploader, 
	                    onChange: this.props.onChange}), 
	            React.createElement("div", {className: "hint-controls-container clearfix"}, 
	                React.createElement("span", {className: "reorder-hints"}, 
	                    React.createElement("button", {type: "button", 
	                            className: this.props.isLast ? "hidden" : "", 
	                            onClick: _.partial(this.props.onMove, 1)}, 
	                        React.createElement("span", {className: "icon-circle-arrow-down"})
	                    ), 
	                    ' ', 
	                    React.createElement("button", {type: "button", 
	                            className: this.props.isFirst ? "hidden" : "", 
	                            onClick: _.partial(this.props.onMove, -1)}, 
	                        React.createElement("span", {className: "icon-circle-arrow-up"})
	                    ), 
	                    ' ', 
	                    this.props.isLast &&
	                    React.createElement(InfoTip, null, 
	                        React.createElement("p", null, "The last hint is automatically bolded.")
	                    )
	                ), 
	                React.createElement("button", {type: "button", 
	                        className: "remove-hint simple-button orange", 
	                        onClick: this.props.onRemove}, 
	                    React.createElement("span", {className: "icon-trash"}), " Remove this hint", ' '
	                )
	            )
	        );
	    },

	    focus: function() {
	        this.refs.editor.focus();
	    },

	    getSaveWarnings: function() {
	        return this.refs.editor.getSaveWarnings();
	    },

	    serialize: function(options) {
	        return this.refs.editor.serialize(options);
	    }
	});


	/* A single hint-row containing a hint editor and preview */
	var CombinedHintEditor = React.createClass({displayName: 'CombinedHintEditor',
	    propTypes: {
	        imageUploader: React.PropTypes.func
	    },

	    render: function() {
	        var shouldBold = this.props.isLast &&
	                         !(/\*\*/).test(this.props.hint.content);
	        return React.createElement("div", {className: "perseus-combined-hint-editor " +
	                    "perseus-editor-row"}, 
	            React.createElement(HintEditor, {
	                ref: "editor", 
	                isFirst: this.props.isFirst, 
	                isLast: this.props.isLast, 
	                widgets: this.props.hint.widgets, 
	                content: this.props.hint.content, 
	                images: this.props.hint.images, 
	                imageUploader: this.props.imageUploader, 
	                onChange: this.props.onChange, 
	                onRemove: this.props.onRemove, 
	                onMove: this.props.onMove}), 

	            React.createElement("div", {className: "perseus-editor-right-cell"}, 
	                React.createElement(HintRenderer, {hint: this.props.hint, bold: shouldBold})
	            )
	        );
	    },

	    getSaveWarnings: function() {
	        return this.refs.editor.getSaveWarnings();
	    },

	    serialize: function(options) {
	        return this.refs.editor.serialize(options);
	    },

	    focus: function() {
	        this.refs.editor.focus();
	    }
	});


	/* The entire hints editing/preview area
	 *
	 * Includes:
	 *  ~ All the hint edit boxes, move and remove buttons
	 *  ~ All the hint previews
	 *  ~ The "add a hint" button
	 */
	var CombinedHintsEditor = React.createClass({displayName: 'CombinedHintsEditor',
	    propTypes: {
	        imageUploader: React.PropTypes.func
	    },

	    getDefaultProps: function() {
	        return {
	            onChange: function()  {},
	            hints: []
	        };
	    },

	    render: function() {
	        var hints = this.props.hints;
	        var hintElems = _.map(hints, function(hint, i) {
	            return React.createElement(CombinedHintEditor, {
	                        ref: "hintEditor" + i, 
	                        key: "hintEditor" + i, 
	                        isFirst: i === 0, 
	                        isLast: i + 1 === hints.length, 
	                        hint: hint, 
	                        imageUploader: this.props.imageUploader, 
	                        onChange: this.handleHintChange.bind(this, i), 
	                        onRemove: this.handleHintRemove.bind(this, i), 
	                        onMove: this.handleHintMove.bind(this, i)});
	        }, this);

	        return React.createElement("div", {className: "perseus-hints-editor perseus-editor-table"}, 
	            hintElems, 
	            React.createElement("div", {className: "perseus-editor-row"}, 
	                React.createElement("div", {className: "add-hint-container perseus-editor-left-cell"}, 
	                React.createElement("button", {type: "button", 
	                        className: "add-hint simple-button orange", 
	                        onClick: this.addHint}, 
	                    React.createElement("span", {className: "icon-plus"}), 
	                    ' ', "Add a hint"
	                )
	                ), 
	                React.createElement("div", {className: "perseus-editor-right-cell"})
	            )
	        );
	    },

	    handleHintChange: function(i, newProps, cb, silent) {
	        // TODO(joel) - lens
	        var hints = _(this.props.hints).clone();
	        hints[i] = _.extend(
	            {},
	            this.serializeHint(i, {keepDeletedWidgets: true}),
	            newProps
	        );

	        this.props.onChange({hints: hints}, cb, silent);
	    },

	    handleHintRemove: function(i) {
	        var hints = _(this.props.hints).clone();
	        hints.splice(i, 1);
	        this.props.onChange({hints: hints});
	    },

	    handleHintMove: function(i, dir) {
	        var hints = _(this.props.hints).clone();
	        var hint = hints.splice(i, 1)[0];
	        hints.splice(i + dir, 0, hint);
	        this.props.onChange({hints: hints}, function()  {
	            this.refs["hintEditor" + (i + dir)].focus();
	        }.bind(this));
	    },

	    addHint: function() {
	        var hints = _(this.props.hints).clone().concat([{ content: "" }]);
	        this.props.onChange({hints: hints}, function()  {
	            var i = hints.length - 1;
	            this.refs["hintEditor" + i].focus();
	        }.bind(this));
	    },

	    getSaveWarnings: function() {
	        return this.props.hints.map(function(hint, i)  {
	            return this.refs["hintEditor" + i].getSaveWarnings();
	        }.bind(this));
	    },

	    serialize: function(options) {
	        return this.props.hints.map(function(hint, i)  {
	            return this.serializeHint(i, options);
	        }.bind(this));
	    },

	    serializeHint: function(index, options) {
	        return this.refs["hintEditor" + index].serialize(options);
	    }
	});

	module.exports = CombinedHintsEditor;


/***/ },
/* 52 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);
	var _ = __webpack_require__(60);

	var AnswerAreaEditor = __webpack_require__(92);
	var Editor = __webpack_require__(7);
	var ApiOptions = __webpack_require__(12).Options;
	var ITEM_DATA_VERSION = __webpack_require__(46).itemDataVersion;

	var ItemEditor = React.createClass({displayName: 'ItemEditor',
	    propTypes: {
	        imageUploader: React.PropTypes.func,
	        wasAnswered: React.PropTypes.bool,
	        gradeMessage: React.PropTypes.string,
	        apiOptions: ApiOptions.propTypes,
	    },

	    getDefaultProps: function() {
	        return {
	            onChange: function()  {},
	            question: {},
	            answerArea: {},
	            apiOptions: ApiOptions.defaults,
	        };
	    },

	    // Notify the parent that the question or answer area has been updated.
	    updateProps: function(newProps, cb, silent) {
	        var props = _(this.props).pick("question", "answerArea");

	        this.props.onChange(_(props).extend(newProps), cb, silent);
	    },

	    render: function() {
	        return React.createElement("div", {className: "perseus-editor-table"}, 
	            React.createElement("div", {className: "perseus-editor-row perseus-question-container"}, 
	                React.createElement("div", {className: "perseus-editor-left-cell"}, 
	                    React.createElement("div", {className: "pod-title"}, "Question"), 
	                    React.createElement(Editor, React.__spread({
	                        ref: "questionEditor", 
	                        placeholder: "Type your question here...", 
	                        className: "perseus-question-editor", 
	                        imageUploader: this.props.imageUploader, 
	                        onChange: this.handleEditorChange, 
	                        apiOptions: this.props.apiOptions}, 
	                        this.props.question))
	                ), 

	                React.createElement("div", {className: "perseus-editor-right-cell"}, 
	                    React.createElement("div", {id: "problemarea"}, 
	                        React.createElement("div", {id: "workarea", className: "workarea"}), 
	                        React.createElement("div", {id: "hintsarea", 
	                             className: "hintsarea", 
	                             style: {display: "none"}})
	                    )
	                )
	            ), 

	            React.createElement("div", {className: "perseus-editor-row perseus-answer-container"}, 
	                React.createElement("div", {className: "perseus-editor-left-cell"}, 
	                    React.createElement("div", {className: "pod-title"}, "Answer"), 
	                    React.createElement(AnswerAreaEditor, React.__spread({
	                        ref: "answerAreaEditor", 
	                        onChange: this.handleAnswerAreaChange, 
	                        apiOptions: this.props.apiOptions}, 
	                        this.props.answerArea))
	                ), 

	                React.createElement("div", {className: "perseus-editor-right-cell"}, 
	                    React.createElement("div", {id: "answer_area"}, 
	                        React.createElement("div", {id: "solutionarea", className: "solutionarea"}), 
	                        React.createElement("div", {className: "answer-buttons"}, 
	                            React.createElement("input", {
	                                type: "button", 
	                                className: "simple-button green", 
	                                onClick: this.props.onCheckAnswer, 
	                                value: "Check Answer"}), 
	                            this.props.wasAnswered &&
	                                React.createElement("img", {src: "/images/face-smiley.png", 
	                                    className: "smiley"}), 
	                            this.props.gradeMessage &&
	                                React.createElement("span", null, this.props.gradeMessage)
	                        )
	                    )
	                )
	            )
	        );
	    },

	    handleEditorChange: function(newProps, cb, silent) {
	        var question = _.extend({}, this.props.question, newProps);
	        this.updateProps({ question:question }, cb, silent);
	    },

	    handleAnswerAreaChange: function(newProps, cb, silent) {
	        var answerArea = _.extend({}, this.props.answerArea, newProps);
	        this.updateProps({ answerArea:answerArea }, cb, silent);
	    },

	    getSaveWarnings: function() {
	        var issues1 = this.refs.questionEditor.getSaveWarnings();
	        var issues2 = this.refs.answerAreaEditor.getSaveWarnings();
	        return issues1.concat(issues2);
	    },

	    serialize: function(options) {
	        return {
	            question: this.refs.questionEditor.serialize(options),
	            answerArea: this.refs.answerAreaEditor.serialize(options),
	            itemDataVersion: ITEM_DATA_VERSION
	        };
	    },

	    focus: function() {
	        this.questionEditor.focus();
	    }
	});

	module.exports = ItemEditor;


/***/ },
/* 53 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);
	var Renderer = __webpack_require__(10);

	/* Renders just a hint preview */
	var HintRenderer = React.createClass({displayName: 'HintRenderer',
	    render: function() {
	        var shouldBold = this.props.bold;
	        var hint = this.props.hint;
	        var classNames;
	        if (shouldBold) {
	            classNames = "perseus-hint-renderer last-hint";
	        } else {
	            classNames = "perseus-hint-renderer";
	        }
	        return React.createElement("div", {className: classNames}, 
	            React.createElement(Renderer, {
	                ref: "renderer", 
	                widgets: this.props.hint.widgets, 
	                content: this.props.hint.content || "", 
	                images: this.props.hint.images})
	        );
	    },

	    getSerializedState: function() {
	        return this.refs.renderer.getSerializedState();
	    },

	    restoreSerializedState: function(state, callback) {
	        this.refs.renderer.restoreSerializedState(state, callback);
	    },
	});

	module.exports = HintRenderer;


/***/ },
/* 54 */
/***/ function(module, exports, __webpack_require__) {

	var _ = __webpack_require__(60);

	var SimpleMarkdown = __webpack_require__(113);
	var TeX = __webpack_require__(65);
	var Util = __webpack_require__(3);

	/**
	 * This "regex" matches math in `$`s, such as:
	 *
	 * $y = x + 1$
	 *
	 * It functions roughly like the following real regex:
	 * /\$([^\$]*)\$/
	 *
	 * Unfortunately, math may have other `$`s inside it, as
	 * long as they are inside `{` braces `}`, mostly for
	 * `\text{ $math$ }`.
	 *
	 * To parse this, we can't use a real regex, since we
	 * should support arbitrary nesting (even though
	 * MathJax actually only supports two levels of nesting
	 * here, which we *could* parse with a regex).
	 *
	 * So instead, we make an object that pretends to be
	 * a regex by having an `exec` method that parses the
	 * `$` and `{}`s in mathy text.
	 *
	 * All simple-markdown relies on is the `exec` method of
	 * the regex, so this is safe. ish.
	 */
	var fakeMathRegex = {
	    exec: function(source)  {
	        var length = source.length;
	        // our source must start with a "$"
	        if (length === 0 || source[0] !== "$") {
	            return null;
	        }
	        var index = 1;
	        var braceLevel = 0;

	        // Loop through the source, looking for a closing '$'
	        // closing '$'s only count if they are not escaped with
	        // a `\`, and we are not in nested `{}` braces.
	        while (index < length) {
	            var character = source[index];

	            if (character === "\\") {
	                // Consume both the `\` and the escaped char as a single
	                // token.
	                // This is so that the second `$` in `$\\$` closes
	                // the math expression, since the first `\` is escaping
	                // the second `\`, but the second `\` is not escaping
	                // the second `$`.
	                // This also handles the case of escaping `$`s or
	                // braces `\{`
	                index++;

	            } else if (braceLevel <= 0 &&
	                    character === "$") {
	                
	                // Return an array that looks like the results of a
	                // regex's .exec function:
	                // capture[0] is the whole string
	                // capture[1] is the first "paren" match, which is the
	                //   content of the math here, as if we wrote the regex
	                //   /\$([^\$]*)\$/
	                return [
	                    source.substring(0, index + 1),
	                    source.substring(1, index)
	                ];

	            } else if (character === "{") {
	                braceLevel++;

	            } else if (character === "}") {
	                braceLevel--;

	            } else if (character === "\n" &&
	                    source[index - 1] === "\n") {
	                // This is a weird case we supported in the old
	                // math implementation--double newlines break
	                // math. I'm preserving it for now because content
	                // creators might have questions with single '$'s
	                // in paragraphs...
	                return null;
	            }

	            index++;
	        }

	        // we didn't find a closing `$`
	        return null;
	    }
	};

	var TITLED_TABLE_REGEX = new RegExp(
	    "^\\|\\| +(.*) +\\|\\| *\\n" +
	    "(" +
	    // The simple-markdown nptable regex, without
	    // the leading `^`
	    SimpleMarkdown.defaultRules.nptable.regex.source.substring(1) +
	    ")"
	);

	var rules = _.extend({}, SimpleMarkdown.defaultRules, {
	    columns: {
	        regex: /^([\s\S]*\n\n)={5,}\n\n([\s\S]*)/,
	        parse: function(capture, parse, state)  {
	            return {
	                col1: parse(capture[1], state),
	                col2: parse(capture[2], state)
	            };
	        },
	        output: function(node, output)  {
	            return React.createElement("div", {className: "perseus-two-columns"}, 
	                React.createElement("div", {className: "perseus-column"}, 
	                    output(node.col1)
	                ), 
	                React.createElement("div", {className: "perseus-column"}, 
	                    output(node.col2)
	                )
	            );
	        }
	    },
	    // This is pretty much horrible, but we have a regex here to capture an
	    // entire table + a title. capture[1] is the title. capture[2] of the
	    // regex is a copy of the simple-markdown nptable regex. Then we turn
	    // our capture[2] into tableCapture[0], and any further captures in
	    // our table regex into tableCapture[1..], and we pass tableCapture to
	    // our nptable regex
	    titledTable: {
	        regex: TITLED_TABLE_REGEX,
	        parse: function(capture, parse, state)  {
	            var title = parse(capture[1], state);

	            // Remove our [0] and [1] captures, and pass the rest to
	            // the nptable parser
	            var tableCapture = _.rest(capture, 2);
	            var table = SimpleMarkdown.defaultRules.nptable.parse(
	                tableCapture,
	                parse,
	                state
	            );
	            return {
	                title: title,
	                table: table,
	            };
	        },
	        output: function(node, output)  {
	            var tableOutput = node.table ?
	                SimpleMarkdown.defaultRules.table.output(node.table, output) :
	                "//invalid table//";
	            return React.createElement("div", {className: "perseus-titled-table"}, 
	                React.createElement("div", {className: "perseus-table-title"}, 
	                    output(node.title)
	                ), 
	                React.createElement("div", null, tableOutput)
	            );
	        }
	    },
	    widget: {
	        regex: Util.rWidgetRule,
	        parse: function(capture, parse, state)  {
	            return {
	                id: capture[1],
	                widgetType: capture[2]
	            };
	        },
	        output: function(node, output)  {
	            // The actual output is handled in the renderer, where
	            // we know the current widget props/state. This is
	            // just a stub for testing.
	            return React.createElement("em", null, "[Widget: ", node.id, "]");
	        }
	    },
	    math: {
	        regex: fakeMathRegex,
	        parse: function(capture, parse, state)  {
	            return {
	                content: capture[1]
	            };
	        },
	        output: function(node, output)  {
	            // The actual output is handled in the renderer, because
	            // it needs to pass in an `onRender` callback prop. This
	            // is just a stub for testing.
	            return React.createElement(TeX, null, node.content);
	        }
	    },
	});

	var linkRuleIndex = SimpleMarkdown.defaultPriorities.indexOf("link");
	var tableRuleIndex = SimpleMarkdown.defaultPriorities.indexOf("table");
	if (linkRuleIndex < 0 || tableRuleIndex < 0) {
	    // assert that we found the positions in the priority list at
	    // which to insert our rules.
	    throw new Error(
	        "could not find link/table rule in simple-markdown to place " +
	        "perseus table, widget, and math rules before"
	    );
	}

	var priorities = _.clone(SimpleMarkdown.defaultPriorities);
	priorities.unshift("columns");
	// Inject our block table rule before normal tables
	priorities.splice(
	    tableRuleIndex,
	    0,
	    "titledTable"
	);
	// Naively inject our inline rules before links so that our `[[`s take
	// precedence
	priorities.splice(
	    linkRuleIndex,
	    0,
	    "widget",
	    "math"
	);

	var builtParser = SimpleMarkdown.parserFor(rules, priorities);
	var parse = function(source)  {
	    var paragraphedSource = source + "\n\n";
	    return builtParser(paragraphedSource);
	};

	module.exports = {
	    parse: parse,
	    outputFor: SimpleMarkdown.outputFor,
	    ruleOutput: SimpleMarkdown.ruleOutput(rules),
	    testOutput: SimpleMarkdown.outputFor(SimpleMarkdown.ruleOutput(rules)),
	    sanitizeUrl: SimpleMarkdown.sanitizeUrl,
	};



/***/ },
/* 55 */
/***/ function(module, exports, __webpack_require__) {

	var diff = __webpack_require__(102);
	var splitDiff = __webpack_require__(93);
	var stringArrayDiff = __webpack_require__(94);

	var cx = React.addons.classSet;

	var BEFORE = "before";
	var AFTER = "after";

	var IMAGE_REGEX = /http.*?\.png/g;

	var imagesInString = function(str) {
	    return str.match(IMAGE_REGEX) || [];
	};

	var classFor = function(entry, ifAdded, ifRemoved) {
	    if (entry.added) {
	        return ifAdded;
	    } else if (entry.removed) {
	        return ifRemoved;
	    } else {
	        return "";
	    }
	};

	var ImageDiffSide = React.createClass({displayName: 'ImageDiffSide',
	    propTypes: {
	        side: React.PropTypes.oneOf([BEFORE, AFTER]).isRequired,
	        images: React.PropTypes.array.isRequired
	    },

	    render: function() {
	        return React.createElement("div", null, 
	            this.props.images.length > 0 &&
	                React.createElement("div", {className: "diff-header"}, "Images"), 
	            _.map(this.props.images, function(entry)  {
	                var className = cx({
	                    "image": true,
	                    "image-unchanged": entry.status === "unchanged",
	                    "image-added": entry.status === "added",
	                    "image-removed": entry.status === "removed"
	                });
	                return React.createElement("div", null, 
	                    React.createElement("img", {src: entry.value, 
	                        title: entry.value, 
	                        className: className})
	                );
	            })
	        );
	    }
	});

	var TextDiff = React.createClass({displayName: 'TextDiff',
	    propTypes: {
	        before: React.PropTypes.string,
	        after: React.PropTypes.string,
	        title: React.PropTypes.string
	    },

	    getDefaultProps: function() {
	        return {
	            before: "",
	            after: "",
	            title: ""
	        };
	    },

	    getInitialState: function() {
	        return {
	            collapsed: this.props.before === this.props.after
	        };
	    },

	    componentWillReceiveProps: function(nextProps) {
	        this.setState({
	            collapsed: nextProps.before === nextProps.after
	        });
	    },

	    render: function() {
	        var diffed = diff.diffWords(this.props.before, this.props.after);

	        var lines = splitDiff(diffed);

	        beforeImages = imagesInString(this.props.before);
	        afterImages = imagesInString(this.props.after);

	        var images = stringArrayDiff(beforeImages, afterImages);

	        var renderedLines = _.map(lines, function(line)  {
	            var contents = {};

	            contents.before = _(line).map(function(entry, i) {
	                var className = classFor(entry, "not-present", "removed dark");
	                return React.createElement("span", {
	                    key: i, 
	                    className: className}, entry.value);
	            });

	            contents.after = _(line).map(function(entry, i) {
	                var className = classFor(entry, "added dark", "not-present");
	                return React.createElement("span", {
	                    key: i, 
	                    className: className}, entry.value);
	            });
	            return contents;
	        });

	        var className = cx({
	            "diff-row": true,
	            "collapsed": this.state.collapsed
	        });

	        return React.createElement("div", null, 
	            React.createElement("div", {className: "ui-helper-clearfix"}, 
	                _.map([BEFORE, AFTER], function(side)  {
	                    return React.createElement("div", {className: "diff-row " + side}, 
	                        !this.state.collapsed &&
	                            _.map(renderedLines, function(line)  {
	                                var changed = line[side].length > 1;
	                                var lineClass = cx({
	                                    "diff-line": true,
	                                    "added": side === AFTER && changed,
	                                    "removed": side === BEFORE && changed
	                                });
	                                return React.createElement("div", {className: lineClass}, 
	                                    line[side]
	                                );
	                            }), 
	                         !this.state.collapsed &&
	                             React.createElement(ImageDiffSide, {
	                                 side: side, 
	                                 images: images[side]})
	                    );
	                }.bind(this))
	            ), 
	            _.map([BEFORE, AFTER], function(side)  {
	                return React.createElement("div", {className: className + " " + side, 
	                    onClick: this.handleExpand}, 
	                    this.state.collapsed &&
	                    React.createElement("span", null, 
	                        React.createElement("span", {className: "expand-button"}, 
	                            " ", "[ show unmodified ]"
	                        )
	                    )
	                );
	            }.bind(this))
	        );
	    },

	    handleExpand: function() {
	        this.setState({ collapsed: false });
	    }
	});

	module.exports = TextDiff;


/***/ },
/* 56 */
/***/ function(module, exports, __webpack_require__) {

	var cx = React.addons.classSet;
	var performDiff = __webpack_require__(95);

	var indentationFromDepth = function(depth) {
	    return (depth - 1) * 20;
	};

	var BEFORE = "before";
	var AFTER = "after";

	var UNCHANGED = "unchanged";

	var DiffSide = React.createClass({displayName: 'DiffSide',
	    propTypes: {
	        side: React.PropTypes.oneOf([BEFORE, AFTER]).isRequired,
	        className: React.PropTypes.string.isRequired,
	        showKey: React.PropTypes.bool.isRequired,
	        propKey: React.PropTypes.string.isRequired,
	        value: React.PropTypes.string,
	        depth: React.PropTypes.number.isRequired
	    },

	    render: function() {
	        var className = this.props.className + " " + cx({
	            "diff-row": true,
	            before: this.props.side === BEFORE,
	            after: this.props.side === AFTER
	        });
	        return React.createElement("div", {className: className}, 
	            React.createElement("div", {style: {
	                paddingLeft: indentationFromDepth(this.props.depth)
	            }}, 
	                this.props.showKey && this.props.propKey + ": ", 
	                React.createElement("span", {className: "inner-value dark " + this.props.className}, 
	                    this.props.value
	                )
	            )
	        );
	    }
	});

	var CollapsedRow = React.createClass({displayName: 'CollapsedRow',
	    propTypes: {
	        depth: React.PropTypes.number,
	        onClick: React.PropTypes.func.isRequired
	    },

	    getDefaultProps: function() {
	        return {
	            depth: 0
	        };
	    },

	    render: function() {
	        var self = this;
	        return React.createElement("div", {onClick: self.props.onClick}, 
	            _.map([BEFORE, AFTER], function(side) {
	                return React.createElement("div", {className: "diff-row collapsed " + side, 
	                    key: side}, 
	                        React.createElement("div", {style: {
	                            paddingLeft: indentationFromDepth(self.props.depth)
	                        }}, 
	                        React.createElement("span", null, " [ show unmodified ] ")
	                    )
	                );
	            })
	        );
	    }
	});

	// Component representing a single property that may be nested.
	var DiffEntry = React.createClass({displayName: 'DiffEntry',
	    propTypes: {
	        entry: React.PropTypes.shape({
	            key: React.PropTypes.string,
	            children: React.PropTypes.array,
	            before: React.PropTypes.string,
	            after: React.PropTypes.string
	        }),
	        depth: React.PropTypes.number,
	        expanded: React.PropTypes.bool
	    },

	    getDefaultProps: function() {
	        return {
	            depth: 0
	        };
	    },

	    getInitialState: function() {
	        return {
	            expanded: this.props.expanded
	        };
	    },

	    render: function() {
	        var entry = this.props.entry;
	        var propertyDeleted = entry.status === "removed";
	        var propertyAdded   = entry.status === "added";
	        var propertyChanged = entry.status === "changed";

	        var hasChildren = entry.children.length > 0;

	        var leftClass = cx({
	            "removed": (propertyDeleted || propertyChanged) && !hasChildren,
	            "dark": propertyDeleted,
	            "blank-space": propertyAdded
	        });

	        var rightClass = cx({
	            "added": (propertyAdded || propertyChanged) && !hasChildren,
	            "dark": propertyAdded,
	            "blank-space": propertyDeleted
	        });

	        var shownChildren;
	        if (this.state.expanded) {
	            shownChildren = entry.children;
	        } else {
	            shownChildren = _(entry.children).select(function(child) {
	                return child.status !== UNCHANGED;
	            });
	        }

	        var collapsed = shownChildren.length < entry.children.length;

	        // don't hide just one entry
	        if (entry.children.length === shownChildren.length + 1) {
	            shownChildren = entry.children;
	            collapsed = false;
	        }

	        var self = this;
	        return React.createElement("div", null, 
	            entry.key && React.createElement("div", null, 
	            React.createElement(DiffSide, {
	                side: BEFORE, 
	                className: leftClass, 
	                depth: this.props.depth, 
	                propKey: entry.key, 
	                showKey: !propertyAdded, 
	                value: entry.before}), 
	            React.createElement(DiffSide, {
	                side: AFTER, 
	                className: rightClass, 
	                depth: this.props.depth, 
	                propKey: entry.key, 
	                showKey: !propertyDeleted, 
	                value: entry.after})
	            ), 
	            _.map(shownChildren, function(child) {
	                return React.createElement(DiffEntry, {
	                    key: child.key, 
	                    depth: self.props.depth + 1, 
	                    entry: child, 
	                    expanded: self.state.expanded});
	            }), 
	            collapsed &&
	                React.createElement(CollapsedRow, {
	                    depth: this.props.depth + 1, 
	                    onClick: this.expand})
	        );
	    },

	    expand: function() {
	        this.setState({ expanded: true });
	    }
	});

	var WidgetDiff = React.createClass({displayName: 'WidgetDiff',
	    propTypes: {
	        before: React.PropTypes.shape({
	            options: React.PropTypes.object
	        }).isRequired,
	        after: React.PropTypes.shape({
	            options: React.PropTypes.object
	        }).isRequired,
	        title: React.PropTypes.string.isRequired
	    },

	    render: function() {
	        var diff = performDiff(this.props.before,
	                               this.props.after);
	        return React.createElement("div", null, 
	            React.createElement("div", {className: "ui-helper-clearfix"}, 
	                React.createElement(DiffEntry, {entry: diff})
	            )
	        );
	    }
	});

	module.exports = WidgetDiff;


/***/ },
/* 57 */
/***/ function(module, exports, __webpack_require__) {

	var _ = __webpack_require__(60);

	var Graphie = __webpack_require__(72);
	var Util = __webpack_require__(3);

	// The global cache of label data. Its format is:
	// {
	//   hash (e.g. "c21435944d2cf0c8f39d9059cb35836aa701d04a"): {
	//     loaded: a boolean of whether the data has been loaded or not
	//     dataCallbacks: a list of callbacks to call with the data when the data
	//                    is loaded
	//     data: the other data for this hash
	//   },
	//   ...
	// }
	var labelDataCache = {};

	var svgLabelsRegex = /^web\+graphie\:/;
	var hashRegex = /\/([^/]+)$/;

	function isLabeledSVG(url) {
	    return svgLabelsRegex.test(url);
	}

	// For each svg+labels, there are two urls we need to download from. This gets
	// the base url without the suffix, and `getSvgUrl` and `getDataUrl` apply
	// appropriate suffixes to get the image and other data
	function getBaseUrl(url) {
	    return url.replace(svgLabelsRegex, "");
	}

	function getSvgUrl(url) {
	    return getBaseUrl(url) + ".svg";
	}

	function getDataUrl(url) {
	    return getBaseUrl(url) + "-data.json";
	}

	// Get the hash from the url, which is just the filename
	function getUrlHash(url) {
	    var match = url.match(hashRegex);

	    return match && match[1];
	}

	var SvgImage = React.createClass({displayName: 'SvgImage',
	    statics: {
	        // Sometimes other components want to download the actual image e.g. to
	        // determine its size. Here, we transform an .svg-labels url into the
	        // correct image url, and leave normal image urls alone
	        getRealImageUrl: function(url) {
	            if (isLabeledSVG(url)) {
	                return getSvgUrl(url);
	            } else {
	                return url;
	            }
	        }
	    },

	    propTypes: {
	        src: React.PropTypes.string.isRequired,
	        alt: React.PropTypes.string,
	        title: React.PropTypes.string,
	        bottom: React.PropTypes.number,
	        left: React.PropTypes.number,
	        width: React.PropTypes.number,
	        height: React.PropTypes.number,
	        scale: React.PropTypes.number,
	    },

	    getDefaultProps: function() {
	        return {
	            src: "",
	            bottom: 0,
	            left: 0,
	            scale: 1,
	        };
	    },

	    getInitialState: function() {
	        return {
	            imageLoaded: false,
	            imageDimensions: null,
	            dataLoaded: false,
	            labels: [],
	            range: [[0, 0], [0, 0]],
	        };
	    },

	    // Check if all of the resources are loaded in a given state
	    isLoadedInState: function(state) {
	        return state.imageLoaded && state.dataLoaded;
	    },

	    shouldComponentUpdate: function(nextProps, nextState) {
	        // If the props changed, we definitely need to update
	        if (!_.isEqual(this.props, nextProps)) {
	            return true;
	        }

	        // If something changed but 
	        if (!isLabeledSVG(nextProps.src)) {
	            return false;
	        }

	        var wasLoaded = this.isLoadedInState(this.state);
	        var nextLoaded = this.isLoadedInState(nextState);

	        return wasLoaded !== nextLoaded;
	    },

	    render: function() {
	        // Props to send to all images
	        var imageProps = {
	            alt: this.props.alt,
	            title: this.props.title
	        };

	        var width = this.props.width && this.props.width * this.props.scale;
	        var height = this.props.height && this.props.height * this.props.scale;

	        var style = {
	            width: width,
	            height: height,
	            bottom: this.props.bottom,
	            left: this.props.left,
	        };

	        // Just use a normal image if a normal image is provided
	        if (!isLabeledSVG(this.props.src)) {
	            return React.createElement("img", React.__spread({style: style, 
	                        src: this.props.src}, 
	                        imageProps));
	        }

	        var imageUrl = getSvgUrl(this.props.src);
	        var image = React.createElement("img", React.__spread({style: {
	                             width: style.width,
	                             height: style.height,
	                         }, 
	                         src: imageUrl, 
	                         ref: "image", 
	                         onLoad: this.onImageLoad}, 
	                         imageProps));

	        var graphie;
	        // Since we only want to do the graphie setup once, we only render the
	        // graphie once everything is loaded
	        if (this.isLoadedInState(this.state)) {
	            // Use the provided width and height to size the graphie if
	            // possible, otherwise use our own calculated size
	            var box;
	            if (this.sizeProvided()) {
	                box = [width, height];
	            } else {
	                box = [this.state.imageDimensions[0] * this.props.scale,
	                       this.state.imageDimensions[1] * this.props.scale];
	            }

	            var scale = [40 * this.props.scale, 40 * this.props.scale];

	            graphie = React.createElement(Graphie, {
	                ref: "graphie", 
	                box: box, 
	                scale: scale, 
	                range: this.state.range, 
	                options: _.pick(this.state, "labels"), 
	                setup: this.setupGraphie});
	        }

	        return React.createElement("div", {className: "svg-image", 
	                    style: style}, 
	            graphie, 
	            image
	        );
	    },

	    componentWillReceiveProps: function(nextProps) {
	        if (this.props.src !== nextProps.src) {
	            this.setState({
	                imageLoaded: false,
	                dataLoaded: false,
	            });
	        }
	    },

	    componentDidMount: function() {
	        if (isLabeledSVG(this.props.src)) {
	            this.loadResources();
	        }
	    },

	    componentDidUpdate: function() {
	        if (isLabeledSVG(this.props.src) &&
	            !this.isLoadedInState(this.state)) {
	            this.loadResources();
	        }
	    },

	    loadResources: function() {
	        var hash = getUrlHash(this.props.src);

	        // We can't make multiple jsonp calls to the same file because their
	        // callbacks will collide with each other. Instead, we cache the data
	        // and only make the jsonp calls once.
	        if (labelDataCache[hash]) {
	            if (labelDataCache[hash].loaded) {
	                this.onDataLoaded(labelDataCache[hash].data);
	            } else {
	                labelDataCache[hash].dataCallbacks.push(this.onDataLoaded);
	            }
	        } else {
	            var cacheData = {
	                loaded: false,
	                dataCallbacks: [this.onDataLoaded],
	                data: null,
	            };

	            labelDataCache[hash] = cacheData;

	            var dataUrl = getDataUrl(this.props.src);

	            $.ajax({
	                url: dataUrl,
	                dataType: "jsonp",
	                jsonp: false,
	                jsonpCallback: "svgData" + hash,
	                success: function(data)  {
	                    cacheData.data = data;
	                    cacheData.loaded = true;

	                    _.each(cacheData.dataCallbacks, function(callback)  {
	                        callback(cacheData.data);
	                    });
	                },
	                error: function(x, status, error)  {
	                    console.error("Data load failed:", urls.data, error);
	                }
	            });
	        }
	    },

	    onDataLoaded: function(data) {
	        this.setState({
	            dataLoaded: true,
	            labels: data.labels,
	            range: data.range
	        });
	    },

	    sizeProvided: function() {
	        return this.props.width != null && this.props.height != null;
	    },

	    onImageLoad: function() {
	        if (this.sizeProvided()) {
	            // If width and height are provided, we don't need to calculate the
	            // size ourselves
	            this.setState({
	                imageLoaded: true
	            });
	        } else {
	            Util.getImageSize(this.props.src, function(width, height)  {
	                this.setState({
	                    imageLoaded: true,
	                    imageDimensions: [width, height],
	                });
	            }.bind(this));
	        }
	    },

	    setupGraphie: function(graphie, options) {
	        _.map(options.labels, function(labelData)  {
	            // Create labels from the data
	            var label = graphie.label(
	                labelData.coordinates,
	                labelData.content,
	                labelData.alignment,
	                labelData.typesetAsMath,
	                {"font-size": (100 * this.props.scale) + "%"}
	            );

	            // Add back the styles to each of the labels
	            _.each(labelData.style, function(styleValue, styleName)  {
	                label.css(styleName, styleValue);
	            });
	        }.bind(this));
	    }
	});

	module.exports = SvgImage;


/***/ },
/* 58 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);
	var _ = __webpack_require__(60);

	/* A checkbox that syncs its value to props using the
	 * renderer's onChange method, and gets the prop name
	 * dynamically from its props list
	 */
	var PropCheckBox = React.createClass({displayName: 'PropCheckBox',
	    propTypes: {
	        labelAlignment: React.PropTypes.oneOf(["left", "right"])
	    },

	    DEFAULT_PROPS: {
	        label: null,
	        onChange: null,
	        labelAlignment: "left"
	    },

	    getDefaultProps: function() {
	        return this.DEFAULT_PROPS;
	    },

	    propName: function() {
	        var propName = _.find(_.keys(this.props), function(localPropName) {
	            return !_.has(this.DEFAULT_PROPS, localPropName);
	        }, this);

	        if (!propName) {
	            throw new Error("Attempted to create a PropCheckBox with no " +
	                    "prop!");
	        }

	        return propName;
	    },

	    _labelAlignLeft: function() {
	        return this.props.labelAlignment === "left";
	    },

	    render: function() {
	        var propName = this.propName();
	        return React.createElement("label", null, 
	            this._labelAlignLeft() && this.props.label, 
	            React.createElement("input", {type: "checkbox", 
	                    checked: this.props[propName], 
	                    onChange: this.toggle}), 
	            !this._labelAlignLeft() && this.props.label
	        );
	    },

	    toggle: function() {
	        var propName = this.propName();
	        var changes = {};
	        changes[propName] = !this.props[propName];
	        this.props.onChange(changes);
	    }
	});

	module.exports = PropCheckBox;


/***/ },
/* 59 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Stub Tag Editor.
	 *
	 * This is stupidly used by Perseus Zero because I didn't implement
	 * the <TagEditor> for Perseus Zero (since everyone wants me to
	 * delete it anyways).
	 *
	 * This is a small wrapper for a TextListEditor that allows us to
	 * edit raw Tag ID strings in perseus zero (please don't use this).
	 *
	 * It also gives a nicer interface for the group metadata editor
	 * in local test.html mode.
	 */

	var TextListEditor = __webpack_require__(70);
	var EMPTY_ARRAY = [];

	var StubTagEditor = React.createClass({displayName: 'StubTagEditor',
	    propTypes: {
	        value: React.PropTypes.arrayOf(React.PropTypes.string),
	        onChange: React.PropTypes.func.isRequired
	    },

	    getDefaultProps: function() {
	        return {
	            value: EMPTY_ARRAY
	        };
	    },

	    render: function() {
	        return React.createElement("div", null, 
	            React.createElement("div", {style: {fontSize: 14}}, "Tags:"), 
	            /* this is silly, but we have to `|| []` here because
	                we sometimes have the metadata set to null
	                (legacy saves, I think), which doesn't get defaulted
	                through defaultProps */
	            React.createElement(TextListEditor, {
	                options: this.props.value || EMPTY_ARRAY, 
	                layout: "vertical", 
	                onChange: this.props.onChange})
	        );
	    }
	});

	module.exports = StubTagEditor;


/***/ },
/* 60 */
/***/ function(module, exports, __webpack_require__) {

	/* This note applies to rcss, react, and underscore.
	 *
	 * We're faking a node module for this package by just exporting the global.
	 * There are a few complications which led us to this solution as a temporary
	 * fix.
	 *
	 * - Browserify can slow down a lot when you include the other packages (and
	 *   their dependency graphs). We were also battling general browserify
	 *   slowness at this time - browserify 3.4.0 is "good" but later versions
	 *   (3.53 if I remember correctly) are terribly slow (on the order of 20x
	 *   slower).
	 *
	 * - I'm not clear on the details of packaging this so we don't duplicate
	 *   dependencies anywhere. For instance when packaging perseus for webapp we
	 *   need to be careful not to include packages like underscore from our
	 *   dependencies or from the packages we depend on. (note: this is a very good
	 *   opportunity to either explain how existing tools solve the problem or
	 *   create a new tool to solve it)
	 *
	 * - Joel (and Jack)
	 */
	module.exports = window._;


/***/ },
/* 61 */
/***/ function(module, exports, __webpack_require__) {

	/* This note applies to rcss, react, and underscore.
	 *
	 * We're faking a node module for this package by just exporting the global.
	 * There are a few complications which led us to this solution as a temporary
	 * fix.
	 *
	 * - Browserify can slow down a lot when you include the other packages (and
	 *   their dependency graphs). We were also battling general browserify
	 *   slowness at this time - browserify 3.4.0 is "good" but later versions
	 *   (3.53 if I remember correctly) are terribly slow (on the order of 20x
	 *   slower).
	 *
	 * - I'm not clear on the details of packaging this so we don't duplicate
	 *   dependencies anywhere. For instance when packaging perseus for webapp we
	 *   need to be careful not to include packages like underscore from our
	 *   dependencies or from the packages we depend on. (note: this is a very good
	 *   opportunity to either explain how existing tools solve the problem or
	 *   create a new tool to solve it)
	 *
	 * - Joel (and Jack)
	 */
	module.exports = window.React;


/***/ },
/* 62 */
/***/ function(module, exports, __webpack_require__) {

	module.exports = [
	    // standard props "added" by react
	    // (technically the renderer still adds them)
	    "key",
	    "ref",
	    // added by src/renderer.jsx
	    "widgetId",
	    "onChange",
	    "problemNum",
	    "enabledFeatures",
	    "apiOptions",
	    "questionCompleted",
	    "interWidgets",
	    // added by src/editor.jsx, for widgets removing themselves
	    // this is soooo not the right place for this, but alas.
	    "onRemove",
	];


/***/ },
/* 63 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * A work-in-progress of _ methods for objects.
	 * That is, they take an object as a parameter,
	 * and return an object instead of an array.
	 *
	 * TODO(aria): Move this out of interactive2
	 */

	var _ = __webpack_require__(60);

	/**
	 * Does a pluck on keys inside objects in an object
	 *
	 * Ex:
	 * tools = {
	 *     translation: {
	 *         enabled: true
	 *     },
	 *     rotation: {
	 *         enabled: false
	 *     }
	 * };
	 * pluckObject(tools, "enabled") returns {
	 *     translation: true
	 *     rotation: false
	 * }
	 */
	var pluck = function(table, subKey) {
	    return _.object(_.map(table, function(value, key) {
	        return [key, value[subKey]];
	    }));
	};

	/**
	 * Maps an object to an object
	 *
	 * > mapObject({a: '1', b: '2'}, (value, key) => {
	 *       return value + 1;
	 *   });
	 * {a: 2, b: 3}
	 */
	var mapObject = function(obj, lambda) {
	    var result = {};
	    _.each(_.keys(obj), function(key) {
	        result[key] = lambda(obj[key], key);
	    });
	    return result;
	};

	/**
	 * Maps an array to an object
	 *
	 * > mapObjectFromArray(['a', 'b'], function(elem) {
	 *       return elem + elem;
	 *   });
	 * {a: 'aa', b: 'bb'}
	 */
	var mapObjectFromArray = function(arr, lambda) {
	    var result = {};
	    _.each(arr, function(elem) {
	        result[elem] = lambda(elem);
	    });
	    return result;
	};

	module.exports = {
	    pluck: pluck,
	    mapObject: mapObject,
	    mapObjectFromArray: mapObjectFromArray
	};


/***/ },
/* 64 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);
	var _ = __webpack_require__(60);

	/* This component makes its children a drag target. Example:
	 *
	 *     <DragTarget onDrop={this.handleDrop}>Drag to me</DragTarget>
	 *
	 *     ...
	 *
	 *     handleDrop: function(e) {
	 *         this.addImages(e.nativeEvent.dataTransfer.files);
	 *     }
	 *
	 * Now "Drag to me" will be a drag target - when something is dragged over it,
	 * the element will become partially transparent as a visual indicator that
	 * it's a target.
	 */
	// TODO(joel) - indicate before the hover is over the target that it's possible
	// to drag into the target. This would (I think) require a high level handler -
	// like on Perseus itself, waiting for onDragEnter, then passing down the
	// event. Sounds like a pain. Possible workaround - create a div covering the
	// entire page...
	//
	// Other extensions:
	// * custom styles for global drag and dragOver
	// * only respond to certain types of drags (only images for instance)!
	var DragTarget = React.createClass({displayName: 'DragTarget',
	    propTypes: {
	        onDrop: React.PropTypes.func.isRequired,
	        component: React.PropTypes.any,  // component type
	        shouldDragHighlight: React.PropTypes.func
	    },
	    render: function() {
	        var opacity = this.state.dragHover ? { "opacity": 0.3 } : {};
	        var Component = this.props.component;

	        return (
	            React.createElement(Component, React.__spread({}, 
	                this.props, 
	                {style: _.extend({}, this.props.style, opacity), 
	                onDrop: this.handleDrop, 
	                onDragEnd: this.handleDragEnd, 
	                onDragOver: this.handleDragOver, 
	                onDragEnter: this.handleDragEnter, 
	                onDragLeave: this.handleDragLeave}))
	        );
	    },
	    getInitialState: function() {
	        return { dragHover: false };
	    },
	    getDefaultProps: function() {
	        return {
	            component: "div",
	            shouldDragHighlight: function()  {return true;}
	        };
	    },
	    handleDrop: function(e) {
	        e.stopPropagation();
	        e.preventDefault();
	        this.setState({ dragHover: false });
	        this.props.onDrop(e);
	    },
	    handleDragEnd: function() {
	        this.setState({ dragHover: false });
	    },
	    handleDragOver: function(e) {
	        e.preventDefault();
	    },
	    handleDragLeave: function() {
	        this.setState({ dragHover: false });
	    },
	    handleDragEnter: function(e) {
	        this.setState({ dragHover: this.props.shouldDragHighlight(e) });
	    }
	});

	module.exports = DragTarget;


/***/ },
/* 65 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * For math rendered using KaTex and/or MathJax. Use me like <TeX>2x + 3</TeX>.
	 */
	// TODO(joel) - require MathJax / katex so they don't have to be global

	var React = __webpack_require__(61);

	var pendingScripts = [];
	var needsProcess = false;
	var timeout = null;

	function process(script, callback) {
	    pendingScripts.push(script);
	    if (!needsProcess) {
	        needsProcess = true;
	        timeout = setTimeout(doProcess, 0, callback);
	    }
	}

	function doProcess(callback) {
	    MathJax.Hub.Queue(function() {
	        var oldElementScripts = MathJax.Hub.elementScripts;
	        MathJax.Hub.elementScripts = function(element) {
	            var scripts = pendingScripts;
	            pendingScripts = [];
	            needsProcess = false;
	            return scripts;
	        };

	        try {
	            return MathJax.Hub.Process(null, callback);
	        } catch (e) {
	            // IE8 requires `catch` in order to use `finally`
	            throw e;
	        } finally {
	            MathJax.Hub.elementScripts = oldElementScripts;
	        }
	    });
	}

	var TeX = React.createClass({displayName: 'TeX',
	    getDefaultProps: function() {
	        return {
	            // Called after math is rendered or re-rendered
	            onRender: function() {},
	            onClick: null
	        };
	    },

	    render: function() {
	        return React.createElement("span", {
	                style: this.props.style, 
	                onClick: this.props.onClick}, 
	            React.createElement("span", {ref: "mathjax"}), 
	            React.createElement("span", {ref: "katex"})
	        );
	    },

	    componentDidMount: function() {
	        var text = this.props.children;
	        var onRender = this.props.onRender;

	        try {
	            var katexHolder = this.refs.katex.getDOMNode();
	            katex.render(text, katexHolder);
	            onRender();
	            return;
	        } catch (e) {
	            /* jshint -W103 */
	            if (e.__proto__ !== katex.ParseError.prototype) {
	            /* jshint +W103 */
	                throw e;
	            }
	        }

	        this.setScriptText(text);
	        process(this.script, onRender);
	    },

	    componentDidUpdate: function(prevProps, prevState) {
	        var oldText = prevProps.children;
	        var newText = this.props.children;
	        var onRender = this.props.onRender;

	        if (oldText !== newText) {
	            try {
	                var katexHolder = this.refs.katex.getDOMNode();
	                katex.render(newText, katexHolder);
	                if (this.script) {
	                    var jax = MathJax.Hub.getJaxFor(this.script);
	                    if (jax) {
	                        jax.Remove();
	                    }
	                }
	                onRender();
	                return;
	            } catch (e) {
	                /* jshint -W103 */
	                if (e.__proto__ !== katex.ParseError.prototype) {
	                /* jshint +W103 */
	                    throw e;
	                }
	            }

	            $(this.refs.katex.getDOMNode()).empty();

	            if (this.script) {
	                var component = this;
	                MathJax.Hub.Queue(function() {
	                    var jax = MathJax.Hub.getJaxFor(component.script);
	                    if (jax) {
	                        return jax.Text(newText, onRender);
	                    } else {
	                        component.setScriptText(newText);
	                        process(component.script, onRender);
	                    }
	                });
	            } else {
	                this.setScriptText(newText);
	                process(this.script, onRender);
	            }
	        }
	    },

	    setScriptText: function(text) {
	        if (!this.script) {
	            this.script = document.createElement("script");
	            this.script.type = "math/tex";
	            this.refs.mathjax.getDOMNode().appendChild(this.script);
	        }
	        if ("text" in this.script) {
	            // IE8, etc
	            this.script.text = text;
	        } else {
	            this.script.textContent = text;
	        }
	    },

	    componentWillUnmount: function() {
	        if (this.script) {
	            var jax = MathJax.Hub.getJaxFor(this.script);
	            if (jax) {
	                jax.Remove();
	            }
	        }
	    }
	});

	module.exports = TeX;


/***/ },
/* 66 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Changeable
	 *
	 * Adds a this.change() function to a component
	 *
	 * This.change takes prop changes as parameters, and calls
	 * this.props.onChange with the modified props.
	 */

	var _ = __webpack_require__(60);

	var WIDGET_PROP_BLACKLIST = __webpack_require__(62);

	var USAGE = "Usage:\n" +
	            "  this.change({propName: 5}, callback);\n" +
	            "  this.change(\"propName\", 5, callback);\n" +
	            "  this.change(\"propName\")";

	/**
	 * Primary helper function for this.change()
	 *
	 * Takes the parameters in a consistent style, once this.change() has
	 * figured out which way it was called.
	 */
	var _changeMultiple = function(component, newProps, callback) {
	    // Omit "default" props:
	    // ref and key come from react, and don't actually represent
	    //   the conceptual state of our component
	    // onChange comes from our parent to allow this modification,
	    //   and doesn't conceptually represent the state of our component
	    var currProps = _.omit(component.props, WIDGET_PROP_BLACKLIST);
	    var nextProps = _.extend(currProps, newProps);
	    component.props.onChange(nextProps, callback);
	};

	/**
	 * Helper function for changing a single prop
	 */
	var _changeSingle = function(component, propName, value, callback) {
	    if (value === undefined) {
	        // If called with a single prop name, return a lambda to change
	        // a single prop on the current object
	        return _.partial(_changeSingle, component, propName);
	    } else {
	        // If called with two values, change a single prop of the
	        // current object
	        var newProps = {};
	        newProps[propName] = value;
	        _changeMultiple(component, newProps, callback);
	    }
	};

	/**
	 * this.change()
	 *
	 * Can be called as follows:
	 * this.change(newProps, callback);
	 *
	 * this.change(propName, propValue, callback);
	 *
	 * this.change(propName) -> returns a lambda that takes a prop value to
	 * set and a callback to call after having set that value.
	 */
	var change = function(newPropsOrSinglePropName,
	                      propValue,
	                      callback) {

	    if (_.isObject(newPropsOrSinglePropName) &&
	            callback === undefined) {
	        // Called with an object of multiple props to change
	        callback = propValue;
	        return _changeMultiple(
	            this,
	            newPropsOrSinglePropName,  // object newProps
	            callback
	        );

	    } else if (_.isString(newPropsOrSinglePropName)) {
	        // Called with a string propName of a single prop to change
	        return _changeSingle(
	            this,
	            newPropsOrSinglePropName,  // string propName
	            propValue,
	            callback
	        );

	    } else {
	        throw new Error("Invalid types sent to this.change(): " +
	                _.toArray(arguments).join() + "\n" + USAGE);
	    }
	};

	var Changeable = {
	    propTypes: {
	        onChange: React.PropTypes.func.isRequired
	    },
	    change: change
	};

	module.exports = Changeable;


/***/ },
/* 67 */
/***/ function(module, exports, __webpack_require__) {

	var _ = __webpack_require__(60);

	var WIDGET_PROP_BLACKLIST = __webpack_require__(62);

	var EditorJsonify = {
	    serialize: function() {
	        // Omit props that get passed to all widgets
	        return _.omit(this.props, WIDGET_PROP_BLACKLIST);
	    }
	};

	module.exports = EditorJsonify;


/***/ },
/* 68 */
/***/ function(module, exports, __webpack_require__) {

	/* Free implementation of getUserInput. This should be used sparingly, since it
	 * just returns all the widget's props rather than picking out those which were
	 * input by the user.
	 */
	var WIDGET_PROP_BLACKLIST = __webpack_require__(62);

	var WidgetJsonifyDeprecated = {
	    getUserInput: function() {
	        // Omit props that get passed to all widgets
	        return _.omit(this.props, WIDGET_PROP_BLACKLIST);
	    }
	};

	module.exports = WidgetJsonifyDeprecated;


/***/ },
/* 69 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);
	var RCSS = __webpack_require__(114);
	var _ = __webpack_require__(60);

	var colors = {
	    grayLight: '#aaa',
	    basicBorderColor: '#ccc',
	    white: '#fff'
	};

	var infoTip = {
	    display: 'inline-block',
	    marginLeft: '5px',
	    position: 'relative'
	};

	var infoTipI = {
	    cursor: 'pointer'
	};

	var infoTipContainer = {
	    position: 'absolute',
	    'top': '-12px',
	    left: '22px',
	    zIndex: '1000'
	};

	var triangleBeforeAfter = {
	    borderBottom: '9px solid transparent',
	    borderTop: '9px solid transparent',
	    content: ' ',
	    height: '0',
	    position: 'absolute',
	    'top': '0',
	    width: '0'
	};

	var infoTipTriangle = {
	    height: '10px',
	    left: '0',
	    position: 'absolute',
	    'top': '8px',
	    width: '0',
	    zIndex: '1',

	    ':before': _.extend({}, triangleBeforeAfter, {
	        borderRight: '9px solid #bbb',
	        right: '0',
	    }),

	    ':after': _.extend({}, triangleBeforeAfter, {
	        borderRight: ("9px solid " + colors.white),
	        right: '-1px'
	    })
	};

	var basicBorder = {
	    border: ("1px solid " + colors.basicBorderColor)
	};

	var boxShadow = function(str)  { return { boxShadow: str }; };

	var verticalShadow = RCSS.merge(
	    basicBorder,
	    boxShadow(("0 1px 3px " + colors.basicBorderColor)),
	    { borderBottom: ("1px solid " + colors.grayLight) }
	);

	var infoTipContentContainer = RCSS.merge(verticalShadow, {
	    background: colors.white,
	    padding: '5px 10px',
	    width: '240px'
	});

	RCSS.createClass(infoTip);
	RCSS.createClass(infoTipI);
	RCSS.createClass(infoTipTriangle);
	RCSS.createClass(verticalShadow);
	RCSS.createClass(infoTipContainer);
	RCSS.createClass(infoTipContentContainer);

	var questionMark = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA3NpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NDkxMSwgMjAxMy8xMC8yOS0xMTo0NzoxNiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo2N2M3NTAxYS04YmVlLTQ0M2MtYmRiNS04OGM2N2IxN2NhYzEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OUJCRTk4Qjc4NjAwMTFFMzg3QUJDNEI4Mzk2QTRGQkQiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OUJCRTk4QjY4NjAwMTFFMzg3QUJDNEI4Mzk2QTRGQkQiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIChNYWNpbnRvc2gpIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6NGE5ZDI0OTMtODk1NC00OGFkLTlhMTgtZDAwM2MwYWNjNDJlIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjY3Yzc1MDFhLThiZWUtNDQzYy1iZGI1LTg4YzY3YjE3Y2FjMSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pqm89uYAAADMSURBVHjaXJA9DoJAEIUH1M4TUHIFsCMGen9OwCGw1YRGW2ntKel0exsojHIBC0ouQAyUviFDstmXfNmZeS+zm7XSNCXRFiRgJf0bXIHixpbhGdxBBJYC1w/xaA424MhNEATkui71fU9KqfEU78UbD9PdbJRlOdae55GmhIP+1NV1TcMwkOM41DSNHvRtMhTHMRVFQW3b6mOLgx99kue5GRp/gIOZuZGvNpTNwjD8oliANU+qqqKu6/TQBdymN57AHjzBT+B6Jx79BRgAvc49kQA4yxgAAAAASUVORK5CYII='; // @NoLint

	var InfoTip = React.createClass({displayName: 'InfoTip',
	    getInitialState: function() {
	        return {
	            hover: false
	        };
	    },

	    render: function() {
	        return React.createElement("div", {className: infoTip.className}, 
	            React.createElement("img", {width: 10, 
	                 height: 10, 
	                 src: questionMark, 
	                 onMouseEnter: this.handleMouseEnter, 
	                 onMouseLeave: this.handleMouseLeave}), 
	            React.createElement("div", {className: infoTipContainer.className, 
	                 style: {display: this.state.hover ? 'block' : 'none'}}, 
	                React.createElement("div", {className: infoTipTriangle.className}), 
	                /* keep the classes here - used for selectors on KA */
	                React.createElement("div", {className: infoTipContentContainer.className}, 
	                    this.props.children
	                )
	            )
	        );
	    },

	    handleMouseEnter: function() {
	        this.setState({hover: true});
	    },

	    handleMouseLeave: function() {
	        this.setState({hover: false});
	    }
	});

	module.exports = InfoTip;


/***/ },
/* 70 */
/***/ function(module, exports, __webpack_require__) {

	var _ = __webpack_require__(60);

	var textWidthCache = {};
	function getTextWidth(text) {
	    if (!textWidthCache[text]) {
	        // Hacky way to guess the width of an input box
	        var $test = $("<span>").text(text).appendTo("body");
	        textWidthCache[text] = $test.width() + 5;
	        $test.remove();
	    }
	    return textWidthCache[text];
	}


	var TextListEditor = React.createClass({displayName: 'TextListEditor',
	    propTypes: {
	        options: React.PropTypes.array,
	        layout: React.PropTypes.string,
	        onChange: React.PropTypes.func.isRequired
	    },

	    getDefaultProps: function() {
	        return {
	            options: [],
	            layout: "horizontal"
	        };
	    },

	    getInitialState: function() {
	        return {
	            items: this.props.options.concat("")
	        };
	    },

	    componentWillReceiveProps: function(nextProps) {
	        this.setState({
	            items: nextProps.options.concat("")
	        });
	    },

	    render: function() {
	        var className = [
	            "perseus-text-list-editor",
	            "ui-helper-clearfix",
	            "layout-" + this.props.layout
	        ].join(" ");

	        var inputs = _.map(this.state.items, function(item, i) {
	            return React.createElement("li", {key: i}, 
	                React.createElement("input", {
	                    ref: "input_" + i, 
	                    type: "text", 
	                    value: item, 
	                    onChange: this.onChange.bind(this, i), 
	                    onKeyDown: this.onKeyDown.bind(this, i), 
	                    style: {width: getTextWidth(item)}})
	            );
	        }, this);

	        return React.createElement("ul", {className: className}, inputs);
	    },

	    onChange: function(index, event) {
	        var items = _.clone(this.state.items);
	        items[index] = event.target.value;

	        if (index === items.length - 1) {
	            items = items.concat("");
	        }

	        this.setState({items: items});
	        this.props.onChange(_.compact(items));
	    },

	    onKeyDown: function(index, event) {
	        var which = event.nativeEvent.keyCode;

	        // Backspace deletes an empty input...
	        if (which === 8 /* backspace */ && this.state.items[index] === "") {
	            event.preventDefault();

	            var items = _.clone(this.state.items);
	            var focusIndex = (index === 0) ? 0 : index - 1;

	            if (index === items.length - 1 &&
	                    (index === 0 || items[focusIndex] !== "")) {
	                // ...except for the last one, iff it is the only empty
	                // input at the end.
	                this.refs["input_" + focusIndex].getDOMNode().focus();
	            } else {
	                items.splice(index, 1);
	                this.setState({items: items}, function() {
	                    this.refs["input_" + focusIndex].getDOMNode().focus();
	                });                
	            }

	        // Deleting the last character in the second-to-last input removes it
	        } else if (which === 8 /* backspace */ &&
	                this.state.items[index].length === 1 &&
	                index === this.state.items.length - 2) {
	            event.preventDefault();

	            var items = _.clone(this.state.items);
	            items.splice(index, 1);
	            this.setState({items: items});
	            this.props.onChange(_.compact(items));

	        // Enter adds an option below the current one...
	        } else if (which === 13 /* enter */) {
	            event.preventDefault();

	            var items = _.clone(this.state.items);
	            var focusIndex = index + 1;

	            if (index === items.length - 2) {
	                // ...unless the empty input is just below.
	                this.refs["input_" + focusIndex].getDOMNode().focus();
	            } else {
	                items.splice(focusIndex, 0, "");
	                this.setState({items: items}, function() {
	                    this.refs["input_" + focusIndex].getDOMNode().focus();
	                });
	            }
	        }
	    }
	});

	module.exports = TextListEditor;


/***/ },
/* 71 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * A <select> component rendered with classes instead of natively,
	 * so that the classes may be styled/animated/magics
	 *
	 * Usage:
	 * <FancySelect value={1}>
	 *     <FancySelect.Option value={0}>text0</FancySelect.Option>
	 *     <FancySelect.Option value={1}>text1</FancySelect.Option>
	 *     <FancySelect.Option value={2}>text2</FancySelect.Option>
	 * </FancySelect>
	 *
	 * Here be dragons.
	 */

	var React = __webpack_require__(61);

	var DROPDOWN_OFFSET = 76;

	var FancyOption = React.createClass({displayName: 'FancyOption',
	    render: function() {
	        throw new Error("FancyOption shouldn't ever be actually rendered");
	    }
	});

	var FancySelect = React.createClass({displayName: 'FancySelect',

	    propTypes: {
	        value: React.PropTypes.any.isRequired,
	        className: React.PropTypes.string,
	        onChange: React.PropTypes.func
	    },

	    getDefaultProps: function() {
	        return {
	            onChange: function()  { }
	        };
	    },

	    getInitialState: function() {
	        return {
	            active: false,
	            // Keep track of whether we've closed this select
	            // from open so that we can only run CSS animations
	            // when closing/opening, and not on page load
	            // If we just use active, we get a closing animation
	            // when the element loads :(.
	            closed: false,
	            // Used to namespace $(document) event handlers
	            selectorNamespace: _.uniqueId("fancy")
	        };
	    },

	    render: function() {
	        var children = _.flatten([this.props.children || []]);

	        // Some css-box magic:
	        // We render all of the options on top of each other in a hidden,
	        // floated span. This span then forces the <FancySelect>'s
	        // width to be large enough to fit the largest option when
	        // selected, so that the page doesn't have to re-flow when changing
	        // select items.
	        var optionSizer = React.createElement("span", {style: {
	                    display: "inline-block",
	                    float: "left",
	                    visibility: "hidden",
	                    height: 0
	                }}, 
	            _.map(children, function(option)  {
	                return React.createElement("div", {className: "fancy-select-value-hidden", 
	                            style: {height: 0}}, 
	                    option.props.children
	                );
	            })
	        );

	        var selectedOption = _.find(
	            children,
	            function(c)  {return c.props.value === this.props.value;}.bind(this)
	        );

	        var selectBoxClassName = React.addons.classSet({
	            "fancy-select": true,
	            active: this.state.active,
	            closed: this.state.closed
	        });

	        var selectBox = React.createElement("div", {className: selectBoxClassName, 
	                onClick: this._swapActive}, 
	                optionSizer, 
	                /* position this absolutely so it goes on top
	                    of the optionSizer, not next to it */
	                React.createElement("span", {
	                        className: "fancy-select-value", 
	                        style: {position: "absolute"}}, 
	                    selectedOption.props.children
	                )
	        );

	        var options = _.map(children, function(option, i)  {
	            // options can specify visible={true|false|null/undefined} to
	            // control whether they are displayed always, never, or when
	            // active (the default). `true` is useful if you want to manage
	            // visibility manually via css.
	            var visible = option.props.visible != null ?
	                    option.props.visible :
	                    this.state.active;
	            if (!visible) {
	                return null;
	            }

	            var className = React.addons.classSet({
	                "fancy-option": true,
	                active: this.state.active,
	                closed: this.state.closed,
	                selected: option.props.value === this.props.value
	            });
	            if (option.props.className) {
	                className += " " + option.props.className;
	            }

	            var translate;
	            var transition;
	            if (this.state.active) {
	                var offset = DROPDOWN_OFFSET * i;
	                translate = "translate3d(0, " + offset + "px, 0)";
	                transition = "0.35s ease-in";
	            } else {
	                translate = "translate3d(0, 0, 0)";
	                transition = "0.35s ease-out";
	            }
	            var style = _.extend({}, option.props.style, {
	                WebkitTransform: translate,
	                transform: translate,
	                WebkitTransition: transition,
	                transition: transition
	            });

	            return React.createElement("li", {
	                    className: className, 
	                    key: i, 
	                    style: style, 
	                    onClick: function()  {
	                        this._unbindClickHandler();
	                        this.props.onChange(option.props.value, option);
	                        this.setState({
	                            active: false,
	                            closed: true
	                        });
	                    }.bind(this)}, 
	                option.props.children
	            );
	        }.bind(this));

	        var optionsBoxClassName = React.addons.classSet({
	            "fancy-select-options": true,
	            active: this.state.active,
	            closed: this.state.closed
	        });

	        var height = DROPDOWN_OFFSET * _.size(children);
	        var style = {
	            clip: "rect(0, auto, " + height + "px, 0)"
	        };

	        return React.createElement("div", {className: this.props.className}, 
	            selectBox, 
	            React.createElement("div", {className: "fancy-select-options-wrapper"}, 
	                React.createElement("ul", {className: optionsBoxClassName, style: style}, 
	                    options
	                )
	            )
	        );
	    },

	    _swapActive: function() {
	        var active = !this.state.active;
	        var closed = !active;

	        // Prepare to detect clicks outside of the dropdown
	        if (active) {
	            this._bindClickHandler();
	        } else {
	            this._unbindClickHandler();
	        }

	        this.setState({
	            active: active,
	            closed: closed
	        });
	    },

	    _bindClickHandler: function() {
	        // Close the dropdown when the user clicks elsewhere
	        $(document).on("vclick." + this.state.selectorNamespace, function(e)  {
	            // Detect whether the target has our React DOM node as a parent
	            var $this = $(this.getDOMNode());
	            var $closestWidget = $(e.target).closest($this);
	            if (!$closestWidget.length) {
	                this._swapActive();
	            }
	        }.bind(this));
	    },

	    _unbindClickHandler: function() {
	        $(document).off("." + this.state.selectorNamespace);
	    },

	    componentWillUnmount: function() {
	        this._unbindClickHandler();
	    },
	});

	FancySelect.Option = FancyOption;

	module.exports = FancySelect;


/***/ },
/* 72 */
/***/ function(module, exports, __webpack_require__) {

	var _ = __webpack_require__(60);
	var GraphieClasses = __webpack_require__(105);
	var Movables = __webpack_require__(106);

	var GraphieMovable = GraphieClasses.GraphieMovable;

	var deepEq = __webpack_require__(3).deepEq;
	var nestedMap = __webpack_require__(3).nestedMap;
	var assert = __webpack_require__(89).assert;

	var createGraphie = KhanUtil.createGraphie;

	var Graphie = React.createClass({displayName: 'Graphie',
	    propTypes: {
	        box: React.PropTypes.arrayOf(React.PropTypes.number).isRequired,
	        range: React.PropTypes.arrayOf(
	            React.PropTypes.arrayOf(React.PropTypes.number)
	        ),
	        options: React.PropTypes.object,
	        setup: React.PropTypes.func.isRequired,
	        onClick: React.PropTypes.func,
	        onMouseDown: React.PropTypes.func,
	        onMouseUp: React.PropTypes.func,
	        onMouseMove: React.PropTypes.func
	    },

	    getDefaultProps: function() {
	        return {
	            range: [[-10, 10], [-10, 10]],
	            options: {}
	        };
	    },

	    render: function() {
	        return React.createElement("div", {className: "graphie-container"}, 
	            React.createElement("div", {className: "graphie", ref: "graphieDiv"})
	        );
	    },

	    componentDidMount: function() {
	        this._setupGraphie();
	        this._updateMovables();
	    },

	    shouldComponentUpdate: function(nextProps) {
	        return !deepEq(this.props, nextProps);
	    },

	    componentDidUpdate: function(prevProps) {
	        // If someone changes the setup function passed in, we should
	        // technically setup graphie again. But that's definitely an
	        // anti-pattern, since it is most-likely caused by passing in an
	        // anonymous function rather than a "real" change, and re-rendering
	        // in that case would cause us to constantly re-setup graphie, which
	        // would have horrible performance implications. In order to avoid
	        // those, we just warn here.
	        if (this.props.setup !== prevProps.setup &&
	                window.console && window.console.warn) {
	            window.console.warn("<Graphie> was given a new setup function. " +
	                    "This is a bad idea; please refactor your code to give " +
	                    "the same setup function reference to <Graphie> on " +
	                    "every render.");
	        }
	        if (!deepEq(this.props.options, prevProps.options) ||
	                !deepEq(this.props.box, prevProps.box) ||
	                !deepEq(this.props.range, prevProps.range)) {
	            this._setupGraphie();
	        }
	        this._updateMovables();
	    },

	    /**
	     * Allow parents of the <Graphie> component to grab a reference to the
	     * underlying graphie object using
	     * `this.refs.graphieComponent.getGraphie()`
	     *
	     * This shouldn't be necessary for 90% of cases, but the power is there.
	     * Use it for good and not evil.
	     */
	    getGraphie: function() {
	        return this._graphie;
	    },

	    // bounds-checked range
	    _range: function() {
	        return _.map(this.props.range, function(dimRange)  {
	            if (dimRange[0] >= dimRange[1]) {
	                return [-10, 10];
	            } else {
	                return dimRange;
	            }
	        });
	    },

	    _box: function() {
	        return _.map(this.props.box, function(pixelDim)  {
	            // 340 = default size in the editor. exact value
	            // is arbitrary; this is just a safety check.
	            return pixelDim > 0 ? pixelDim : 340;
	        });
	    },

	    _scale: function() {
	        var box = this._box();
	        var range = this._range();
	        return _.map(box, function(pixelDim, i)  {
	            var unitDim = range[i][1] - range[i][0];
	            return pixelDim / unitDim;
	        });
	    },

	    _setupGraphie: function() {
	        this._removeMovables();

	        var graphieDiv = this.refs.graphieDiv.getDOMNode();
	        $(graphieDiv).empty();
	        var graphie = this._graphie = createGraphie(graphieDiv);

	        // This has to be called before addMouseLayer. You can re-init
	        // with graphInit later if you prefer
	        graphie.init({
	            range: this._range(),
	            scale: this._scale()
	        });
	        graphie.addMouseLayer({
	            onClick: this.props.onClick,
	            onMouseDown: this.props.onMouseDown,
	            onMouseUp: this.props.onMouseUp,
	            onMouseMove: this.props.onMouseMove
	        });

	        graphie.snap = this.props.options.snapStep || [1, 1];

	        this.props.setup(graphie, _.extend({
	            range: this._range(),
	            scale: this._scale()
	        }, this.props.options));
	    },

	    _removeMovables: function() {
	        // _.invoke works even when this._movables is undefined
	        _.invoke(this._movables, "remove");
	        this._movables = {};
	    },

	    _renderMovables: function(children, options) {
	        // Each leaf of `children` is a movable descriptor created by a call to
	        // some `GraphieMovable`, such as `MovablePoint`.
	        //
	        // This function takes these descriptors and renders them into
	        // on-screen movables, or updates on-screen movables for
	        // descriptors when possible.
	        //
	        // If there is no movable with that key already, this descriptor is
	        // stored in this._movables and promoted to an on-screen movable by
	        // calling `child.add(graphie)`.
	        //
	        // If a movable of the same type with the same key exists already,
	        // we take `child.props` and give them to the already-existing
	        // on-screen movable, and call `movable.modify()`

	        var graphie = options.graphie;
	        var oldMovables = options.oldMovables;
	        var newMovables = options.newMovables; /* output parameter */

	        var renderChildren = function(elem)  {
	            _.each(elem.movableProps, function(prop)  {
	                // Render the children, and save the results of that
	                // render to the appropriate props
	                elem.props[prop] = this._renderMovables(
	                    elem.props[prop],
	                    options
	                );
	            }.bind(this));
	        }.bind(this);

	        // Add/modify movables

	        // We want to keep track of whether we have added a new svg element,
	        // because if we have, then we need to call .toFront() on any svg
	        // elements occurring afterwards. If this happens, we set
	        // `areMovablesOutOfOrder` to true:
	        var areMovablesOutOfOrder = false;
	        return nestedMap(children, function(childDescriptor)  {
	            if (!childDescriptor) {
	                // Still increment the key to avoid cascading key changes
	                // on hiding/unhiding children, i.e. by using
	                // {someBoolean && <MovablePoint />}
	                options.nextKey++;
	                // preserve the null/undefined in the resulting array
	                return childDescriptor;
	            }

	            // Instantiate the descriptor to turn it into a real Movable
	            var child = new childDescriptor.type(childDescriptor);
	            assert(child instanceof GraphieMovable,
	                "All children of a Graphie component must be Graphie " +
	                "movables");

	            // Give each child a key
	            var keyProp = childDescriptor.key;
	            var key = (keyProp == null) ?
	                    ("_no_id_" + options.nextKey) :
	                    keyProp;
	            options.nextKey++;
	            var ref = childDescriptor.ref;

	            // We render our children first. This allows us to replace any
	            // `movableProps` on our child with the on-screen movables
	            // corresponding with those descriptors.
	            renderChildren(child);

	            var prevMovable = oldMovables[key];
	            if (!prevMovable) {
	                // We're creating a new child
	                child.add(graphie);
	                areMovablesOutOfOrder = true;

	                newMovables[key] = child;

	            } else if (child.constructor === prevMovable.constructor) {
	                // We're updating an old child
	                prevMovable.props = child.props;
	                var modifyResult = prevMovable.modify(graphie);
	                if (modifyResult === "reordered") {
	                    areMovablesOutOfOrder = true;
	                }

	                newMovables[key] = prevMovable;

	            } else {
	                // We're destroying an old child and replacing it
	                // with a new child of a different type

	                // This generally is a bad idea, so warn about it if this
	                // is being caused by implicit keys
	                if (keyProp == null) {
	                    if (typeof console !== "undefined" && console.warn) {
	                        console.warn("Replacing a <Graphie> child with a " +
	                                "child of a different type. Please add keys " +
	                                "to your <Graphie> children");
	                    }
	                }

	                prevMovable.remove();
	                child.add(graphie);
	                areMovablesOutOfOrder = true;

	                newMovables[key] = child;
	            }

	            if (areMovablesOutOfOrder) {
	                newMovables[key].toFront();
	            }

	            if (ref) {
	                this.movables[ref] = newMovables[key];
	            }

	            return newMovables[key];
	        }.bind(this));
	    },

	    // Sort of like react diffing, but for movables
	    _updateMovables: function() {
	        var graphie = this._graphie;

	        var oldMovables = this._movables;
	        var newMovables = {};
	        this._movables = newMovables;
	        this.movables = {};

	        this._renderMovables(this.props.children, {
	            nextKey: 1,
	            graphie: graphie,
	            oldMovables: oldMovables,
	            newMovables: newMovables
	        });

	        // Remove any movables that no longer exist in the child array
	        _.each(oldMovables, function(oldMovable, key)  {
	            if (!newMovables[key]) {
	                oldMovable.remove();
	            }
	        });
	    }
	});

	// Attach Graphie.createClass and Graphie.createSimpleClass
	_.extend(Graphie, GraphieClasses);
	// Attach the Movable react components for easy reference
	_.extend(Graphie, Movables);

	module.exports = Graphie;


/***/ },
/* 73 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);
	var _     = __webpack_require__(60);

	var PT    = React.PropTypes;

	var sortableDragging = {
	    cursor: "ns-resize"
	};

	var sortableEnabled = {
	    cursor: "pointer"
	};

	var userSelect = function(rhs)  {
	    return {
	        "-webkit-user-select": rhs,
	        "-khtml-user-drag": rhs,
	        "-khtml-user-select": rhs,
	        "-moz-user-select": rhs,
	        "-ms-user-select": rhs,
	        userSelect: rhs
	    };
	};

	var sortableDisabled = userSelect("none");

	// Takes an array of components to sort
	var SortableArea = React.createClass({displayName: 'SortableArea',
	    propTypes: {
	        components: PT.arrayOf(PT.node).isRequired,
	        onReorder: PT.func.isRequired,
	        verify: PT.func
	    },
	    render: function() {
	        var sortables = _(this.state.components).map(function(component, index) 
	            {return React.createElement(SortableItem, {
	                index: index, 
	                component: component, 
	                area: this, 
	                key: component.key, 
	                draggable: component.props.draggable, 
	                dragging: index === this.state.dragging});}.bind(this)
	        );
	        return React.createElement("ol", {className: this.props.className, style: this.props.style}, 
	            sortables
	        );
	    },
	    getDefaultProps: function() {
	        return { verify: function()  {return true;} };
	    },
	    getInitialState: function() {
	        return {
	            // index of the component being dragged
	            dragging: null,
	            components: this.props.components
	        };
	    },
	    componentWillReceiveProps: function(nextProps) {
	        this.setState({ components: nextProps.components });
	    },
	    // Alternatively send each handler to each component individually,
	    // partially applied
	    onDragStart: function(startIndex) {
	        this.setState({ dragging: startIndex });
	    },
	    onDrop: function() {
	        // tell the parent component
	        this.setState({ dragging: null });
	        this.props.onReorder(this.state.components);
	    },
	    onDragEnter: function(enterIndex) {
	        // When a label is first dragged it triggers a dragEnter with itself,
	        // which we don't care about.
	        if (this.state.dragging === enterIndex) {
	            return;
	        }

	        var newComponents = this.state.components.slice();

	        // splice the tab out of its old position
	        var removed = newComponents.splice(this.state.dragging, 1);
	        // ... and into its new position
	        newComponents.splice(enterIndex, 0, removed[0]);

	        var verified = this.props.verify(newComponents);
	        if (verified) {
	            this.setState({
	                dragging: enterIndex,
	                components: newComponents
	            });
	        }
	        return verified;
	    },

	    // Firefox refuses to drag an element unless you set data on it. Hackily
	    // add data each time an item is dragged.
	    componentDidMount: function() {
	        this._setDragEvents();
	    },
	    componentDidUpdate: function() {
	        this._setDragEvents();
	    },
	    _listenEvent: function(e) {
	        e.dataTransfer.setData('hackhackhack', 'because browsers!');
	    },
	    _cancelEvent: function(e) {
	        // prevent the browser from redirecting to 'because browsers!'
	        e.preventDefault();
	    },
	    _setDragEvents: function() {
	        this._dragItems = this._dragItems || [];
	        var items = this.getDOMNode().querySelectorAll('[draggable=true]');
	        var oldItems = _(this._dragItems).difference(items);
	        var newItems = _(items).difference(this._dragItems);

	        _(newItems).each(function(dragItem)  {
	            dragItem.addEventListener('dragstart', this._listenEvent);
	            dragItem.addEventListener('drop',      this._cancelEvent);
	        }.bind(this));

	        _(oldItems).each(function(dragItem)  {
	            dragItem.removeEventListener('dragstart', this._listenEvent);
	            dragItem.removeEventListener('drop',      this._cancelEvent);
	        }.bind(this));
	    }
	});

	// An individual sortable item
	var SortableItem = React.createClass({displayName: 'SortableItem',
	    propTypes: {
	        // item: what is this?
	    },
	    render: function() {
	        var dragState = "sortable-disabled";
	        if (this.props.dragging) {
	            dragState = "sortable-dragging";
	        } else if (this.props.draggable) {
	            dragState = "sortable-enabled";
	        }

	        return React.createElement("li", {draggable: this.props.draggable, 
	                   className: dragState, 
	                   onDragStart: this.handleDragStart, 
	                   onDrop: this.handleDrop, 
	                   onDragEnter: this.handleDragEnter, 
	                   onDragOver: this.handleDragOver}, 
	            this.props.component
	        );
	    },
	    handleDragStart: function(e) {
	        e.nativeEvent.dataTransfer.effectAllowed = "move";
	        this.props.area.onDragStart(this.props.index);
	    },
	    handleDrop: function() {
	        this.props.area.onDrop(this.props.index);
	    },
	    handleDragEnter: function(e) {
	        var verified = this.props.area.onDragEnter(this.props.index);
	        // Ideally this would change the cursor based on whether this is a
	        // valid place to drop.
	        e.nativeEvent.dataTransfer.effectAllowed = verified ? "move" : "none";
	    },
	    handleDragOver: function(e) {
	        // allow a drop by preventing default handling
	        e.preventDefault();
	    }
	});

	module.exports = SortableArea;


/***/ },
/* 74 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);
	var _ = __webpack_require__(60);

	// TODO(joel/jack) fix z-index issues https://s3.amazonaws.com/uploads.hipchat.com/6574/29028/yOApjwmgiMhEZYJ/Screen%20Shot%202014-05-30%20at%203.34.18%20PM.png
	// z-index: 3 on perseus-formats-tooltip seemed to work

	/**
	 * A generic tooltip library for React.js
	 *
	 * This should eventually end up in react-components
	 *
	 * Interface: ({a, b} means one of a or b)
	 * var Tooltip = require("./tooltip.jsx");
	 * <Tooltip
	 *         className="class-for-tooltip-contents"
	 *         horizontalPosition="left" // one of "left", "right"
	 *         horizontalAlign="left" // one of "left", "right"
	 *         verticalPosition="bottom" // one of "top", "bottom"
	 *         arrowSize={10} // arrow size in pixels
	 *         borderColor="#ccc" // color of the border for the tooltip
	 *         show={true} // whether the tooltip should currently be visible
	 *         >
	 *     <TargetElementOfTheTooltip />
	 *     <TooltipContents1 />
	 *     <TooltipContents2 />
	 * </Tooltip>
	 *
	 * To show/hide the tooltip, the parent component should call the
	 * .show() and .hide() methods of the tooltip when appropriate.
	 * (These are usually set up as handlers of events on the target element.)
	 *
	 * Notes:
	 *     className should not specify a border; that is handled by borderColor
	 *     so that the arrow and tooltip match
	 */

	//          __,,--``\\
	//  _,,-''``         \\     ,
	// '----------_.------'-.___|\__
	//    _.--''``    `)__   )__   @\__
	//   (  .. ''---/___,,E/__,E'------`
	//    `-''`''
	// Here be dragons.

	var zIndex = 10;

	var Triangle = React.createClass({displayName: 'Triangle',
	    propTypes: {
	        color: React.PropTypes.string.isRequired,
	        left: React.PropTypes.number.isRequired,
	        "top": React.PropTypes.number.isRequired,
	        width: React.PropTypes.number.isRequired,
	        height: React.PropTypes.number.isRequired,
	        horizontalDirection: React.PropTypes.oneOf(
	            ["left", "right"]
	        ).isRequired,
	        verticalDirection: React.PropTypes.oneOf(
	            ["top", "bottom"]
	        ).isRequired,
	    },

	    render: function() {
	        var borderLeft, borderRight, borderTop, borderBottom;

	        var hBorder = (this.props.width + "px solid transparent");
	        if (this.props.horizontalDirection === "right") {
	            borderLeft = hBorder;
	        } else {
	            borderRight = hBorder;
	        }

	        var vBorder = (this.props.height + "px solid " + this.props.color);
	        if (this.props.verticalDirection === "top") {
	            borderTop = vBorder;
	        } else {
	            borderBottom = vBorder;
	        }

	        return React.createElement("div", {style: {
	            display: "block",
	            height: 0,
	            width: 0,
	            position: "absolute",
	            left: this.props.left,
	            "top": this.props["top"],
	            borderLeft: borderLeft,
	            borderRight: borderRight,
	            borderTop: borderTop,
	            borderBottom: borderBottom
	        }});
	    }
	});

	var TooltipArrow = React.createClass({displayName: 'TooltipArrow',
	    propTypes: {
	        position: React.PropTypes.string,
	        visibility: React.PropTypes.string,
	        left: React.PropTypes.number,
	        "top": React.PropTypes.number,
	        color: React.PropTypes.string.isRequired,  // a css color
	        border: React.PropTypes.string.isRequired,  // a css color
	        width: React.PropTypes.number.isRequired,
	        height: React.PropTypes.number.isRequired,
	        horizontalDirection: React.PropTypes.oneOf(
	            ["left", "right"]
	        ).isRequired,
	        verticalDirection: React.PropTypes.oneOf(
	            ["top", "bottom"]
	        ).isRequired
	    },

	    getDefaultProps: function() {
	        return {
	            position: "relative",
	            visibility: "visible",
	            left: 0,
	            "top": 0
	        };
	    },

	    // TODO(jack): Think about adding a box-shadow to the triangle here
	    // See http://css-tricks.com/triangle-with-shadow/
	    render: function() {
	        var isRight = (this.props.horizontalDirection === "right");
	        var isTop = (this.props.verticalDirection === "top");

	        var frontTopOffset = isTop ? 0 : 1;
	        var borderTopOffset = isTop ? 0 : -1;

	        return React.createElement("div", {style: {
	                display: "block",
	                position: this.props.position,
	                visibility: this.props.visibility,
	                left: this.props.left,
	                "top": this.props["top"],
	                width: this.props.width + 2,
	                height: this.props.height + 1,
	                marginTop: -1,
	                marginBottom: -2,
	                zIndex: zIndex
	            }}, 
	            /* The background triangle used to create the effect of a
	                border around the foreground triangle*/
	            React.createElement(Triangle, {
	                horizontalDirection: this.props.horizontalDirection, 
	                verticalDirection: this.props.verticalDirection, 
	                color: this.props.border, 
	                left: 0, 
	                top: borderTopOffset, 
	                width: this.props.width + 2, // one extra for the diagonal
	                height: this.props.height + 2}), 
	            /* The foreground triangle covers all but the left/right edges
	                of the background triangle */
	            React.createElement(Triangle, {
	                horizontalDirection: this.props.horizontalDirection, 
	                verticalDirection: this.props.verticalDirection, 
	                color: this.props.color, 
	                left: 1, 
	                top: frontTopOffset, 
	                width: this.props.width, 
	                height: this.props.height})
	        );
	    }
	});

	var VERTICAL_CORNERS = {
	    "top": {
	        "top": "-100%"
	    },
	    bottom: {
	        "top": 0
	    }
	};

	var HORIZONTAL_CORNERS = {
	    left: {
	        targetLeft: 0,
	    },

	    right: {
	        targetLeft: "100%",
	    }
	};

	var HORIZONTAL_ALIGNMNENTS = {
	    left: {
	        tooltipLeft: 0,
	        arrowLeft: function(arrowSize)  {return 0;}
	    },
	    right: {
	        tooltipLeft: "-100%",
	        arrowLeft: function(arrowSize)  {return -arrowSize - 2;}
	    }
	};


	var Tooltip = React.createClass({displayName: 'Tooltip',
	    propTypes: {
	        show: React.PropTypes.bool.isRequired,
	        className: React.PropTypes.string,
	        arrowSize: React.PropTypes.number,
	        borderColor: React.PropTypes.string,
	        verticalPosition: React.PropTypes.oneOf(
	            _.keys(VERTICAL_CORNERS)
	        ),
	        horizontalPosition: React.PropTypes.oneOf(
	            _.keys(HORIZONTAL_CORNERS)
	        ),
	        horizontalAlign: React.PropTypes.oneOf(
	            _.keys(HORIZONTAL_ALIGNMNENTS)
	        ),
	        children: React.PropTypes.arrayOf(
	            React.PropTypes.element
	        ).isRequired
	    },

	    getDefaultProps: function() {
	        return {
	            className: "",
	            arrowSize: 10,
	            borderColor: "#ccc",
	            verticalPosition: "bottom",
	            horizontalPosition: "left",
	            horizontalAlign: "left"
	        };
	    },

	    getInitialState: function() {
	        return {
	            height: null  // used for offsetting "top" positioned tooltips
	        };
	    },

	    componentWillReceiveProps: function() {
	        // If the contents have changed, reset our measure of the height
	        this.setState({height: null});
	    },

	    render: function() {
	        var isTooltipAbove = this.props.verticalPosition === "top";

	        /* We wrap the entire output in a span so that it displays inline */
	        return React.createElement("span", null, 
	            isTooltipAbove && this._renderToolTipDiv(isTooltipAbove), 

	            /* We wrap our input in a div so that we can put the tooltip in a
	                div above/below it */
	            React.createElement("div", null, 
	                _.first(this.props.children)
	            ), 

	            !isTooltipAbove && this._renderToolTipDiv()
	        );
	    },

	    _renderToolTipDiv: function(isTooltipAbove) {
	        var settings = _.extend({},
	            HORIZONTAL_CORNERS[this.props.horizontalPosition],
	            HORIZONTAL_ALIGNMNENTS[this.props.horizontalAlign],
	            VERTICAL_CORNERS[this.props.verticalPosition]
	        );

	        var arrowAbove;
	        var arrowBelow;

	        if (isTooltipAbove) {
	            // We put an absolutely positioned arrow in the correct place
	            arrowAbove = React.createElement(TooltipArrow, {
	                verticalDirection: "top", 
	                horizontalDirection: this.props.horizontalAlign, 
	                position: "absolute", 
	                color: "white", 
	                border: this.props.borderColor, 
	                left: settings.arrowLeft(this.props.arrowSize), 
	                top: -this.props.arrowSize + 2, 
	                width: this.props.arrowSize, 
	                height: this.props.arrowSize, 
	                zIndex: zIndex});

	            // And we use a visibility: hidden arrow below to shift up the
	            // content by the correct amount
	            arrowBelow = React.createElement(TooltipArrow, {
	                verticalDirection: "top", 
	                horizontalDirection: this.props.horizontalAlign, 
	                visibility: "hidden", 
	                color: "white", 
	                border: this.props.borderColor, 
	                left: settings.arrowLeft(this.props.arrowSize), 
	                top: -1, 
	                width: this.props.arrowSize, 
	                height: this.props.arrowSize, 
	                zIndex: zIndex});
	        } else {
	            arrowAbove = React.createElement(TooltipArrow, {
	                verticalDirection: "bottom", 
	                horizontalDirection: this.props.horizontalAlign, 
	                color: "white", 
	                border: this.props.borderColor, 
	                left: settings.arrowLeft(this.props.arrowSize), 
	                top: -1, 
	                width: this.props.arrowSize, 
	                height: this.props.arrowSize, 
	                zIndex: zIndex});

	            arrowBelow = null;
	        }

	        /* A positioned div below the input to be the parent for our
	            tooltip */
	        return React.createElement("div", {style: {
	                position: "relative",
	                height: 0,
	                display: this.props.show ? "block" : "none",
	                }}, 
	            React.createElement("div", {ref: "tooltipContainer", className: "tooltipContainer", style: {
	                        position: "absolute",
	                        // height must start out undefined, not null, so that
	                        // we can measure the actual height with jquery.
	                        // This is used to position the tooltip with top: -100%
	                        // when in verticalPosition: "top" mode
	                        height: this.state.height || undefined,
	                        left: settings.targetLeft
	                    }}, 
	                arrowAbove, 

	                /* The contents of the tooltip */
	                React.createElement("div", {className: this.props.className, 
	                        ref: "tooltipContent", 
	                        style: {
	                            position: "relative",
	                            top: settings["top"],
	                            left: settings.tooltipLeft,
	                            border: "1px solid " + this.props.borderColor,
	                            WebkitBoxShadow: "0 1px 3px " +
	                                    this.props.borderColor,
	                            MozBoxShadow: "0 1px 3px " +
	                                    this.props.borderColor,
	                            boxShadow: "0 1px 3px " +
	                                    this.props.borderColor,
	                            zIndex: zIndex - 1
	                        }}, 
	                    _.rest(this.props.children)
	                ), 

	                arrowBelow
	            )
	        );
	    },

	    componentDidMount: function() {
	        this._updateHeight();
	    },

	    componentDidUpdate: function() {
	        this._updateHeight();
	    },

	    _updateHeight: function() {
	        var height = this.refs.tooltipContainer.getDOMNode().offsetHeight;
	        if (height !== this.state.height) {
	            this.setState({height:height});
	        }
	    }
	});

	// Sorry.  // Apology-Oriented-Programming
	module.exports = Tooltip;


/***/ },
/* 75 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);
	var Tooltip = __webpack_require__(74);
	var _ = __webpack_require__(60);

	var ApiClassNames = __webpack_require__(12).ClassNames;
	var MathInput  = __webpack_require__(76);
	var Renderer   = __webpack_require__(10);
	var TextInput  = __webpack_require__(86);
	var MathOutput = __webpack_require__(88);

	var captureScratchpadTouchStart =
	        __webpack_require__(3).captureScratchpadTouchStart;

	var MATH = "math";
	var TEXT = "text";
	var TEX = "tex";

	var InputWithExamples = React.createClass({displayName: 'InputWithExamples',
	    propTypes: {
	        type: React.PropTypes.oneOf([MATH, TEXT, TEX]),
	        value: React.PropTypes.string,
	        onChange: React.PropTypes.func.isRequired,
	        className: React.PropTypes.string,
	        examples: React.PropTypes.arrayOf(React.PropTypes.string).isRequired,
	        shouldShowExamples: React.PropTypes.bool,
	        convertDotToTimes: React.PropTypes.bool,
	        buttonSet: React.PropTypes.string,
	        buttonsVisible: React.PropTypes.oneOf(['always', 'never', 'focused']),
	        onFocus: React.PropTypes.func,
	        onBlur: React.PropTypes.func,
	    },

	    getDefaultProps: function() {
	        return {
	            type: TEXT,
	            shouldShowExamples: true,
	            onFocus: function() { },
	            onBlur: function() { }
	        };
	    },

	    getInitialState: function() {
	        return {
	            focused: false,
	            showExamples: false
	        };
	    },

	    _getInputClassName: function() {
	        // <MathOutput> is a special component that manages its own class and
	        // state, as it's a <span> that wants to act like an <input>.
	        if (this.props.type === TEX) {
	            return this.props.className;
	        }

	        // Otherwise, we need to add these INPUT and FOCUSED tags here.
	        var className = ApiClassNames.INPUT + " " + ApiClassNames.INTERACTIVE;
	        if (this.state.focused) {
	            className += " " + ApiClassNames.FOCUSED;
	        }
	        if (this.props.className) {
	            className += " " + this.props.className;
	        }
	        return className;
	    },

	    _getPropsForInputType: function() {
	        // Minimal set of props, used by each input type
	        var inputProps = {
	            ref: "input",
	            className: this._getInputClassName(),
	            value: this.props.value,
	            onFocus: this._handleFocus,
	            onBlur: this._handleBlur
	        };

	        if (this.props.type === TEX) {
	            return inputProps;
	        }

	        // Add useful props required for MATH and TEXT modes
	        _.extend(inputProps, {
	            buttonSet: this.props.buttonSet,
	            buttonsVisible: this.props.buttonsVisible,
	            onChange: this.props.onChange,
	            onTouchStart: captureScratchpadTouchStart
	        });

	        // And add final props that are MATH- and TEXT-specific
	        if (this.props.type === MATH) {
	            return _.extend({
	                convertDotToTimes: this.props.convertDotToTimes,
	            }, inputProps);
	        } else if (this.props.type === TEXT) {
	            return _.extend({
	                autoCapitalize: "off",
	                autoComplete: "off",
	                autoCorrect: "off",
	                spellCheck: "false",
	            }, inputProps);
	        }
	    },

	    _getComponentForInputType: function() {
	        switch (this.props.type) {
	            case TEX:
	                return MathOutput;

	            case MATH:
	                return MathInput;

	            case TEXT:
	                return TextInput;

	            default:
	                return null;
	        }
	    },

	    _renderInput: function() {
	        var inputProps = this._getPropsForInputType();
	        var InputComponent = this._getComponentForInputType();
	        return React.createElement(InputComponent, React.__spread({},  inputProps));
	    },

	    render: function() {
	        var input = this._renderInput();

	        // Static rendering, which doesn't include the 'tooltip' logic that the
	        // other types require, and is hence handled separately.
	        if (this.props.type === TEX) {
	            return input;
	        }

	        // Else, we need to be able to show examples
	        var examplesContent = _.map(this.props.examples, function(example)  {
	            return "- " + example;
	        }).join("\n");

	        var showExamples = this.props.shouldShowExamples &&
	                this.state.showExamples;

	        return React.createElement(Tooltip, {
	                ref: "tooltip", 
	                className: "perseus-formats-tooltip", 
	                horizontalPosition: "left", 
	                horizontalAlign: "left", 
	                verticalPosition: "bottom", 
	                arrowSize: 10, 
	                borderColor: "#ccc", 
	                show: showExamples}, 
	            input, 
	            React.createElement(Renderer, {content: examplesContent})
	        );
	    },

	    _handleFocus: function() {
	        this.props.onFocus();
	        this.setState({
	            focused: true,
	            showExamples: true
	        });
	    },

	    show: function() {
	        this.setState({showExamples: true});
	    },

	    hide: function() {
	        this.setState({showExamples: false});
	    },

	    _handleBlur: function() {
	        this.props.onBlur();
	        this.setState({
	            focused: false,
	            showExamples: false
	        });
	    },

	    focus: function() {
	        this.refs.input.focus();
	    },

	    blur: function() {
	        this.refs.input.blur();
	    },

	    handleChange: function(e) {
	        this.props.onChange(e.target.value);
	    }
	});

	module.exports = InputWithExamples;


/***/ },
/* 76 */
/***/ function(module, exports, __webpack_require__) {

	// TODO(alex): Package MathQuill
	var MathQuill = window.MathQuill;
	var React     = __webpack_require__(61);
	var _         = __webpack_require__(60);
	var cx        = React.addons.classSet;
	var PT = React.PropTypes;
	var TexButtons = __webpack_require__(77);

	// A WYSIWYG math input that calls `onChange(LaTeX-string)`
	var MathInput = React.createClass({displayName: 'MathInput',
	    propTypes: {
	        value: PT.string,
	        onChange: PT.func.isRequired,
	        convertDotToTimes: PT.bool,
	        buttonsVisible: PT.oneOf(['always', 'never', 'focused']),
	        buttonSets: TexButtons.buttonSetsType.isRequired,
	        onFocus: PT.func,
	        onBlur: PT.func
	    },

	    render: function() {
	        var className = cx({
	            "perseus-math-input": true,

	            // mathquill usually adds these itself but react removes them when
	            // updating the component.
	            "mq-editable-field": true,
	            "mq-math-mode": true
	        });

	        if (this.props.className) {
	            className = className + " " + this.props.className;
	        }

	        var buttons = null;
	        if (this._shouldShowButtons()) {
	            buttons = React.createElement(TexButtons, {
	                sets: this.props.buttonSets, 
	                className: "math-input-buttons absolute", 
	                convertDotToTimes: this.props.convertDotToTimes, 
	                onInsert: this.insert});
	        }

	        return React.createElement("div", {style: {display: "inline-block"}}, 
	            React.createElement("div", {style: {display: 'inline-block'}}, 
	                React.createElement("span", {className: className, 
	                      ref: "mathinput", 
	                      onFocus: this.handleFocus, 
	                      onBlur: this.handleBlur})
	            ), 
	            React.createElement("div", {style: {position: "relative"}}, 
	                buttons
	            )
	        );
	    },

	    // handlers:
	    // keep track of two related bits of state:
	    // * this.state.focused - whether the buttons are currently shown
	    // * this.mouseDown - whether a mouse click is active that started in the
	    //   buttons div

	    handleFocus: function() {
	        this.setState({ focused: true });
	        // TODO(joel) fix properly - we should probably allow onFocus handlers
	        // to this property, but we need to work correctly with them.
	        // if (this.props.onFocus) {
	        //     this.props.onFocus();
	        // }
	    },

	    handleMouseDown: function(event) {
	        var focused = this.getDOMNode().contains(event.target);
	        this.mouseDown = focused;
	        if (!focused) {
	            this.setState({ focused: false });
	        }
	    },

	    handleMouseUp: function() {
	        // this mouse click started in the buttons div so we should focus the
	        // input
	        if (this.mouseDown) {
	            this.focus();
	        }
	        this.mouseDown = false;
	    },

	    handleBlur: function() {
	        if (!this.mouseDown) {
	            this.setState({ focused: false });
	        }
	    },

	    _shouldShowButtons: function() {
	        if (this.props.buttonsVisible === 'always') {
	            return true;
	        } else if (this.props.buttonsVisible === 'never') {
	            return false;
	        } else {
	            return this.state.focused;
	        }
	    },

	    getDefaultProps: function() {
	        return {
	            value: "",
	            convertDotToTimes: false,
	            buttonsVisible: 'focused'
	        };
	    },

	    getInitialState: function() {
	        return { focused: false };
	    },

	    insert: function(value) {
	        var input = this.mathField();
	        if (_(value).isFunction()) {
	            value(input);
	        } else if (value[0] === '\\') {
	            input.cmd(value).focus();
	        } else {
	            input.write(value).focus();
	        }
	        input.focus();
	    },

	    mathField: function(options) {
	        // MathQuill.MathField takes a DOM node, MathQuill-ifies it if it's
	        // seeing that node for the first time, then returns the associated
	        // MathQuill object for that node. It is stable - will always return
	        // the same object when called on the same DOM node.
	        return MathQuill.MathField(this.refs.mathinput.getDOMNode(), options);
	    },

	    componentWillUnmount: function() {
	        window.removeEventListener("mousedown", this.handleMouseDown);
	        window.removeEventListener("mouseup", this.handleMouseUp);
	    },

	    componentDidMount: function() {
	        window.addEventListener("mousedown", this.handleMouseDown);
	        window.addEventListener("mouseup", this.handleMouseUp);

	        // These options can currently only be set globally. (Hopefully this
	        // will change at some point.) They appear safe to set multiple times.

	        // LaTeX commands that, when typed, are immediately replaced by the
	        // appropriate symbol. This does not include ln, log, or any of the
	        // trig functions; those are always interpreted as commands.
	        MathQuill.addAutoCommands("pi theta phi sqrt");

	        // Pop the cursor out of super/subscripts on arithmetic operators or
	        // (in)equalities.
	        MathQuill.addCharsThatBreakOutOfSupSub("+-*/=<>≠≤≥");

	        // Prevent excessive super/subscripts or fractions from being created
	        // without operands, e.g. when somebody holds down a key
	        MathQuill.disableCharsWithoutOperand("^_/");

	        var initialized = false;

	        // Initialize MathQuill.MathField instance
	        this.mathField({
	            // The name of this option is somewhat misleading, as tabbing in
	            // MathQuill breaks you out of a nested context (fraction/script)
	            // if you're in one, but moves focus to the next input if you're
	            // not. Spaces (with this option enabled) are just ignored in the
	            // latter case.
	            //
	            // TODO(alex): In order to allow inputting mixed numbers, we will
	            // have to accept spaces in certain cases. The desired behavior is
	            // still to escape nested contexts if currently in one, but to
	            // insert a space if not (we don't expect mixed numbers in nested
	            // contexts). We should also limit to one consecutive space.
	            spaceBehavesLikeTab: true,

	            handlers: {
	                edited: function(mathField)  {
	                    // This handler is guaranteed to be called on change, but
	                    // unlike React it sometimes generates false positives.
	                    // One of these is on initialization (with an empty string
	                    // value), so we have to guard against that below.
	                    var value = mathField.latex();

	                    // Provide a MathQuill-compatible way to generate the
	                    // not-equals sign without pasting unicode or typing TeX
	                    value = value.replace(/<>/g, "\\ne");

	                    // Use the specified symbol to represent multiplication
	                    // TODO(alex): Add an option to disallow variables, in
	                    // which case 'x' should get converted to '\\times'
	                    if (this.props.convertDotToTimes) {
	                        value = value.replace(/\\cdot/g, "\\times");
	                    } else {
	                        value = value.replace(/\\times/g, "\\cdot");
	                    }

	                    if (initialized && this.props.value !== value) {
	                        this.props.onChange(value);
	                    }
	                }.bind(this),
	                enter: function()  {
	                    // This handler is called when the user presses the enter
	                    // key. Since this isn't an actual <input> element, we have
	                    // to manually trigger the usually automatic form submit.
	                    $(this.refs.mathinput.getDOMNode()).submit();
	                }.bind(this),
	                upOutOf: function(mathField)  {
	                    // This handler is called when the user presses the up
	                    // arrow key, but there is nowhere in the expression to go
	                    // up to (no numerator or exponent). For ease of use,
	                    // interpret this as an attempt to create an exponent.
	                    mathField.typedText("^");
	                }
	            }
	        });

	        // Ideally, we would be able to pass an initial value directly into
	        // the constructor above
	        this.mathField().latex(this.props.value);

	        initialized = true;
	    },

	    componentDidUpdate: function() {
	        if (!_.isEqual(this.mathField().latex(), this.props.value)) {
	            this.mathField().latex(this.props.value);
	        }
	    },

	    focus: function() {
	        this.mathField().focus();
	        this.setState({ focused: true });
	    },

	    blur: function() {
	        this.mathField().blur();
	        this.setState({ focused: false });
	    }
	});

	module.exports = MathInput;


/***/ },
/* 77 */
/***/ function(module, exports, __webpack_require__) {

	var React     = __webpack_require__(61);
	var _ = __webpack_require__(60);

	var TeX       = __webpack_require__(65);

	var prettyBig = { fontSize: "150%" };
	var slightlyBig = { fontSize: "120%" };
	var trigStyle = { marginLeft: -4 };
	var symbStyle = { fontSize: "130%" };

	// These are functions because we want to generate a new component for each use
	// on the page rather than reusing an instance (which will cause an error).
	// Also, it's useful for things which might look different depending on the
	// props.

	var basic = [
	    function()  {return [React.createElement("span", {style: slightlyBig}, "+"), "+"];},
	    function()  {return [React.createElement("span", {style: prettyBig}, "-"), "-"];},

	    // TODO(joel) - display as \cdot when appropriate
	    function(props)  {
	        if (props.convertDotToTimes) {
	            return [React.createElement(TeX, {style: prettyBig}, "\\times"), "\\times"];
	        } else {
	            return [React.createElement(TeX, {style: prettyBig}, "\\cdot"), "\\cdot"];
	        }
	    },
	    function()  {return [
	        React.createElement(TeX, {style: prettyBig}, "\\frac{□}{□}"),

	        // If there's something in the input that can become part of a
	        // fraction, typing "/" puts it in the numerator. If not, typing
	        // "/" does nothing. In that case, enter a \frac.
	        function(input)  {
	            var contents = input.latex();
	            input.typedText("/");
	            if (input.latex() === contents) {
	                input.cmd("\\frac");
	            }
	        }
	    ];}
	];

	var buttonSets = {
	    basic:basic,

	    "basic+div": basic.concat([
	        function()  {return [React.createElement(TeX, null, "\\div"), "\\div"];}
	    ]),

	    trig: [
	        function()  {return [React.createElement(TeX, null, "\\sin"), "\\sin"];},
	        function()  {return [React.createElement(TeX, null, "\\cos"), "\\cos"];},
	        function()  {return [React.createElement(TeX, null, "\\tan"), "\\tan"];},
	        function()  {return [React.createElement(TeX, {style: symbStyle}, "\\theta"), "\\theta"];},
	        function()  {return [React.createElement(TeX, {style: symbStyle}, "\\phi"), "\\phi"];}
	    ],

	    prealgebra: [
	        function()  {return [React.createElement(TeX, null, "\\sqrt{x}"), "\\sqrt"];},
	        // TODO(joel) - how does desmos do this?
	        // ["\\sqrt[3]{x}", "\\sqrt[3]{x}"],
	        function()  {return [
	            React.createElement(TeX, {style: slightlyBig}, "□^a"),
	            function(input)  {
	                var contents = input.latex();
	                input.keystroke("Up");
	                if (input.latex() === contents) {
	                    input.typedText("a^b");
	                }
	            }
	        ];},
	        function()  {return [React.createElement(TeX, {style: slightlyBig}, "\\pi"), "\\pi"];},
	        function()  {return [React.createElement(TeX, null, "\\div"), "\\div"];},
	    ],

	    logarithms: [
	        function()  {return [React.createElement(TeX, null, "\\log"), "\\log"];},
	        function()  {return [React.createElement(TeX, null, "\\ln"), "\\ln"];}
	    ],

	    "basic relations": [
	        function()  {return [React.createElement(TeX, null, "="), "\\eq"];},
	        function()  {return [React.createElement(TeX, null, "\\lt"), "\\lt"];},
	        function()  {return [React.createElement(TeX, null, "\\gt"), "\\gt"];},
	    ],

	    "advanced relations": [
	        function()  {return [React.createElement(TeX, null, "\\neq"), "\\neq"];},
	        function()  {return [React.createElement(TeX, null, "\\leq"), "\\leq"];},
	        function()  {return [React.createElement(TeX, null, "\\geq"), "\\geq"];},
	    ],
	};

	var buttonSetsType = React.PropTypes.arrayOf(
	        React.PropTypes.oneOf(_(buttonSets).keys())
	    );

	var TexButtons = React.createClass({displayName: 'TexButtons',
	    propTypes: {
	        sets: buttonSetsType.isRequired,
	        onInsert: React.PropTypes.func.isRequired
	    },

	    render: function() {
	        var buttons = _(this.props.sets).map(function(setName)  {return buttonSets[setName];});

	        var buttonRows = _(buttons).map(function(row)  {return row.map(function(symbGen)  {
	            // create a (component, thing we should send to mathquill) pair
	            var symbol = symbGen(this.props);
	            return React.createElement("button", {onClick: function()  {return this.props.onInsert(symbol[1]);}.bind(this), 
	                           className: "tex-button", 
	                           tabIndex: -1, 
	                           type: "button"}, 
	                symbol[0]
	            );
	        }.bind(this));}.bind(this));

	        var buttonPopup = _(buttonRows).map(function(row, i)  {
	            return React.createElement("div", {className: "clearfix tex-button-row", 
	                        key: this.props.sets[i]}, 
	                row
	            );
	        }.bind(this));

	        return React.createElement("div", {className: this.props.className}, 
	            buttonPopup
	        );
	    },

	    statics: {
	        buttonSets:buttonSets,
	        buttonSetsType:buttonSetsType
	    }
	});

	module.exports = TexButtons;


/***/ },
/* 78 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);
	var RCSS = __webpack_require__(114);
	var _ = __webpack_require__(60);
	var styles = __webpack_require__(107);

	var buttonStyle = styles.button.buttonStyle;
	var selectedStyle = styles.button.selectedStyle;

	RCSS.createClass(buttonStyle);
	RCSS.createClass(selectedStyle);

	/* ButtonGroup is an aesthetically pleasing group of buttons.
	 *
	 * The class requires these properties:
	 *   buttons - an array of objects with keys:
	 *     "value": this is the value returned when the button is selected
	 *     "content": this is the JSX shown within the button, typically a string
	 *         that gets rendered as the button's display text
	 *     "title": this is the title-text shown on hover
	 *   onChange - a function that is provided with the updated value
	 *     (which it then is responsible for updating)
	 *
	 * The class has these optional properties:
	 *   value - the initial value of the button selected, defaults to null.
	 *   allowEmpty - if false, exactly one button _must_ be selected; otherwise
	 *     it defaults to true and _at most_ one button (0 or 1) may be selected.
	 *
	 * Requires stylesheets/perseus-admin-package/editor.less to look nice.
	 */

	var ButtonGroup = React.createClass({displayName: 'ButtonGroup',
	    propTypes: {
	        value: React.PropTypes.any,
	        buttons: React.PropTypes.arrayOf(React.PropTypes.shape({
	            value: React.PropTypes.any.isRequired,
	            content: React.PropTypes.node,
	            title: React.PropTypes.string
	        })).isRequired,
	        onChange: React.PropTypes.func.isRequired,
	        allowEmpty: React.PropTypes.bool
	    },

	    getDefaultProps: function() {
	        return {
	            value: null,
	            allowEmpty: true
	        };
	    },

	    render: function() {
	        var value = this.props.value;
	        var buttons = _(this.props.buttons).map(function(button, i)  {
	                var maybeSelected = button.value === value ?
	                        selectedStyle.className :
	                        "";
	                return React.createElement("button", {title: button.title, 
	                        type: "button", 
	                        id: "" + i, 
	                        ref: "button" + i, 
	                        key: "" + i, 
	                        className: (buttonStyle.className + " " + maybeSelected), 
	                        onClick: this.toggleSelect.bind(this, button.value)}, 
	                    button.content || "" + button.value
	                );
	            }.bind(this));

	        var outerStyle = {
	            display: 'inline-block',
	        };
	        return React.createElement("div", {style: outerStyle}, 
	            buttons
	        );
	    },

	    focus: function() {
	        this.getDOMNode().focus();
	        return true;
	    },

	    toggleSelect: function(newValue) {
	        var value = this.props.value;

	        if (this.props.allowEmpty) {
	            // Select the new button or unselect if it's already selected
	            this.props.onChange(value !== newValue ? newValue : null);
	        } else {
	            this.props.onChange(newValue);
	        }
	    }
	});

	module.exports = ButtonGroup;


/***/ },
/* 79 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);
	var _ = __webpack_require__(60);

	var Changeable  = __webpack_require__(66);

	var ButtonGroup = __webpack_require__(78);
	var InfoTip = __webpack_require__(69);
	var NumberInput = __webpack_require__(85);
	var PropCheckBox = __webpack_require__(58);
	var RangeInput = __webpack_require__(83);
	var TeX = __webpack_require__(65);
	var Util = __webpack_require__(3);

	var defaultBoxSize = 340;
	var defaultBackgroundImage = {
	    url: null,
	    scale: 1,
	    bottom: 0,
	    left: 0,
	};

	function numSteps(range, step) {
	    return Math.floor((range[1] - range[0]) / step);
	}

	var GraphSettings = React.createClass({displayName: 'GraphSettings',
	    mixins: [Changeable],

	    propTypes: {
	        editableSettings: React.PropTypes.arrayOf(
	            React.PropTypes.oneOf(
	                ["canvas", "graph", "snap", "image", "measure"])),
	        box: React.PropTypes.arrayOf(React.PropTypes.number),
	        labels: React.PropTypes.arrayOf(React.PropTypes.string),
	        range: React.PropTypes.arrayOf(React.PropTypes.arrayOf(
	            React.PropTypes.number)),
	        step: React.PropTypes.arrayOf(React.PropTypes.number),
	        gridStep: React.PropTypes.arrayOf(React.PropTypes.number),
	        snapStep: React.PropTypes.arrayOf(React.PropTypes.number),
	        valid: React.PropTypes.bool,
	        backgroundImage: React.PropTypes.object,
	        markings: React.PropTypes.oneOf(["graph", "grid", "none"]),
	        showProtractor: React.PropTypes.bool,
	        showRuler: React.PropTypes.bool,
	        rulerLabel: React.PropTypes.string,
	        rulerTicks: React.PropTypes.number
	    },

	    getInitialState: function() {
	        return {
	            labelsTextbox: this.props.labels,
	            gridStepTextbox: this.props.gridStep,
	            snapStepTextbox: this.props.snapStep,
	            stepTextbox: this.props.step,
	            rangeTextbox: this.props.range
	        };
	    },

	    getDefaultProps: function() {
	        return {
	            editableSettings: ["graph", "snap", "image", "measure"],
	            box: [defaultBoxSize, defaultBoxSize],
	            labels: ["x", "y"],
	            range: [[-10, 10], [-10, 10]],
	            step: [1, 1],
	            gridStep: [1, 1],
	            snapStep: [1, 1],
	            valid: true,
	            backgroundImage: defaultBackgroundImage,
	            markings: "graph",
	            showProtractor: false,
	            showRuler: false,
	            rulerLabel: "",
	            rulerTicks: 10
	        };
	    },

	    render: function() {
	        var scale = [
	            KhanUtil.roundTo(2,
	                Util.scaleFromExtent(this.props.range[0], this.props.box[0])),
	            KhanUtil.roundTo(2,
	                Util.scaleFromExtent(this.props.range[1], this.props.box[1]))];

	        return React.createElement("div", null, 
	            _.contains(this.props.editableSettings, "canvas") &&
	            React.createElement("div", {className: "graph-settings"}, 
	                React.createElement("div", {className: "perseus-widget-row"}, 
	                    "Canvas size (x,y pixels)", 
	                    React.createElement(RangeInput, {
	                        value: this.props.box, 
	                        onChange: function(box)  { this.change({box: box}); }.bind(this)})
	                ), 
	                React.createElement("div", {className: "perseus-widget-row"}, 
	                    "Scale (px per div): ", React.createElement(TeX, null, "(" + scale[0] + ", " +
	                        scale[1] + ")")
	                )
	            ), 

	            _.contains(this.props.editableSettings, "graph") &&
	            React.createElement("div", {className: "graph-settings"}, 
	                React.createElement("div", {className: "perseus-widget-row"}, 
	                    React.createElement("div", {className: "perseus-widget-left-col"}, " x Label", 
	                        React.createElement("input", {type: "text", 
	                                className: "graph-settings-axis-label", 
	                                ref: "labels-0", 
	                                onChange: function(e)  {return this.changeLabel(0, e);}.bind(this), 
	                                value: this.state.labelsTextbox[0]})
	                    ), 
	                    React.createElement("div", {className: "perseus-widget-right-col"}, "y Label", 
	                        React.createElement("input", {type: "text", 
	                                className: "graph-settings-axis-label", 
	                                ref: "labels-1", 
	                                onChange: function(e)  {return this.changeLabel(1, e);}.bind(this), 
	                                value: this.state.labelsTextbox[1]})
	                    )
	                ), 

	                React.createElement("div", {className: "perseus-widget-row"}, 
	                    React.createElement("div", {className: "perseus-widget-left-col"}, 
	                        "x Range", 
	                        React.createElement(RangeInput, {
	                            value: this.state.rangeTextbox[0], 
	                            onChange: function(vals)  {return this.changeRange(0, vals);}.bind(this)})
	                    ), 
	                    React.createElement("div", {className: "perseus-widget-right-col"}, 
	                        "y Range", 
	                        React.createElement(RangeInput, {
	                            value: this.state.rangeTextbox[1], 
	                            onChange: function(vals)  {return this.changeRange(1, vals);}.bind(this)})
	                    )
	                ), 
	                React.createElement("div", {className: "perseus-widget-row"}, 
	                    React.createElement("div", {className: "perseus-widget-left-col"}, 
	                        "Tick Step", 
	                        React.createElement(RangeInput, {value: this.state.stepTextbox, 
	                                    onChange: this.changeStep})
	                    ), 
	                    React.createElement("div", {className: "perseus-widget-right-col"}, 
	                        "Grid Step", 
	                        React.createElement(RangeInput, {value: this.state.gridStepTextbox, 
	                                    onChange: this.changeGridStep})
	                    )
	                ), 
	                _.contains(this.props.editableSettings, "snap") &&
	                React.createElement("div", {className: "perseus-widget-row"}, 
	                    React.createElement("div", {className: "perseus-widget-left-col"}, 
	                        "Snap Step", 
	                        React.createElement(RangeInput, {value: this.state.snapStepTextbox, 
	                                    onChange: this.changeSnapStep})
	                    )
	                ), 
	                React.createElement("div", {className: "perseus-widget-row"}, 
	                    React.createElement("label", null, "Markings:", ' ', " "), 
	                    React.createElement(ButtonGroup, {value: this.props.markings, 
	                        allowEmpty: false, 
	                        buttons: [
	                            {value: "graph", content: "Graph"},
	                            {value: "grid", content: "Grid"},
	                            {value: "none", content: "None"}], 
	                        onChange: this.change("markings")})
	                )
	            ), 

	            _.contains(this.props.editableSettings, "image") &&
	            React.createElement("div", {className: "image-settings"}, 
	                React.createElement("div", null, "Background image:"), 
	                React.createElement("div", null, "Url:", ' ', 
	                    React.createElement("input", {type: "text", 
	                            className: "graph-settings-background-url", 
	                            ref: "bg-url", 
	                            defaultValue: this.props.backgroundImage.url, 
	                            onKeyPress: this.changeBackgroundUrl, 
	                            onBlur: this.changeBackgroundUrl}), 
	                    React.createElement(InfoTip, null, 
	                        React.createElement("p", null, "Create an image in graphie, or use the \"Add image\"" + ' ' +
	                        "function to create a background.")
	                    )
	                ), 
	                this.props.backgroundImage.url && React.createElement("div", null, 
	                    React.createElement("div", null, "Pixels from left:", ' ', 
	                        React.createElement(NumberInput, {type: "text", 
	                                     ref: "bg-left", 
	                                     value: this.props.backgroundImage.left, 
	                                     onChange: 
	                        _.partial(this.changeBackgroundSetting, "left")})
	                    ), 
	                    React.createElement("div", null, "Pixels from bottom:", ' ', 
	                        React.createElement(NumberInput, {type: "text", 
	                                     ref: "bg-bottom", 
	                                     value: this.props.backgroundImage.bottom, 
	                                     onChange: 
	                        _.partial(this.changeBackgroundSetting, "bottom")})
	                    ), 
	                    React.createElement("div", null, "Image scale:", ' ', 
	                        React.createElement(NumberInput, {type: "text", 
	                                     ref: "bg-scale", 
	                                     value: this.props.backgroundImage.scale, 
	                                     onChange: 
	                        _.partial(this.changeBackgroundSetting, "scale")})
	                    )
	                )
	            ), 

	            _.contains(this.props.editableSettings, "measure") &&
	            React.createElement("div", {className: "misc-settings"}, 
	                React.createElement("div", {className: "perseus-widget-row"}, 
	                    React.createElement("div", {className: "perseus-widget-left-col"}, 
	                        React.createElement(PropCheckBox, {label: "Show ruler", 
	                            showRuler: this.props.showRuler, 
	                            onChange: this.change})
	                    ), 
	                    React.createElement("div", {className: "perseus-widget-right-col"}, 
	                        React.createElement(PropCheckBox, {label: "Show protractor", 
	                            showProtractor: this.props.showProtractor, 
	                            onChange: this.change})
	                    )
	                ), 
	                this.props.showRuler && React.createElement("div", null, 
	                    React.createElement("div", null, 
	                        React.createElement("label", null, 
	                            ' ', "Ruler label:", ' ', 
	                            React.createElement("select", {
	                                onChange: this.changeRulerLabel, 
	                                value: this.props.rulerLabel}, 
	                                    React.createElement("option", {value: ""}, "None"), 
	                                    React.createElement("optgroup", {label: "Metric"}, 
	                                        this.renderLabelChoices([
	                                            ["milimeters", "mm"],
	                                            ["centimeters", "cm"],
	                                            ["meters", "m"],
	                                            ["kilometers", "km"]
	                                        ])
	                                    ), 
	                                    React.createElement("optgroup", {label: "Imperial"}, 
	                                        this.renderLabelChoices([
	                                            ["inches", "in"],
	                                            ["feet", "ft"],
	                                            ["yards", "yd"],
	                                            ["miles", "mi"]
	                                        ])
	                                    )
	                            )
	                        )
	                    ), 
	                    React.createElement("div", null, 
	                        React.createElement("label", null, 
	                            ' ', "Ruler ticks:", ' ', 
	                            React.createElement("select", {
	                                onChange: this.changeRulerTicks, 
	                                value: this.props.rulerTicks}, 
	                                    _.map([1, 2, 4, 8, 10, 16], function(n) {
	                                        return React.createElement("option", {value: n}, n);
	                                    })
	                            )
	                        )
	                    )
	                )
	            )
	        );
	    },

	    renderLabelChoices: function(choices) {
	        return _.map(choices, function(nameAndValue) {
	            return React.createElement("option", {value: nameAndValue[1]}, nameAndValue[0]);
	        });
	    },

	    componentDidMount: function() {
	        this.changeGraph = _.debounce(this.changeGraph, 300);
	    },


	    validRange: function(range) {
	        var numbers = _.every(range, function(num) {
	            return _.isFinite(num);
	        });
	        if (! numbers) {
	            return "Range must be a valid number";
	        }
	        if (range[0] >= range[1]) {
	            return "Range must have a higher number on the right";
	        }
	        return true;
	    },

	    validateStepValue: function(settings) {
	        var $__0=        settings,step=$__0.step,range=$__0.range,name=$__0.name,minTicks=$__0.minTicks,maxTicks=$__0.maxTicks;

	        if (! _.isFinite(step)) {
	            return name + " must be a valid number";
	        }
	        var nSteps = numSteps(range, step);
	        if (nSteps < minTicks) {
	            return name + " is too large, there must be at least " +
	               minTicks + " ticks.";
	        }
	        if (nSteps > maxTicks) {
	            return name + " is too small, there can be at most " +
	               maxTicks + " ticks.";
	        }
	        return true;
	    },

	    validSnapStep: function(step, range) {
	        return this.validateStepValue({
	            step: step,
	            range: range,
	            name: "Snap step",
	            minTicks: 5,
	            maxTicks: 60
	        });
	    },

	    validGridStep: function(step, range) {
	        return this.validateStepValue({
	            step: step,
	            range: range,
	            name: "Grid step",
	            minTicks: 3,
	            maxTicks: 60
	        });
	    },

	    validStep: function(step, range) {
	        return this.validateStepValue({
	            step: step,
	            range: range,
	            name: "Step",
	            minTicks: 3,
	            maxTicks: 20
	        });
	    },

	    validateGraphSettings: function(range, step, gridStep, snapStep) {
	        var self = this;
	        var msg;
	        var goodRange = _.every(range, function(range) {
	            msg = self.validRange(range);
	            return msg === true;
	        });
	        if (!goodRange) {
	            return msg;
	        }
	        var goodStep = _.every(step, function(step, i) {
	            msg = self.validStep(step, range[i]);
	            return msg === true;
	        });
	        if (!goodStep) {
	            return msg;
	        }
	        var goodGridStep = _.every(gridStep, function(gridStep, i) {
	            msg = self.validGridStep(gridStep, range[i]);
	            return msg === true;
	        });
	        if (!goodGridStep) {
	            return msg;
	        }
	        var goodSnapStep = _.every(snapStep, function(snapStep, i) {
	            msg = self.validSnapStep(snapStep, range[i]);
	            return msg === true;
	        });
	        if (!goodSnapStep) {
	            return msg;
	        }
	        return true;
	    },

	    changeLabel: function(i, e) {
	        var val = e.target.value;
	        var labels = this.state.labelsTextbox.slice();
	        labels[i] = val;
	        this.setState({ labelsTextbox: labels }, this.changeGraph);
	    },

	    changeRange: function(i, values) {
	        var ranges = this.state.rangeTextbox.slice();
	        ranges[i] = values;
	        var step = this.state.stepTextbox.slice();
	        var gridStep = this.state.gridStepTextbox.slice();
	        var snapStep = this.state.snapStepTextbox.slice();
	        var scale = Util.scaleFromExtent(ranges[i], this.props.box[i]);
	        if (this.validRange(ranges[i]) === true) {
	            step[i] = Util.tickStepFromExtent(
	                    ranges[i], this.props.box[i]);
	            gridStep[i] = Util.gridStepFromTickStep(step[i], scale);
	            snapStep[i] = gridStep[i] / 2;
	        }
	        this.setState({
	            stepTextbox: step,
	            gridStepTextbox: gridStep,
	            snapStepTextbox: snapStep,
	            rangeTextbox: ranges
	        }, this.changeGraph);
	    },

	    changeStep: function(step) {
	        this.setState({ stepTextbox: step }, this.changeGraph);
	    },

	    changeSnapStep: function(snapStep) {
	        this.setState({ snapStepTextbox: snapStep },
	                this.changeGraph);
	    },

	    changeGridStep: function(gridStep) {
	        this.setState({
	            gridStepTextbox: gridStep,
	            snapStepTextbox: _.map(gridStep, function(step) {
	                return step / 2;
	            })
	        }, this.changeGraph);
	    },

	    changeGraph: function() {
	        var labels = this.state.labelsTextbox;
	        var range = _.map(this.state.rangeTextbox, function(range) {
	            return _.map(range, Number);
	        });
	        var step = _.map(this.state.stepTextbox, Number);
	        var gridStep = this.state.gridStepTextbox;
	        var snapStep = this.state.snapStepTextbox;

	        // validationResult is either:
	        //   true -> the settings are valid
	        //   a string -> the settings are invalid, and the explanation
	        //               is contained in the string
	        // TODO(jack): Refactor this to not be confusing
	        var validationResult = this.validateGraphSettings(range, step,
	                gridStep, snapStep);

	        if (validationResult === true) {  // either true or a string
	            this.change({
	                valid: true,
	                labels: labels,
	                range: range,
	                step: step,
	                gridStep: gridStep,
	                snapStep: snapStep
	            });
	        } else {
	            this.change({
	                valid: validationResult  // a string message, not false
	            });
	        }
	    },

	    changeBackgroundUrl: function(e) {
	        // Only continue on blur or "enter"
	        if (e.type === "keypress" && e.keyCode !== 13) {
	            return;
	        }

	        var setUrl = function(url, width, height)  {
	            var image = _.clone(this.props.backgroundImage);
	            image.url = url;
	            image.width = width;
	            image.height = height;
	            this.change({
	                backgroundImage: image,
	                markings: url ? "none" : "graph"
	            });
	        }.bind(this);

	        var url = this.refs["bg-url"].getDOMNode().value;
	        if (url) {
	            Util.getImageSize(url, function(width, height)  {
	                setUrl(url, width, height);
	            });
	        } else {
	            setUrl(url, 0, 0);
	        }
	    },

	    changeBackgroundSetting: function(type, value) {
	        var image = _.clone(this.props.backgroundImage);
	        image[type] = value;
	        this.change({ backgroundImage: image });
	    },

	    // TODO(jack): Make either a wrapper for standard events to work
	    // with this.change, or make these use some TextInput/NumberInput box
	    changeRulerLabel: function(e) {
	        this.change({rulerLabel: e.target.value});
	    },

	    changeRulerTicks: function(e) {
	        this.change({rulerTicks: +e.target.value});
	    }
	});

	module.exports = GraphSettings;


/***/ },
/* 80 */
/***/ function(module, exports, __webpack_require__) {

	var Movable = __webpack_require__(108);
	var MovablePoint = __webpack_require__(109);
	var MovableLine = __webpack_require__(110);
	var MovablePolygon = __webpack_require__(111);

	var Interactive2 = {
	    MovablePoint: MovablePoint,
	    addMovablePoint: function(graphie, options) {
	        var movable = new Movable(graphie, {});
	        return new MovablePoint(graphie, movable, options);
	    },
	    MovableLine: MovableLine,
	    addMovableLine: function(graphie, options) {
	        var movable = new Movable(graphie, {});
	        return new MovableLine(graphie, movable, options);
	    },
	    MovablePolygon: MovablePolygon,
	    addMovablePolygon: function(graphie, options) {
	        var movable = new Movable(graphie, {});
	        return new MovablePolygon(graphie, movable, options);
	    }
	};

	module.exports = Interactive2;


/***/ },
/* 81 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);
	var RCSS = __webpack_require__(114);
	var _ = __webpack_require__(60);
	var styles = __webpack_require__(107);

	var buttonStyle = styles.button.buttonStyle;
	var selectedStyle = styles.button.selectedStyle;

	RCSS.createClass(buttonStyle);
	RCSS.createClass(selectedStyle);

	/* MultiButtonGroup is an aesthetically pleasing group of buttons,
	 * which allows multiple buttons to be selected at the same time.
	 *
	 * The class requires these properties:
	 *   buttons - an array of objects with keys:
	 *     "value": this is the value returned when the button is selected
	 *     "content": this is the JSX shown within the button, typically a string
	 *         that gets rendered as the button's display text
	 *     "title": this is the title-text shown on hover
	 *   onChange - a function that is provided with an array of the updated
	 *     values (which it then is responsible for updating)
	 *
	 * The class has these optional properties:
	 *   values - an array of the initial values of the buttons selected.
	 *
	 * Requires stylesheets/perseus-admin-package/editor.less to look nice.
	 */

	var MultiButtonGroup = React.createClass({displayName: 'MultiButtonGroup',
	    propTypes: {
	        values: React.PropTypes.arrayOf(React.PropTypes.any),
	        buttons: React.PropTypes.arrayOf(React.PropTypes.shape({
	            value: React.PropTypes.any.isRequired,
	            content: React.PropTypes.node,
	            title: React.PropTypes.string
	        })).isRequired,
	        onChange: React.PropTypes.func.isRequired,
	        allowEmpty: React.PropTypes.bool
	    },

	    getDefaultProps: function() {
	        return {
	            values: [],
	            allowEmpty: true
	        };
	    },

	    render: function() {
	        var values = this.props.values;
	        var buttons = _(this.props.buttons).map(function(button, i)  {
	            var maybeSelected = _.contains(values, button.value) ?
	                selectedStyle.className : "";
	            return React.createElement("button", {title: button.title, 
	                    type: "button", 
	                    id: "" + i, 
	                    key: "" + i, 
	                    ref: "button" + i, 
	                    className: (buttonStyle.className + " " + maybeSelected), 
	                    onClick: this.toggleSelect.bind(this, button.value)}, 
	                button.content || "" + button.value
	            );
	        }.bind(this));

	        var outerStyle = {
	            display: 'inline-block'
	        };
	        return React.createElement("div", {style: outerStyle}, 
	            buttons
	        );
	    },

	    focus: function() {
	        this.getDOMNode().focus();
	        return true;
	    },

	    toggleSelect: function(newValue) {
	        var values = this.props.values;
	        var allowEmpty = this.props.allowEmpty;

	        if (_.contains(values, newValue) &&
	                (values.length > 1 || allowEmpty)) {
	            // If the value is already selected, unselect it
	            this.props.onChange(_.without(values, newValue));
	        } else {
	            // Otherwise merge with other values and return
	            this.props.onChange(_.union(values, [newValue]));
	        }
	    }
	});

	module.exports = MultiButtonGroup;


/***/ },
/* 82 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);

	/* You know when you want to propagate input to a parent...
	 * but then that parent does something with the input...
	 * then changing the props of the input...
	 * on every keystroke...
	 * so if some input is invalid or incomplete...
	 * the input gets reset or otherwise effed...
	 *
	 * This is the solution.
	 *
	 * Enough melodrama. Its an input that only sends changes
	 * to its parent on blur.
	 */
	var BlurInput = React.createClass({displayName: 'BlurInput',
	    propTypes: {
	        value: React.PropTypes.string.isRequired,
	        onChange: React.PropTypes.func.isRequired
	    },
	    getInitialState: function() {
	        return { value: this.props.value };
	    },
	    render: function() {
	        return React.createElement("input", React.__spread({}, 
	            this.props, 
	            {type: "text", 
	            value: this.state.value, 
	            onChange: this.handleChange, 
	            onBlur: this.handleBlur}));
	    },
	    componentWillReceiveProps: function(nextProps) {
	        this.setState({ value: nextProps.value });
	    },
	    handleChange: function(e) {
	        this.setState({ value: e.target.value });
	    },
	    handleBlur: function(e) {
	        this.props.onChange(e.target.value);
	    }
	});

	module.exports = BlurInput;


/***/ },
/* 83 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);
	var NumberInput = __webpack_require__(85);

	var truth = function()  {return true;};

	/* A minor abstraction on top of NumberInput for ranges
	 *
	 */
	var RangeInput = React.createClass({displayName: 'RangeInput',
	    propTypes: {
	        value: React.PropTypes.array.isRequired,
	        onChange: React.PropTypes.func.isRequired,
	        placeholder: React.PropTypes.array,
	        checkValidity: React.PropTypes.func
	    },

	    getDefaultProps: function() {
	        return {
	            placeholder: [null, null]
	        };
	    },

	    render: function() {
	        var value = this.props.value;
	        var checkValidity = this.props.checkValidity || truth;

	        return React.createElement("div", {className: "range-input"}, 
	            React.createElement(NumberInput, React.__spread({}, 
	                this.props, 
	                {value: value[0], 
	                checkValidity: function(val)  {return checkValidity([val, value[1]]);}, 
	                onChange: this.onChange.bind(this, 0), 
	                placeholder: this.props.placeholder[0]})), 
	            React.createElement(NumberInput, React.__spread({}, 
	                this.props, 
	                {value: value[1], 
	                checkValidity: function(val)  {return checkValidity([value[0], val]);}, 
	                onChange: this.onChange.bind(this, 1), 
	                placeholder: this.props.placeholder[1]}))
	        );
	    },

	    onChange: function(i, newVal) {
	        var value = this.props.value;
	        if (i === 0) {
	            this.props.onChange([newVal, value[1]]);
	        } else {
	            this.props.onChange([value[0], newVal]);
	        }
	    }

	});

	module.exports = RangeInput;


/***/ },
/* 84 */
/***/ function(module, exports, __webpack_require__) {

	/*
	 * In this file, an `expression` is some portion of valid TeX enclosed in
	 * curly brackets.
	 */

	 /*
	  * Find the index at which an expression ends, i.e., has an unmatched
	  * closing curly bracket. This method assumes that we start with a non-open
	  * bracket character and end when we've seen more left than right brackets
	  * (rather than assuming that we start with a bracket character and wait for
	  * bracket equality).
	  */
	function findEndpoint(tex, currentIndex) {
	    var bracketDepth = 0;

	    for (var i = currentIndex, len = tex.length; i < len; i++) {
	        var c = tex[i];

	        if (c === '{') {
	            bracketDepth++;
	        } else if (c === '}') {
	            bracketDepth--;
	        }

	        if (bracketDepth < 0) {
	            return i;
	        }
	    }
	    // If we never see unbalanced curly brackets, default to the
	    // entire string
	    return tex.length;
	}


	/*
	 * Parses an individual set of curly brackets into TeX.
	 */
	function parseNextExpression(tex, currentIndex, handler) {
	    // Find the first '{' and grab subsequent TeX
	    // Ex) tex: '{3}{7}', and we want the '3'
	    var openBracketIndex = tex.indexOf('{', currentIndex);
	    var nextExpIndex = openBracketIndex + 1;

	    // Truncate to only contain remaining TeX
	    var endpoint = findEndpoint(tex, nextExpIndex);
	    var expressionTeX = tex.substring(nextExpIndex, endpoint);
	    var parsedExp = walkTex(expressionTeX, handler);

	    return {
	        endpoint: endpoint,
	        expression: parsedExp
	    };
	}


	function getNextFracIndex(tex, currentIndex) {
	    var dfrac = "\\dfrac";
	    var frac = "\\frac";

	    var nextFrac = tex.indexOf(frac, currentIndex);
	    var nextDFrac = tex.indexOf(dfrac, currentIndex);

	    if (nextFrac > -1 && nextDFrac > -1) {
	        return Math.min(nextFrac, nextDFrac);
	    } else if (nextFrac > -1) {
	        return nextFrac;
	    } else if (nextDFrac > -1) {
	        return nextDFrac;
	    } else {
	        return -1;
	    }
	}


	function walkTex(tex, handler) {
	    // Ex) tex: '2 \dfrac {3}{7}'
	    var parsedString = "";
	    var currentIndex = 0;
	    var nextFrac = getNextFracIndex(tex, currentIndex);

	    // For each \dfrac, find the two expressions (wrapped in {}) and recur
	    while (nextFrac > -1) {
	        // Gather first fragment, preceding \dfrac
	        // Ex) parsedString: '2 '
	        parsedString += tex.substring(currentIndex, nextFrac);

	        // Remove everything preceding \dfrac, which has been parsed
	        currentIndex = nextFrac;

	        // Parse first expression and move index past it
	        // Ex) firstParsedExpression.expression: '3'
	        var firstParsedExpression = parseNextExpression(
	            tex, currentIndex, handler
	        );
	        currentIndex = firstParsedExpression.endpoint + 1;

	        // Parse second expression
	        // Ex) secondParsedExpression.expression: '7'
	        var secondParsedExpression = parseNextExpression(
	            tex, currentIndex, handler
	        );
	        currentIndex = secondParsedExpression.endpoint + 1;

	        // Add expressions to running total of parsed expressions
	        if (parsedString.length) {
	            parsedString += " ";
	        }

	        // Apply a custom handler based on the parsed subexpressions
	        parsedString += handler(firstParsedExpression.expression,
	            secondParsedExpression.expression);

	        // Find next DFrac, relative to currentIndex
	        nextFrac = getNextFracIndex(tex, currentIndex);
	    }

	    // Add remaining TeX, which is \dfrac-free
	    parsedString += tex.slice(currentIndex);

	    return parsedString;
	}

	/*
	 * Modify a TeX expression, returning another TeX expression. The resulting
	 * expression will have its innermost fractions stubbed out with \fracs
	 * (as opposed to \dfracs). All other content will remain untouched.
	 */
	function modifyTex(tex) {
	    function isNestedFraction(tex) {
	        return tex.indexOf("\\frac") > -1 || tex.indexOf("\\dfrac") > -1;
	    }
	    var handler = function(exp1, exp2) {
	        var prefix;
	        if (isNestedFraction(exp1) || isNestedFraction(exp2)) {
	            prefix = "\\dfrac";
	        } else {
	            prefix = "\\frac";
	        }
	        return prefix + " {" + exp1 + "}{" + exp2 + "}";
	    };
	    return walkTex(tex, handler);
	}

	/*
	 * Parse a TeX expression into something interpretable by input-number.
	 * The process is exclusively concerned with parsing fractions, i.e., \dfracs.
	 * The basic algorithm splits on \dfracs and then recurs on the subsequent
	 * "expressions", i.e., the {} pairs that follow \dfrac. The recursion is to
	 * allow for nested \dfrac elements.
	 */
	function parseTex(tex) {
	    var handler = function(exp1, exp2) {
	        return exp1 + "/" + exp2;
	    };
	    return walkTex(tex, handler);
	}

	module.exports = {
	    parseTex: parseTex,
	    modifyTex: modifyTex
	};


/***/ },
/* 85 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);
	var _ = __webpack_require__(60);

	var firstNumericalParse = __webpack_require__(3).firstNumericalParse;
	var captureScratchpadTouchStart =
	        __webpack_require__(3).captureScratchpadTouchStart;
	var knumber = __webpack_require__(103).number;
	var toNumericString = KhanUtil.toNumericString;
	var getNumericFormat = KhanUtil.getNumericFormat;

	/* An input box that accepts only numeric strings
	 *
	 * Calls onChange(value, format) for valid numbers.
	 * Reverts to the current value onBlur or on [ENTER],
	 *   but maintains the format (i.e. 3/2, 1 1/2, 150%)
	 * Accepts empty input and sends it to onChange as null
	 *   if no numeric placeholder is set.
	 * If given a checkValidity function, will turn
	 *   the background/outline red when invalid
	 * If useArrowKeys is set to true, up/down arrows will
	 *   increment/decrement integers
	 * Optionally takes a size ("mini", "small", "normal")
	 */
	var NumberInput = React.createClass({displayName: 'NumberInput',
	    propTypes: {
	        value: React.PropTypes.number,
	        format: React.PropTypes.string,
	        placeholder: React.PropTypes.oneOfType([
	            React.PropTypes.string,
	            React.PropTypes.number
	        ]),
	        onChange: React.PropTypes.func.isRequired,
	        onFormatChange: React.PropTypes.func,
	        checkValidity: React.PropTypes.func,
	        size: React.PropTypes.string
	    },

	    getDefaultProps: function() {
	        return {
	            value: null,
	            placeholder: null,
	            format: null,
	            onFormatChange: function()  {return null;},
	            checkValidity: function()  {return true;},
	            useArrowKeys: false
	        };
	    },

	    getInitialState: function() {
	        return {
	            format: this.props.format
	        };
	    },

	    render: function() {
	        var cx = React.addons.classSet;

	        var classes = cx({
	            "number-input": true,
	            "number-input-label": this.props.label != null,
	            "invalid-input": !this._checkValidity(this.props.value),
	            "mini": this.props.size === "mini",
	            "small": this.props.size === "small",
	            "normal": this.props.size === "normal"
	        });
	        if (this.props.className != null) {
	            classes = [classes, this.props.className].join(" ");
	        }

	        var input = React.createElement("input", React.__spread({}, 
	            this.props, 
	            {className: classes, 
	            type: "text", 
	            ref: "input", 
	            onChange: this._handleChange, 
	            onFocus: this._handleFocus, 
	            onBlur: this._handleBlur, 
	            onKeyPress: this._handleBlur, 
	            onKeyDown: this._onKeyDown, 
	            onTouchStart: captureScratchpadTouchStart, 
	            defaultValue: toNumericString(this.props.value, this.state.format), 
	            value: undefined}));

	        if (this.props.label) {
	            // TODO(aria): Remove this prop and option. Put your labels
	            // outside your inputs
	            return React.createElement("label", null, this.props.label, input);
	        } else {
	            return input;
	        }
	    },

	    componentDidUpdate: function(prevProps) {
	        if (!knumber.equal(this.getValue(), this.props.value)) {
	            this._setValue(this.props.value, this.state.format);
	        }
	    },

	    /* Return the current "value" of this input
	     * If empty, it returns the placeholder (if it is a number) or null
	     */
	    getValue: function() {
	        return this.parseInputValue(this.refs.input.getDOMNode().value);
	    },

	    parseInputValue: function(value) {
	        if (value === "") {
	            placeholder = this.props.placeholder;
	            return _.isFinite(placeholder) ? +placeholder : null;
	        } else {
	            var result = firstNumericalParse(value);
	            return _.isFinite(result) ? result : this.props.value;
	        }
	    },

	    /* Set text input focus to this input */
	    focus: function() {
	        this.refs.input.getDOMNode().focus();
	        this._handleFocus();
	    },

	    blur: function() {
	        this.refs.input.getDOMNode().blur();
	        this._handleBlur();
	    },

	    _checkValidity: function(value) {
	        if(value == null) {
	            return true;
	        }

	        var val = firstNumericalParse(value);
	        var checkValidity = this.props.checkValidity;

	        return _.isFinite(val) && checkValidity(val);
	    },

	    _handleChange: function(e) {
	        var text = e.target.value;
	        var value = this.parseInputValue(text);
	        var format = getNumericFormat(text);

	        this.props.onChange(value);
	        if (format) {
	            this.props.onFormatChange(value, format);
	            this.setState({format: format});
	        }
	    },

	    _handleFocus: function() {
	        if (this.props.onFocus) {
	            this.props.onFocus();
	        }
	    },

	    _handleBlur: function(e) {
	        // Only continue on blur or "enter"
	        if (e && e.type === "keypress" && e.keyCode !== 13) {
	            return;
	        }

	        this._setValue(this.props.value, this.state.format);
	        if (this.props.onBlur) {
	            this.props.onBlur();
	        }
	    },

	    _onKeyDown: function(e) {
	        if (this.props.onKeyDown) {
	            this.props.onKeyDown(e);
	        }

	        if (!this.props.useArrowKeys ||
	            !_.contains(["ArrowUp", "ArrowDown"], e.key)) {
	            return;
	        }

	        var val = this.getValue();
	        if (val !== Math.floor(val)) {
	            return; // bail if not an integer
	        }

	        if (e.key === "ArrowUp") {
	            val = val + 1;
	        } else if (e.key === "ArrowDown") {
	            val = val - 1;
	        }

	        if (this._checkValidity(val)) {
	            this.props.onChange(val);
	        }
	    },

	    _setValue: function(val, format) {
	        $(this.refs.input.getDOMNode()).val(toNumericString(val, format));
	    }
	});

	module.exports = NumberInput;


/***/ },
/* 86 */
/***/ function(module, exports, __webpack_require__) {

	var TextInput = React.createClass({displayName: 'TextInput',
	    propTypes: {
	        value: React.PropTypes.string,
	        onChange: React.PropTypes.func.isRequired,
	        className: React.PropTypes.string,
	        onFocus: React.PropTypes.func,
	        onBlur: React.PropTypes.func
	    },

	    getDefaultProps: function() {
	        return {
	            value: ""
	        };
	    },

	    render: function() {
	        return React.createElement("input", React.__spread({}, 
	            this.props, 
	            {type: "text", 
	            onChange: function(e)  {return this.props.onChange(e.target.value);}.bind(this)})
	        );
	    },

	    focus: function() {
	        this.getDOMNode().focus();
	    },

	    blur: function() {
	        this.getDOMNode().blur();
	    }
	});

	module.exports = TextInput;


/***/ },
/* 87 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);
	var _ = __webpack_require__(60);

	var Util = __webpack_require__(3);

	var SvgImage = __webpack_require__(57);

	var defaultBoxSize = 400;
	var defaultBackgroundImage = {
	    url: null,
	    scale: 1,
	    bottom: 0,
	    left: 0,
	};

	/* Style objects */
	var defaultInstructionsStyle = {
	    fontStyle: 'italic',
	    fontWeight: 'bold',
	    fontSize: '32px',
	    width: '100%',
	    height: '100%',
	    textAlign: 'center',
	    backgroundColor: 'white',
	    position: 'absolute',
	    zIndex: 1,
	    transition: 'opacity .25s ease-in-out',
	    '-moz-transition': 'opacity .25s ease-in-out',
	    '-webkit-transition': 'opacity .25s ease-in-out'
	};

	var instructionsTextStyle = {
	    position: 'relative',
	    top: '25%'
	};

	function numSteps(range, step) {
	    return Math.floor((range[1] - range[0]) / step);
	}

	var Graph = React.createClass({displayName: 'Graph',
	    propTypes: {
	        box: React.PropTypes.array.isRequired,
	        labels: React.PropTypes.arrayOf(React.PropTypes.string),
	        range: React.PropTypes.arrayOf(
	            React.PropTypes.arrayOf(
	                React.PropTypes.number
	            )
	        ),
	        step: React.PropTypes.arrayOf(React.PropTypes.number),
	        gridStep: React.PropTypes.arrayOf(React.PropTypes.number),
	        snapStep: React.PropTypes.arrayOf(React.PropTypes.number),
	        markings: React.PropTypes.string,
	        backgroundImage: React.PropTypes.shape({
	            url: React.PropTypes.string,
	            scale: React.PropTypes.number,
	            bottom: React.PropTypes.number,
	            left: React.PropTypes.number
	        }),
	        showProtractor: React.PropTypes.bool,
	        showRuler: React.PropTypes.bool,
	        rulerLabel: React.PropTypes.string,
	        rulerTicks: React.PropTypes.number,
	        onNewGraphie: React.PropTypes.func,
	        instructions: React.PropTypes.string,
	        onClick: React.PropTypes.func
	    },

	    getDefaultProps: function() {
	        return {
	            box: [defaultBoxSize, defaultBoxSize],
	            labels: ["x", "y"],
	            range: [[-10, 10], [-10, 10]],
	            step: [1, 1],
	            gridStep: [1, 1],
	            snapStep: [0.5, 0.5],
	            markings: "graph",
	            backgroundImage: defaultBackgroundImage,
	            showProtractor: false,
	            showRuler: false,
	            rulerLabel: "",
	            rulerTicks: 10,
	            instructions: null,
	            onNewGraphie: null,
	            onClick: null,
	            onMouseDown: null,
	        };
	    },

	    render: function() {
	        var image;
	        var imageData = this.props.backgroundImage;
	        if (imageData.url) {
	            var preScale = this.props.box[0] / defaultBoxSize;
	            var scale = imageData.scale * preScale;
	            image = React.createElement(SvgImage, {src: imageData.url, 
	                              left: preScale * imageData.left, 
	                              bottom: preScale * imageData.bottom, 
	                              width: imageData.width, 
	                              height: imageData.height, 
	                              scale: scale});
	        } else {
	            image = null;
	        }

	        return React.createElement("div", {
	                    className: "graphie-container above-scratchpad", 
	                    style: {
	                        width: this.props.box[0],
	                        height: this.props.box[1]
	                    }, 
	                    onMouseOut: this.onMouseOut, 
	                    onMouseOver: this.onMouseOver, 
	                    onClick: this.onClick}, 
	            image, 
	        React.createElement("div", {className: "graphie", ref: "graphieDiv"})
	        );
	    },

	    componentDidMount: function() {
	        this._setupGraphie();
	    },

	    componentDidUpdate: function() {
	        // Only setupGraphie once per componentDidUpdate().
	        // See explanation in setupGraphie().
	        this._hasSetupGraphieThisUpdate = false;
	        if (this._shouldSetupGraphie) {
	            this._setupGraphie();
	            this._shouldSetupGraphie = false;
	        }
	    },

	    componentWillReceiveProps: function(nextProps) {
	        var potentialChanges = ["labels", "range", "step", "markings",
	            "showProtractor", "showRuler", "rulerLabel", "rulerTicks",
	            "gridStep", "snapStep"];
	        var self = this;
	        _.each(potentialChanges, function(prop) {
	            if (!_.isEqual(self.props[prop], nextProps[prop])) {
	                self._shouldSetupGraphie = true;
	            }
	        });
	    },

	    /* Reset the graphie canvas to its initial state
	     *
	     * Use when re-rendering the parent component and you need a blank
	     * graphie.
	     */
	    reset: function() {
	        this._setupGraphie();
	    },

	    graphie: function() {
	        return this._graphie;
	    },

	    pointsFromNormalized: function(coordsList, noSnap) {
	        var self = this;
	        return _.map(coordsList, function(coords) {
	            return _.map(coords, function(coord, i) {
	                var range = self.props.range[i];
	                if (noSnap) {
	                    return range[0] + (range[1] - range[0]) * coord;
	                } else {
	                    var step = self.props.step[i];
	                    var nSteps = numSteps(range, step);
	                    var tick = Math.round(coord * nSteps);
	                    return range[0] + step * tick;
	                }
	            });
	        });
	    },

	    _setupGraphie: function() {
	        // Only setupGraphie once per componentDidUpdate().
	        // This prevents this component from rendering graphie
	        // and then immediately re-render graphie because its
	        // parent component asked it to. This will happen when
	        // props on the parent and props on this component both
	        // require graphie to be re-rendered.
	        if (this._hasSetupGraphieThisUpdate) {
	            return;
	        }

	        var graphieDiv = this.refs.graphieDiv.getDOMNode();
	        $(graphieDiv).empty();
	        var labels = this.props.labels;
	        var range = this.props.range;
	        var graphie = this._graphie = KhanUtil.createGraphie(graphieDiv);

	        var gridConfig = this._getGridConfig();
	        graphie.snap = this.props.snapStep;

	        if (this.props.markings === "graph") {
	            graphie.graphInit({
	                range: range,
	                scale: _.pluck(gridConfig, "scale"),
	                axisArrows: "<->",
	                labelFormat: function(s) { return "\\small{" + s + "}"; },
	                gridStep: this.props.gridStep,
	                tickStep: _.pluck(gridConfig, "tickStep"),
	                labelStep: 1,
	                unityLabels: _.pluck(gridConfig, "unityLabel")
	            });
	            graphie.label([0, range[1][1]], labels[1], "above");
	            graphie.label([range[0][1], 0], labels[0], "right");
	        } else if (this.props.markings === "grid") {
	            graphie.graphInit({
	                range: range,
	                scale: _.pluck(gridConfig, "scale"),
	                gridStep: this.props.gridStep,
	                axes: false,
	                ticks: false,
	                labels: false
	            });
	        } else if (this.props.markings === "none") {
	            graphie.init({
	                range: range,
	                scale: _.pluck(gridConfig, "scale")
	            });
	        }

	        // Add instructions just before mouse layer
	        var visible = 0.5;
	        var invisible = 0.0;
	        var $instructionsWrapper;
	        if (this.props.instructions) {
	            var $instructionsWrapper = $("<div/>");
	            _.each(defaultInstructionsStyle, function(value, key) {
	                $instructionsWrapper.css(key, value);
	            });
	            $instructionsWrapper.css("opacity", visible);

	            var $instructions = $("<span/>", {
	                text: this.props.instructions
	            });
	            _.each(instructionsTextStyle, function(value, key) {
	                $instructions.css(key, value);
	            });

	            $instructionsWrapper.append($instructions);
	            $(graphieDiv).append($instructionsWrapper);
	        } else {
	            $instructionsWrapper = undefined;
	        }

	        // Add some handlers for instructions text (if necessary)
	        var onMouseDown = ($instructionsWrapper || this.props.onMouseDown) ?
	            _.bind(function(coord) {
	                if ($instructionsWrapper) {
	                    $instructionsWrapper.remove();
	                    $instructionsWrapper = null;
	                }
	                this.props.onMouseDown(coord);
	            }, this) : null;

	        var onMouseOver = ($instructionsWrapper) ?
	            function() {
	                $instructionsWrapper &&
	                    $instructionsWrapper.css("opacity", invisible);
	            } : null;

	        var onMouseOut = ($instructionsWrapper) ?
	            function() {
	                $instructionsWrapper &&
	                    $instructionsWrapper.css("opacity", visible);
	            } : null;

	        graphie.addMouseLayer({
	            onClick: this.props.onClick,
	            onMouseDown: onMouseDown,
	            onMouseOver: onMouseOver,
	            onMouseOut: onMouseOut,
	            onMouseUp: this.props.onMouseUp,
	            onMouseMove: this.props.onMouseMove,
	            allowScratchpad: true
	        });

	        this._updateProtractor();
	        this._updateRuler();

	        // We set this flag before jumping into our callback
	        // to avoid recursing if our callback calls reset() itself
	        this._hasSetupGraphieThisUpdate = true;
	        if (this.props.onNewGraphie) {
	            this.props.onNewGraphie(graphie);
	        }
	    },

	    _getGridConfig: function() {
	        var self = this;
	        return _.map(self.props.step, function(step, i) {
	            return Util.gridDimensionConfig(
	                    step,
	                    self.props.range[i],
	                    self.props.box[i],
	                    self.props.gridStep[i]);
	        });
	    },

	    _updateProtractor: function() {
	        if (this.protractor) {
	            this.protractor.remove();
	        }

	        if (this.props.showProtractor) {
	            var coord = this.pointsFromNormalized([[0.50, 0.05]])[0];
	            this.protractor = this._graphie.protractor(coord);
	        }
	    },

	    _updateRuler: function() {
	        if (this.ruler) {
	            this.ruler.remove();
	        }

	        if (this.props.showRuler) {
	            var coord = this.pointsFromNormalized([[0.50, 0.25]])[0];
	            var extent = this._graphie.range[0][1] - this._graphie.range[0][0];
	            this.ruler = this._graphie.ruler({
	                center: coord,
	                label: this.props.rulerLabel,
	                pixelsPerUnit: this._graphie.scale[0],
	                ticksPerUnit: this.props.rulerTicks,
	                units: Math.round(0.8 * extent)
	            });
	        }
	    },

	    toJSON: function() {
	        return _.pick(this.props, 'range', 'step', 'markings', 'labels',
	                      'backgroundImage', 'showProtractor', 'showRuler',
	                      'rulerLabel', 'rulerTicks', 'gridStep', 'snapStep');
	    }
	});

	module.exports = Graph;


/***/ },
/* 88 */
/***/ function(module, exports, __webpack_require__) {

	var React         = __webpack_require__(61);
	var TeX           = __webpack_require__(65);
	var ApiClassNames = __webpack_require__(12).ClassNames;
	var Tooltip       = __webpack_require__(74);
	var ModifyTex     = __webpack_require__(84).modifyTex;

	var MathOutput = React.createClass({displayName: 'MathOutput',
	    propTypes: {
	        value: React.PropTypes.oneOfType([
	            React.PropTypes.string,
	            React.PropTypes.number
	        ]),
	        className: React.PropTypes.string,
	        onFocus: React.PropTypes.func,
	        onBlur: React.PropTypes.func
	    },

	    getDefaultProps: function() {
	        return {
	            value: "",
	            onFocus: function() { },
	            onBlur: function() { }
	        };
	    },

	    getInitialState: function() {
	        return {
	            focused: false,
	            selectorNamespace: _.uniqueId("math-output")
	        };
	    },

	    _getInputClassName: function() {
	        var className = "math-output " + ApiClassNames.INPUT + " " +
	            ApiClassNames.INTERACTIVE;
	        if (this.state.focused) {
	            className += " " + ApiClassNames.FOCUSED;
	        }
	        if (this.props.className) {
	            className += " " + this.props.className;
	        }
	        return className;
	    },

	    _getDisplayValue: function(value) {
	        // Cast from (potentially a) number to string
	        var displayText;
	        if (value != null) {
	            displayText = "" + value;
	        } else {
	            displayText = "";
	        }
	        return ModifyTex(displayText);
	    },

	    render: function() {
	        var divStyle = {
	            textAlign: "center"
	        };

	        return React.createElement("span", {ref: "input", 
	                className: this._getInputClassName(), 
	                onMouseDown: this.focus, 
	                onTouchStart: this.focus}, 
	            React.createElement("div", {style: divStyle}, 
	                React.createElement(TeX, null, 
	                    this._getDisplayValue(this.props.value)
	                )
	            )
	        );
	    },

	    getValue: function() {
	        return this.props.value;
	    },

	    focus: function() {
	        if (!this.state.focused) {
	            this.props.onFocus();
	            this._bindBlurHandler();
	            this.setState({
	                focused: true
	            });
	        }
	    },

	    blur: function() {
	        if (this.state.focused) {
	            this.props.onBlur();
	            this._unbindBlurHandler();
	            this.setState({
	                focused: false
	            });
	        }
	    },

	    _bindBlurHandler: function() {
	        $(document).bind("vclick." + this.state.selectorNamespace, function(e)  {
	            // Detect whether the target has our React DOM node as a parent
	            var $closestWidget = $(e.target).closest(this.getDOMNode());
	            if (!$closestWidget.length) {
	                this.blur();
	            }
	        }.bind(this));
	    },

	    _unbindBlurHandler: function() {
	        $(document).unbind("." + this.state.selectorNamespace);
	    },

	    componentWillUnmount: function() {
	        this._unbindBlurHandler();
	    }
	});

	module.exports = MathOutput;


/***/ },
/* 89 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Utility functions for writing Interactive2 movablethings
	 */

	var _ = __webpack_require__(60);
	var MovableHelperMethods = __webpack_require__(112);

	/**
	 * Compute the correct vendor-prefixed `transform`.
	 */
	var prefixedTransform = null;
	function computePrefixedTransform() {
	    // Temporary element for testing prefix validity
	    var el = document.createElement("div");

	    var prefixes = ["transform", "msTransform", "MozTransform",
	        "WebkitTransform", "OTransform"];
	    var correctPrefix = null;
	    _.each(prefixes, function(prefix) {
	        if (typeof el.style[prefix] !== 'undefined') {
	            correctPrefix = prefix;
	        }
	    });
	    return correctPrefix;
	}

	/**
	 * Compute whether the browser can use 3d transforms by trying to use the
	 * translateZ transformation.
	 */
	var canUse3dTransform = null;
	function computeCanUse3dTransform() {
	    var el = document.createElement("div");

	    var prefix = InteractiveUtil.getPrefixedTransform();

	    el.style[prefix] = "translateZ(0px)";
	    return !!el.style[prefix];
	}

	var InteractiveUtil = {
	    assert: function(isTrue, message) {
	        if (!isTrue) {
	            throw new Error("Assertion Error" +
	                    (message ? ": " + message : ""));
	        }
	    },

	    /**
	     * Create getters for this.state, based on the default state, `defaults`
	     */
	    createGettersFor: function(Class, defaults) {
	        _.each(_.keys(defaults), function(key) {
	            if (Class.prototype[key] === undefined) {
	                Class.prototype[key] = function() {
	                    return this.state[key];
	                };
	            }
	        });
	    },

	    /**
	     * Add MovableHelperMethods methods to a MovableThing class
	     */
	    addMovableHelperMethodsTo: function(Class) {
	        _.each(MovableHelperMethods, function(methodFunc, methodName) {
	            if (Class.prototype[methodName] === undefined) {
	                Class.prototype[methodName] = methodFunc;
	            }
	        });
	    },

	    /**
	     * Turn a function or an array of functions into an array of functions
	     */
	    arrayify: function(funcOrArray) {
	        if (funcOrArray == null) {
	            return [];
	        } else if (_.isArray(funcOrArray)) {
	            return _.filter(_.flatten(funcOrArray), _.identity);
	        } else {
	            return [funcOrArray];
	        }
	    },

	    /**
	     * Convert all function-or-array arguments to arrays of functions
	     */
	    normalizeOptions: function(arrayOptionNames, options) {
	        // TODO(jack): Having to clone here is annoying; this
	        // function should really just modify this.state in place
	        // (and maybe be a function on MovableHelperMethods to get access
	        // to this.state), which would also be nicer because we could
	        // normalizeOptions once in this.modify
	        var result = _.clone(options);
	        _.each(arrayOptionNames, function(eventName) {
	            var funcOrArray = options[eventName];
	            // Only propagate values which were set; not present values
	            // shouldn't be added to options because we'd like them to
	            // fall through to defaults
	            if (funcOrArray !== undefined) {
	                var funcArray = InteractiveUtil.arrayify(funcOrArray);
	                result[eventName] = funcArray;
	            }
	        });
	        return result;
	    },

	    /**
	     * Get the correct vendor-prefixed `transform`.
	     */
	    getPrefixedTransform: function() {
	        // Cache result to avoid re-computation
	        prefixedTransform = prefixedTransform || computePrefixedTransform();
	        return prefixedTransform;
	    },

	    /**
	     * Get whether the browser can use 3d transforms.
	     */
	    getCanUse3dTransform: function() {
	        if (canUse3dTransform == null) {
	            canUse3dTransform = computeCanUse3dTransform();
	        }
	        return canUse3dTransform;
	    }
	};

	module.exports = InteractiveUtil;


/***/ },
/* 90 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);
	var _ = __webpack_require__(60);

	var Util     = __webpack_require__(3);
	var Renderer = __webpack_require__(10);

	var ApiClassNames = __webpack_require__(12).ClassNames;

	var PREFIX = "perseus-sortable";


	// A placeholder that appears in the sortable whenever an item is dragged.
	var Placeholder = React.createClass({displayName: 'Placeholder',
	    propTypes: {
	        width: React.PropTypes.number.isRequired,
	        height: React.PropTypes.number.isRequired
	    },

	    render: function() {
	        var className = [PREFIX + "-card", PREFIX + "-placeholder"].join(" ");
	        var style = {width: this.props.width, height: this.props.height};

	        if (this.props.margin != null) {
	            style.margin = this.props.margin;
	        }

	        return React.createElement("li", {className: className, style: style});
	    }
	});


	var STATIC = "static",
	    DRAGGING = "dragging",
	    ANIMATING = "animating",
	    DISABLED = "disabled";

	// A draggable item in the sortable. Can be in one of four states:
	//     Static:    The item is not being interacted with.
	//     Dragging:  The item is being dragged.
	//     Animating: The item has been released, and is moving to its destination.
	//     Disabled:  The item cannot be interacted with.
	//
	// Usual flow:      Static -> Dragging -> Animating -> Static
	// [Dis|en]abling:  Static|Dragging|Animating -> Disabled -> Static
	var Draggable = React.createClass({displayName: 'Draggable',
	    propTypes: {
	        type: React.PropTypes.oneOf([STATIC, DRAGGING, ANIMATING, DISABLED]),
	        content: React.PropTypes.string.isRequired,
	        endPosition: React.PropTypes.object.isRequired,
	        onRender: React.PropTypes.func.isRequired,
	        onMouseDown: React.PropTypes.func.isRequired,
	        onMouseMove: React.PropTypes.func.isRequired,
	        onMouseUp: React.PropTypes.func.isRequired,
	        onAnimationEnd: React.PropTypes.func.isRequired
	    },

	    getDefaultProps: function() {
	        return {
	            type: STATIC
	        };
	    },

	    getInitialState: function() {
	        return {
	            startPosition: {left: 0, top: 0},
	            startMouse: {left: 0, top: 0},
	            mouse: {left: 0, top: 0}
	        };
	    },

	    componentDidMount: function() {
	        this.isMouseMoveUpBound = false;
	    },

	    componentWillUnmount: function() {
	        // Event handlers should be unbound before component unmounting, but
	        // just in case...
	        if (this.isMouseMoveUpBound) {
	            this.unbindMouseMoveUp();
	        }
	    },

	    getCurrentPosition: function() {
	        return {
	            left: this.state.startPosition.left +
	                  this.state.mouse.left -
	                  this.state.startMouse.left,
	            top: this.state.startPosition.top +
	                 this.state.mouse.top -
	                 this.state.startMouse.top
	        };
	    },

	    render: function() {
	        var className = [
	                PREFIX + "-card",
	                PREFIX + "-draggable",
	                PREFIX + "-" + this.props.type,
	                ApiClassNames.INTERACTIVE
	            ].join(" ");

	        var style = {
	            position: "static"
	        };

	        if (this.props.type === DRAGGING || this.props.type === ANIMATING) {
	            _.extend(style, {position: "absolute"}, this.getCurrentPosition());
	        }

	        if (this.props.width) {
	            style.width = this.props.width + 1; // Fix for non-integer widths
	        }
	        if (this.props.height) {
	            style.height = this.props.height;
	        }
	        if (this.props.margin != null) {
	            style.margin = this.props.margin;
	        }

	        return React.createElement("li", {
	                    className: className, 
	                    style: style, 
	                    onMouseDown: this.onMouseDown, 
	                    onTouchStart: this.onMouseDown, 
	                    onTouchMove: this.onMouseMove, 
	                    onTouchEnd: this.onMouseUp, 
	                    onTouchCancel: this.onMouseUp}, 
	            React.createElement(Renderer, {
	                content: this.props.content, 
	                onRender: this.props.onRender})
	        );
	    },

	    componentDidUpdate: function(prevProps) {
	        if (this.props.type === prevProps.type) {
	            return;
	        }

	        if (this.props.type === ANIMATING) {
	            // Start animating
	            var current = this.getCurrentPosition();
	            var duration = 15 * Math.sqrt(
	                Math.sqrt(
	                    Math.pow(this.props.endPosition.left - current.left, 2) +
	                    Math.pow(this.props.endPosition.top - current.top, 2)
	                )
	            );

	            $(this.getDOMNode()).animate(this.props.endPosition, {
	                duration: Math.max(duration, 1),
	                // Animating -> Static
	                complete: this.props.onAnimationEnd
	            });
	        } else if (this.props.type === STATIC) {
	            // Ensure that any animations are done
	            $(this.getDOMNode()).finish();
	        }
	    },

	    bindMouseMoveUp: function() {
	        this.isMouseMoveUpBound = true;
	        $(document).on("mousemove", this.onMouseMove);
	        $(document).on("mouseup", this.onMouseUp);
	    },

	    unbindMouseMoveUp: function() {
	        this.isMouseMoveUpBound = false;
	        $(document).off("mousemove", this.onMouseMove);
	        $(document).off("mouseup", this.onMouseUp);
	    },


	    onMouseDown: function(event) {
	        if (this.props.type !== STATIC) {
	            return;
	        }

	        if (!(event.button === 0 ||
	                (event.touches != null && event.touches.length === 1))) {
	            return;
	        }

	        event.preventDefault();
	        var loc = Util.extractPointerLocation(event);
	        if (loc) {
	            this.setState({
	                startPosition: $(this.getDOMNode()).position(),
	                startMouse: loc,
	                mouse: loc
	            }, function() {
	                this.bindMouseMoveUp();

	                // Static -> Dragging
	                this.props.onMouseDown();
	            });
	        }
	    },

	    onMouseMove: function(event) {
	        if (this.props.type !== DRAGGING) {
	            return;
	        }

	        event.preventDefault();
	        var loc = Util.extractPointerLocation(event);
	        if (loc) {
	            this.setState({
	                mouse: loc,
	            }, this.props.onMouseMove);
	        }
	    },

	    onMouseUp: function(event) {
	        if (this.props.type !== DRAGGING) {
	            return;
	        }

	        event.preventDefault();
	        var loc = Util.extractPointerLocation(event);
	        if (loc) {
	            this.unbindMouseMoveUp();

	            // Dragging -> Animating
	            this.props.onMouseUp();
	        }
	    }
	});


	var HORIZONTAL = "horizontal",
	    VERTICAL = "vertical";

	// The main sortable component.
	var Sortable = React.createClass({displayName: 'Sortable',
	    propTypes: {
	        options: React.PropTypes.array.isRequired,
	        layout: React.PropTypes.oneOf([HORIZONTAL, VERTICAL]),
	        padding: React.PropTypes.bool,
	        disabled: React.PropTypes.bool,
	        constraints: React.PropTypes.object,
	        onMeasure: React.PropTypes.func,
	        margin: React.PropTypes.number,
	        onChange: React.PropTypes.func
	    },

	    getDefaultProps: function() {
	        return {
	            layout: HORIZONTAL,
	            padding: true,
	            disabled: false,
	            constraints: {},
	            onMeasure: function() {},
	            margin: 5,
	            onChange: function() {}
	        };
	    },

	    getInitialState: function() {
	        return {
	            items: this.itemsFromProps(this.props)
	        };
	    },

	    componentWillReceiveProps: function(nextProps) {
	        var prevProps = this.props;

	        if (!_.isEqual(nextProps.options, prevProps.options)) {

	            // Regenerate items
	            this.setState({
	                items: this.itemsFromProps(nextProps)
	            });

	        } else if (nextProps.layout !== prevProps.layout ||
	                   nextProps.padding !== prevProps.padding ||
	                   nextProps.disabled !== prevProps.disabled ||
	                   !_.isEqual(nextProps.constraints, prevProps.constraints)) {

	            // Clear item measurements
	            this.setState({
	                items: this.clearItemMeasurements(this.state.items)
	            });
	        }
	    },

	    componentDidUpdate: function(prevProps) {
	        // Measure items if their dimensions have been reset
	        if (this.state.items.length && !this.state.items[0].width) {
	            this.measureItems();
	        }
	    },

	    itemsFromProps: function(props) {
	        var type = props.disabled ? DISABLED : STATIC;
	        return _.map(props.options, function(option, i) {
	            return {
	                option: option,
	                key: i,
	                type: type,
	                endPosition: {},
	                width: 0,
	                height: 0
	            };
	        });
	    },

	    clearItemMeasurements: function(items) {
	        return _.map(items, function(item) {
	            return _.extend(item, {
	                width: 0,
	                height: 0
	            });
	        });
	    },

	    measureItems: function() {
	        // Measure all items and cache what their dimensions should be, taking
	        // into account constraints and the current layout. This allows syncing
	        // widths and heights for pretty rows/columns. Note that dimensions are
	        // explictly set on Draggables - this prevents them from changing size
	        // or shape while being dragged.

	        var items = _.clone(this.state.items);
	        var $items = _.map(items, function(item) {
	            return $(this.refs[item.key].getDOMNode());
	        }, this);

	        var widths = _.invoke($items, "outerWidth");
	        var heights = _.invoke($items, "outerHeight");

	        var constraints = this.props.constraints;
	        var layout = this.props.layout;

	        var syncWidth;
	        if (constraints.width) {
	            // Items must be at least as wide as the specified constraint
	            syncWidth = _.max(widths.concat(constraints.width));
	        } else if (layout === VERTICAL) {
	            // Sync widths to get a clean column
	            syncWidth = _.max(widths);
	        }

	        var syncHeight;
	        if (constraints.height) {
	            // Items must be at least as high as the specified constraint
	            syncHeight = _.max(heights.concat(constraints.height));
	        } else if (layout === HORIZONTAL) {
	            // Sync widths to get a clean row
	            syncHeight = _.max(heights);
	        }

	        items = _.map(items, function(item, i) {
	            item.width = syncWidth || widths[i];
	            item.height = syncHeight || heights[i];
	            return item;
	        });

	        this.setState({items: items}, function()  {
	            this.props.onMeasure({widths: widths, heights: heights});
	        }.bind(this));
	    },

	    remeasureItems: _.debounce(function() {
	        this.setState({
	            // Clear item measurements
	            items: this.clearItemMeasurements(this.state.items)
	        }, this.measureItems);
	    }, 20),

	    render: function() {
	        var className = [PREFIX, "layout-" + this.props.layout].join(" ");
	        var cards = [];

	        className += this.props.padding ? "" : " unpadded";

	        _.each(this.state.items, function(item, i, items) {
	            var isLast = (i === items.length - 1);
	            var isStatic = (item.type === STATIC || item.type === DISABLED);
	            var margin;

	            if (this.props.layout === HORIZONTAL) {
	                margin = "0 " + this.props.margin + "px 0 0"; // right
	            } else if (this.props.layout === VERTICAL) {
	                margin = "0 0 " + this.props.margin + "px 0"; // bottom
	            }

	            cards.push(
	                React.createElement(Draggable, {
	                    content: item.option, 
	                    key: item.key, 
	                    type: item.type, 
	                    ref: item.key, 
	                    width: item.width, 
	                    height: item.height, 
	                    margin: isLast && isStatic ? 0 : margin, 
	                    endPosition: item.endPosition, 
	                    onRender: this.remeasureItems, 
	                    onMouseDown: this.onMouseDown.bind(this, item.key), 
	                    onMouseMove: this.onMouseMove.bind(this, item.key), 
	                    onMouseUp: this.onMouseUp.bind(this, item.key), 
	                    onTouchMove: this.onMouseMove.bind(this, item.key), 
	                    onTouchEnd: this.onMouseUp.bind(this, item.key), 
	                    onTouchCancel: this.onMouseUp.bind(this, item.key), 
	                    onAnimationEnd: this.onAnimationEnd.bind(this,
	                        item.key)})
	            );

	            if (item.type === DRAGGING || item.type === ANIMATING) {
	                cards.push(
	                    React.createElement(Placeholder, {
	                        key: "placeholder_" + item.key, 
	                        ref: "placeholder_" + item.key, 
	                        width: item.width, 
	                        height: item.height, 
	                        margin: isLast ? 0 : margin})
	                );
	            }
	        }, this);

	        return React.createElement("ul", {className: className}, 
	            cards
	        );
	    },

	    onMouseDown: function(key) {
	        // Static -> Dragging
	        var items = _.map(this.state.items, function(item) {
	            if (item.key === key) {
	                item.type = DRAGGING;
	            }
	            return item;
	        });

	        this.setState({items: items});
	     },

	    onMouseMove: function(key) {
	        // Dragging: Rearrange items based on draggable's position
	        var $draggable = $(this.refs[key].getDOMNode());
	        var $sortable = $(this.getDOMNode());
	        var items = _.clone(this.state.items);
	        var item = _.findWhere(this.state.items, {key: key});
	        var margin = this.props.margin;
	        var currentIndex = _.indexOf(items, item);
	        var newIndex = 0;

	        items.splice(currentIndex, 1);

	        if (this.props.layout === HORIZONTAL) {
	            var midWidth = $draggable.offset().left - $sortable.offset().left;
	            var sumWidth = 0;
	            var cardWidth;

	            _.each(items, function(item) {
	                cardWidth = item.width;
	                if (midWidth > sumWidth + cardWidth / 2) {
	                    newIndex += 1;
	                }
	                sumWidth += cardWidth + margin;
	            });

	        } else {
	            var midHeight = $draggable.offset().top - $sortable.offset().top;
	            var sumHeight = 0;
	            var cardHeight;

	            _.each(items, function(item) {
	                cardHeight = item.height;
	                if (midHeight > sumHeight + cardHeight / 2) {
	                    newIndex += 1;
	                }
	                sumHeight += cardHeight + margin;
	            });
	        }

	        if (newIndex !== currentIndex) {
	            items.splice(newIndex, 0, item);
	            this.setState({items: items});
	        }
	    },

	    onMouseUp: function(key) {
	        // Dragging -> Animating
	        var items = _.map(this.state.items, function(item) {
	            if (item.key === key) {
	                item.type = ANIMATING;
	                item.endPosition = $(this.refs["placeholder_" + key]
	                                    .getDOMNode()).position();
	            }
	            return item;
	        }, this);

	        this.setState({items: items});
	        // HACK: We need to know *that* the widget changed, but currently it's
	        // not set up in a nice way to tell us *how* it changed, since the
	        // permutation of the items is stored in state.
	        this.props.onChange({});
	    },

	    onAnimationEnd: function(key) {
	        // Animating -> Static
	        var items = _.map(this.state.items, function(item) {
	            if (item.key === key) {
	                item.type = STATIC;
	            }
	            return item;
	        });

	        this.setState({items: items});
	    },

	    getOptions: function() {
	        return _.pluck(this.state.items, "option");
	    }
	});

	module.exports = Sortable;


/***/ },
/* 91 */
/***/ function(module, exports, __webpack_require__) {

	/*
	 * A class for formatting number to significant digits.
	 * Copyright (C) 2001 Stephen Ostermiller
	 * http://ostermiller.org/contact.pl?regarding?JavaScript+Significant+Figures
	 *
	 * This program is free software; you can redistribute it and/or modify
	 * it under the terms of the GNU General Public License as published by
	 * the Free Software Foundation; either version 2 of the License, or
	 * (at your option) any later version.
	 *
	 * This program is distributed in the hope that it will be useful,
	 * but WITHOUT ANY WARRANTY; without even the implied warranty of
	 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	 * GNU General Public License for more details.
	 */

	/**
	 * An object tho both scans floating points to
	 * determine the number of significant figures.
	 * and can display a floating point using any number
	 * of significant figures.
	 *
	 * @param s A string representation of a floating point.
	 */
	function SignificantFigures(s){
	    this.order = parseOrder(s);
	    this.mantissa = parseMantissa(s);
	    this.positive = parseSign(s);

	    /**
	     * Get the number of significant figures this object uses.
	     * Leading zeros are not significant.  Traling zeros up to
	     * and after the decimal point are significant.
	     * Significant figures is good to know when the number is
	     * used in multiplication.
	     *
	     * @return the number of significant figures.
	     */
	    this.sigFigs = this.mantissa.length;

	    /**
	     * Get the least significant decimal this object uses.
	     * This is useful to know if a number is being used
	     * in addition.
	     * 400 - 2 the hundreds place (10^2) is the least significant decimal.
	     * 75 - 0 the ones place (10^0) is the least significant decimal.
	     * .543 - -3 the 1/1000's place (10^-3) is the least significant decimal.
	     *
	     * @return an integer representing the least significant decimal place.
	     */
	    this.sigDecs = ((this.order) - this.mantissa.length);
	}

	/**
	 * Format a floating point for display using the specified
	 * number of significant figures and least significant decimal.
	 * Scientific notation may used by this method if this
	 * object is very small, very large, has many significant
	 * figures, or the number of significant figures would be
	 * ambiguous in the output if scientific notation were not
	 * used.
	 *
	 * @param f A floating point number that should be displayed
	 * @param sigFigs desired number of significant figures (integer).
	 * @param sigDecs the least significant decimal place (integer).
	 * @param scientific true iff scientific notation should always be used.
	 * @return a string of this object formatted correctly.
	 */
	function displaySigFigs(f, sigFigs, sigDecs, scientific){
	    var s = "" + f;
	    var order = parseOrder(s);
	    var mantissa = parseMantissa(s);
	    var positive = parseSign(s);
	    var add;
	    var decAdd;
	    var sigAdd;
	    var zeroScientific=false;
	    if (f == 0 || mantissa=="" || mantissa=="0"){
	        mantissa = "";
	        for (i=0; i<sigFigs; i++){
	            mantissa += "0";
	        }
	        order = sigFigs + sigDecs;
	        if (sigDecs < 0 && -sigDecs >= sigFigs){
	            zeroScientific = true;
	        }
	    } else {
	        decAdd = ((order - mantissa.length) - sigDecs);
	        sigAdd = sigFigs - mantissa.length;
	        add = Math.min(sigAdd, decAdd);
	        if (add < 0){
	            var rounded = round(mantissa, -add);
	            if (rounded.length > mantissa.length + add){
	                order++;
	                if (decAdd > sigAdd){
	                	rounded = round(rounded, 1);
	                }
	            }
	            mantissa=rounded;
	        } else if (add > 0){
	            for (i=0; i<add; i++){
	                mantissa += '0';
	            }
	        }
	        if (mantissa=="" || mantissa=="0"){
	            mantissa = "0";
	            positive = true;
	            order = 1 + sigDecs;
	            if (order != 0){
	                zeroScientific = true;
	            }
	        }
	    }
	    var useScientific = (scientific || mantissa.length > 20 || order > 4 || order < -2 ||
			    (order - mantissa.length > 0 && trailingZeros(mantissa) > 0) || zeroScientific);
	    var returnVal = "";
	    if (!positive){
	        returnVal += "-";
	    }
	    if (useScientific) {
	        returnVal += mantissa.charAt(0);
	        if (mantissa.length > 1){
	            returnVal += '.' + mantissa.substring(1, mantissa.length);
	        }
	        if (order-1!=0){
				returnVal += " x 10^" + (order-1);
	        }
	    } else {
	        var wholePart = "";
	        var fractPart = "";
	        var needDot = true;
	        if (order > 0){
	            if (mantissa.length > order){
	                wholePart = mantissa.substring(0, order);
	                fractPart = mantissa.substring(order, mantissa.length);
	            } else {
	                wholePart = mantissa;
	                needDot = (trailingZeros(mantissa) != 0);
	                for(var i=0; i<order-mantissa.length; i++){
	                    wholePart += "0";
	                }
	            }
			} else {
	            for(i=0; i<-order; i++){
	                fractPart += "0";
				}
	            fractPart += mantissa
	        }
	        returnVal += (
	            (wholePart==""?"0":wholePart) + (needDot?".":"") + fractPart

	        );
	    }
	    return (returnVal);
	}

	/**
	 * Count the significant trailing zeros on this object.
	 *
	 * @return the number of trailing zeros
	 */
	function trailingZeros(mantissa){
	    var zeros = 0;
	    for (var i=mantissa.length-1; i>=0; i--){
	    	var c = mantissa.charAt(i);
	        if (c=='0'){
	            zeros++;
	        } else {
	            return zeros;
	        }
	    }
	    return zeros;
	}

	/**
	 * Parse a string representation of a floating point
	 * and pull out the sign.
	 *
	 * @param s the string representation of a floating point.
	 * @return true iff this is a positive number
	 */
	function parseSign(s){
	    var beginning = true;
	    var seenDot = false;
	    var seenSomething = false;
	    var zeros = "";
	    var leadZeros = "";
	    var all = "";
	    var decPlaces = 0;
	    var totalDecs = 0;
	    var pos = true;
		for (var i=0; i<s.length; i++){
	        var c = s.charAt(i);
	        if (c>='1' && c<='9'){
	            all += zeros + c;
	            zeros = "";
	            seenSomething = true;
	            if (!seenDot){
	                totalDecs++;
	                decPlaces++;
	            }
	            beginning = false;
	        } else if (c=='0'){
	            if (seenDot){
	                if (seenSomething){
	                    all += zeros + c;
	                    zeros = "";
	                } else {
	                    leadZeros += c;
	                    decPlaces--;
					}
	            } else {
	                totalDecs++;
	                if (seenSomething){
	                    leadZeros += c;
	                    decPlaces++;
	                    zeros += c;
	                } else {
	                    leadZeros += c;
	                }
	            }
	            beginning = false
			} else if (!seenDot && c=='.'){
	            all += zeros;
	            zeros = "";
	            seenDot=true;
	            beginning = false;
			} else if (c=='e' || c=='E' && i+1<s.length){
	            var raised = parseInt(s.substring(i+1, s.length));
	            decPlaces += raised;
	            totalDecs += raised;
	            i = s.length;
	        } else if (beginning && (c=='+' || c=='-')){
	            if (c=='-'){
	                pos = !pos;
	            }
			}
	    }
	    if (all == ""){
	        return(true);
	    } else {
	        return(pos);
	    }
	}

	/**
	 * Parse a string representation of a floating point
	 * and pull out the mantissa.
	 *
	 * @param s the string representation of a floating point.
	 * @return the mantissa of this number.
	 */
	function parseMantissa(s){
	    var beginning = true;
	    var seenDot = false;
	    var seenSomething = false;
	    var zeros = "";
	    var leadZeros = "";
	    var all = "";
	    var decPlaces = 0;
	    var totalDecs = 0;
	    var pos = true;
		for (var i=0; i<s.length; i++){
	        var c = s.charAt(i);
	        if (c>='1' && c<='9'){
	            all += zeros + c;
	            zeros = "";
	            seenSomething = true;
	            if (!seenDot){
	                totalDecs++;
	                decPlaces++;
	            }
	            beginning = false;
	        } else if (c=='0'){
	            if (seenDot){
	                if (seenSomething){
	                    all += zeros + c;
	                    zeros = "";
	                } else {
	                    leadZeros += c;
	                    decPlaces--;
					}
	            } else {
	                totalDecs++;
	                if (seenSomething){
	                    leadZeros += c;
	                    decPlaces++;
	                    zeros += c;
	                } else {
	                    leadZeros += c;
	                }
	            }
	            beginning = false;
			} else if (!seenDot && c=='.'){
	            all += zeros;
	            zeros = "";
	            seenDot=true;
	            beginning = false;
			} else if (c=='e' || c=='E' && i+1<s.length){
	            var raised = parseInt(s.substring(i+1, s.length));
	            decPlaces += raised;
	            totalDecs += raised;
	            i = s.length;
	        } else if (beginning && (c=='+' || c=='-')){
	            if (c=='-'){
	                pos = !pos;
	            }
			}
	    }
	    if (all == ""){
	        return leadZeros;
	    } else {
	        return all;
	    }
	}

	/**
	 * Parse a string representation of a floating point
	 * and pull out the exponent.
	 *
	 * @param s the string representation of a floating point.
	 * @return (integer) the number after the e.
	 */
	function parseOrder(s){
	    var beginning = true;
	    var seenDot = false;
	    var seenSomething = false;
	    var zeros = "";
	    var leadZeros = "";
	    var all = "";
	    var decPlaces = 0;
	    var totalDecs = 0;
	    var pos = true;
		for (var i=0; i<s.length; i++){
	        var c = s.charAt(i);
	        if (c>='1' && c<='9'){
	            all += zeros + c;
	            zeros = "";
	            seenSomething = true;
	            if (!seenDot){
	                totalDecs++;
	                decPlaces++;
	            }
	            beginning = false;
	        } else if (c=='0'){
	            if (seenDot){
	                if (seenSomething){
	                    all += zeros + c;
	                    zeros = "";
	                } else {
	                    leadZeros += c;
	                    decPlaces--;
					}
	            } else {
	                totalDecs++;
	                if (seenSomething){
	                    leadZeros += c;
	                    decPlaces++;
	                    zeros += c;
	                } else {
	                    leadZeros += c;
	                }
	            }
	            beginning = false
			} else if (!seenDot && c=='.'){
	            all += zeros;
	            zeros = "";
	            seenDot=true;
	            beginning = false;
			} else if (c=='e' || c=='E' && i+1<s.length){
	            var raised = parseInt(s.substring(i+1, s.length));
	            decPlaces += raised;
	            totalDecs += raised;
	            i = s.length;
	        } else if (beginning && (c=='+' || c=='-')){
	            if (c=='-'){
	                pos = !pos;
	            }
			}
	    }
	    if (all == ""){
	        return totalDecs;
	    } else {
	    	return decPlaces;
	    }
	}

	/**
	 * Remove the specified number of digits from string by
	 * rounding.  Proper rounding rules for scientific purposes
	 * are followed.
	 * This method may cause an extra significant figure
	 * to be added to the number.  For example, if 999999
	 * were rounded, A one would carry over and become
	 * a significant figure.  Those who call this method
	 * should check for this and call round again if needed.
	 *
	 * @param mantissa A string representing an whole number of arbitrary length.
	 * @param digits A number of digits to remove
	 * @return A string represted the rounded version of mantissa
	 */
	function round(mantissa, digits){
	    var last = mantissa.length - digits - 1;
	    if (last < 0){
	        return("");
	    } else if (last >= mantissa.length -1){
	        return(mantissa);
		} else {
	    	var nextToLast = mantissa.charAt(last+1);
	        var lastChar = mantissa.charAt(last);
	        var roundUp = false;
	        if (nextToLast > '5') {
	            roundUp = true;
	        } else if (nextToLast == '5') {
	            for (var j=last+2; j<mantissa.length; j++){
	                if(mantissa.charAt(j) != '0'){
	                    roundUp = true;
	                }
	            }
	            if (lastChar % 2 == 1){
	                roundUp = true;
	            }
	        }
	        var result = "";
	        for (var i=last; i>=0; i--){
	            var c = mantissa.charAt(i);
	            if (roundUp){
	                var nextChar;
	                if (c == '9'){
	                    nextChar = '0';
	                } else {
	                    switch (c){
	                        case '0': nextChar='1'; break;
	                        case '1': nextChar='2'; break;
	                        case '2': nextChar='3'; break;
	                        case '3': nextChar='4'; break;
	                        case '4': nextChar='5'; break;
	                        case '5': nextChar='6'; break;
	                        case '6': nextChar='7'; break;
	                        case '7': nextChar='8'; break;
	                        case '8': nextChar='9'; break;
	                    }
	                    roundUp = false;
	                }
	                result = nextChar + result;
	            } else {
	                result = c + result;
	            }
	        }
	        if (roundUp){
	            result = '1' + result;
	        }
	        return(result);
	    }
	}

	module.exports = {
	    SignificantFigures:SignificantFigures,
	    displaySigFigs:displaySigFigs,
	};


/***/ },
/* 92 */
/***/ function(module, exports, __webpack_require__) {

	var React = __webpack_require__(61);
	var Editor = __webpack_require__(7);
	var InfoTip = __webpack_require__(69);
	var Widgets = __webpack_require__(14);
	var ApiOptions = __webpack_require__(12).Options;

	var cx = React.addons.classSet;

	var AnswerTypeSelector = React.createClass({displayName: 'AnswerTypeSelector',
	    render: function() {
	        return React.createElement("div", null, 
	            React.createElement("label", null, 
	                "Answer type:", ' ', 
	                React.createElement("select", {value: this.props.type, 
	                        onChange: function(e)  {return this.props.onChange(e.target.value);}.bind(this)}, 
	                    React.createElement("option", {value: "radio"}, "Multiple choice"), 
	                    React.createElement("option", {value: "table"}, "Table of values"), 
	                    React.createElement("option", {value: "input-number"}, "Text input (number)"), 
	                    React.createElement("option", {value: "expression"}, "Expression / Equation"), 
	                    React.createElement("option", {value: "multiple"}, "Custom format")
	                )
	            ), 
	            React.createElement(InfoTip, null, 
	                React.createElement("p", null, "Use the custom format if the question is in the question" + ' ' +
	                "area, and tell the students how to complete the problem.")
	            )
	        );
	    }
	});

	var AnswerAreaEditor = React.createClass({displayName: 'AnswerAreaEditor',
	    getDefaultProps: function() {
	        return {
	            type: "multiple",
	            options: {},
	            calculator: false,
	            apiOptions: ApiOptions.defaults,
	        };
	    },

	    getInitialState: function() {
	        return {
	            // We don't want people to use the answer area at all, so if it
	            // doesn't have any content we prevent users from adding any.
	            showEditor: !!this.props.options.content,

	            // If it does have content, it should use the "multiple" aka Custom
	            // Format answer type. So unless they are already using something
	            // else, don't give them the option to switch.
	            showTypeSelector: this.props.type !== "multiple",
	        };
	    },

	    render: function() {
	        var Ed;
	        if (this.props.type === "multiple") {
	            Ed = Editor;
	        } else {
	            Ed = Widgets.getEditor(this.props.type);
	        }

	        var className = cx({
	            'perseus-answer-widget': this.props.type !== 'multiple',
	            'perseus-answer-none': !(this.state.showEditor ||
	                                    this.state.showTypeSelector ||
	                                    this.props.apiOptions.enableOldAnswerTypes)
	        });

	        var editor = React.createElement("div", {className: className}, 
	            React.createElement(Ed, React.__spread({
	                ref: "editor", 
	                placeholder: "This answer area is deprecated. Please " +
	                "use the widgets in the question area for your answer.", 
	                apiOptions: this.props.apiOptions, 
	                onChange: function(newProps, cb)  {
	                    var options = _.extend({},
	                                           this.props.options,
	                                           newProps);
	                    this.props.onChange({options: options}, cb);
	                }.bind(this)}, 
	                this.props.options))
	        );

	        return React.createElement("div", {className: "perseus-answer-editor"}, 
	            React.createElement("div", {className: "perseus-answer-options"}, 
	            React.createElement("div", null, React.createElement("label", null, 
	                "Show calculator:", ' ', 
	                React.createElement("input", {type: "checkbox", checked: this.props.calculator, 
	                    onChange: function(e)  {
	                        this.props.onChange({calculator: e.target.checked});
	                    }.bind(this)})
	            ), 
	            React.createElement(InfoTip, null, 
	                React.createElement("p", null, "Use the calculator when completing difficult calculations is" + ' ' +
	                "NOT the intent of the question. DON’T use the calculator when" + ' ' +
	                "testing the student’s ability to complete different types of" + ' ' +
	                "computations.")
	            )
	            ), 

	            (this.state.showTypeSelector ||
	                    this.props.apiOptions.enableOldAnswerTypes) &&
	                React.createElement(AnswerTypeSelector, {
	                   type: this.props.type, 
	                   onChange: function(newValue)  {
	                       this.props.onChange({
	                           type: newValue,
	                           options: {}
	                       }, function()  {
	                           this.refs.editor.focus();
	                       }.bind(this));
	                   }.bind(this)})
	            

	            ), 

	            editor
	        );
	    },

	    getSaveWarnings: function() {
	        var issuesFunc = this.refs.editor.getSaveWarnings();
	        return issuesFunc ? issuesFunc() : [];
	    },

	    serialize: function(options) {
	        // could be _.pick(this.props, "type", "options", "calculator");
	        return {
	            type: this.props.type,
	            options: this.refs.editor.serialize(options),
	            calculator: this.props.calculator
	        };
	    }
	});

	module.exports = AnswerAreaEditor;


/***/ },
/* 93 */
/***/ function(module, exports, __webpack_require__) {

	// Split a word-wise diff generated by jsdiff into multiple lines, for the
	// purpose of breaking up the diffs into lines, so that modified lines can be
	// faintly highlighted

	var splitDiff = function(diffEntries) {
	    var lines = [];
	    var currentLine = [];
	    _.each(diffEntries, function(entry)  {
	        var values = entry.value.split("\n");
	        _.each(values, function(value, i)  {
	            var isNewline = i > 0;
	            if (isNewline) {
	                lines.push(currentLine);
	                currentLine = [];
	            }
	            var newEntry = _.extend({}, entry, { value: value });
	            currentLine.push(newEntry);
	        });
	    });

	    if (currentLine.length) {
	        lines.push(currentLine);
	    }
	    return lines;
	};


	module.exports = splitDiff;


/***/ },
/* 94 */
/***/ function(module, exports, __webpack_require__) {

	var jsdiff = __webpack_require__(102);
	var _ = __webpack_require__(60);

	var statusFor = function(chunk) {
	    if (chunk.added) {
	        return "added";
	    } else if (chunk.removed) {
	        return "removed";
	    } else {
	        return "unchanged";
	    }
	};

	// Turn a chunk (which contains an array of values and a status)
	// into an array of values, each with the same status
	var splitUpChunk = function(chunk)  {return _.map(chunk.value, function(value)  {
	    return {
	        value: value,
	        status: statusFor(chunk)
	    };
	});};

	// Apply `fn` to every element in `lst` and then concatenate all the results
	// http://clojuredocs.org/clojure_core/clojure.core/mapcat
	var mapcat = function(lst, fn) {
	    return _.flatten(_.map(lst, fn), true /* only flatten one level */);
	};

	// > ArrayDiff.diff([1,2,3], [2,3,4]);
	// = [{ "value": [1],
	//      "removed": true },
	//    { "value": [2, 3] },
	//    { "value": [4],
	//      "added": true }]
	var ArrayDiff = new jsdiff.Diff();
	ArrayDiff.tokenize = function(array)  {return _.map(array, function(elem)  {return [elem];});};
	// The default is `+` for string concatenation, which doesn't work for array
	// concatenation.
	ArrayDiff.join = function(a, b)  {return a.concat(b);};
	// By default jsDiff uses ===
	ArrayDiff.equals = _.isEqual;

	// Take the output of jsdiff's function (which concatenates adjacent entries)
	// and make it just one entry per chunk
	// > flattenChunks([{ "value": [1],
	//                    "removed": true },
	//                  { "value": [2, 3] },
	//                  { "value": [4],
	//                    "added": true }])
	// = [{ "value":1, "status":"removed"},
	//    { "value":2, "status":"unchanged"},
	//    { "value":3, "status":"unchanged"},
	//    { "value":4, "status":"added"}]
	var flattenChunks = function(chunks)  {return mapcat(chunks, splitUpChunk);};

	// Take two arrays and create a diff for them. The result is two arrays of
	// objects, one for the things that should be included in a 'before', and one
	// for 'after'
	var stringArrayDiff = function(a, b) {
	    var diffResult = ArrayDiff.diff(a, b);
	    var flattened = flattenChunks(diffResult);

	    return {
	        before: _.filter(flattened, function(entry)  {return entry.status !== "added";}),
	        after: _.filter(flattened, function(entry)  {return entry.status !== "removed";})
	    };
	};

	module.exports = stringArrayDiff;


/***/ },
/* 95 */
/***/ function(module, exports, __webpack_require__) {

	var UNCHANGED = "unchanged";
	var CHANGED = "changed";
	var ADDED = "added";
	var REMOVED = "removed";

	// For values which do not have further values nested within them (strings,
	// numbers, and booleans)
	var valueEntry = function(before, after, key) {
	    var status;
	    if (before === after) {
	        status = UNCHANGED;
	    } else if (before === undefined) {
	        status = ADDED;
	    } else if (after === undefined) {
	        status = REMOVED;
	    } else {
	        status = CHANGED;
	    }

	    return {
	        after: JSON.stringify(after),
	        before: JSON.stringify(before),
	        children: [],
	        key: key,
	        status: status
	    };
	};

	// For values which require a more granular diff (objects and arrays)
	var objectEntry = function(before, after, key) {
	    var beforeKeys = (_.isObject(before)) ? _(before).keys() : [];
	    var afterKeys = (_.isObject(after)) ? _(after).keys() : [];
	    var keys = _.union(beforeKeys, afterKeys);

	    var children = _.map(keys, function(key) {
	        return performDiff((before || {})[key], (after || {})[key], key);
	    });

	    var status;
	    if (before === undefined) {
	        status = ADDED;
	    } else if (after === undefined) {
	        status = REMOVED;
	    } else {
	        var changed = _.any(children, function(child) {
	            return child.status !== UNCHANGED;
	        });
	        status = changed ? CHANGED : UNCHANGED;
	    }

	    return {
	        after: "",
	        before: "",
	        children: children,
	        key: key,
	        status: status
	    };
	};

	var performDiff = function(before, after, /* optional */ key) {
	    if (typeof before === "object" || typeof after === "object") {
	        return objectEntry(before, after, key);
	    } else {
	        return valueEntry(before, after, key);
	    }
	};

	module.exports = performDiff;


/***/ },
/* 96 */
/***/ function(module, exports, __webpack_require__) {

	var ButtonGroup = __webpack_require__(78);

	var ArrowPicker = React.createClass({displayName: 'ArrowPicker',
	    propTypes: {
	        value: React.PropTypes.string,
	        onChange: React.PropTypes.func.isRequired
	    },

	    getDefaultProps: function() {
	        return {
	            value: ""
	        };
	    },

	    render: function() {
	        return React.createElement(ButtonGroup, {value: this.props.value, 
	            allowEmpty: false, 
	            buttons: [
	                {value: "", content: React.createElement("span", null, "—")},
	                {value: "->", content: React.createElement("span", null, "→")},
	                /*
	                TODO(eater): fix khan-exercises so these are supported
	                {value: "<-", content: <span>&#x2190;</span>},
	                {value: "<->", content: <span>&#x2194;</span>}
	                */], 
	            onChange: this.props.onChange});
	    }
	});

	module.exports = ArrowPicker;


/***/ },
/* 97 */
/***/ function(module, exports, __webpack_require__) {

	var ButtonGroup = __webpack_require__(78);

	var ColorPicker = React.createClass({displayName: 'ColorPicker',
	    COLORS: [KhanUtil.BLACK, KhanUtil.BLUE, KhanUtil.GREEN, KhanUtil.PINK,
	        KhanUtil.PURPLE, KhanUtil.RED, KhanUtil.GRAY],

	    LIGHT_COLORS: [KhanUtil.LIGHT_BLUE, KhanUtil.LIGHT_ORANGE,
	        KhanUtil.LIGHT_PINK, KhanUtil.LIGHT_GREEN, KhanUtil.LIGHT_PURPLE,
	        KhanUtil.LIGHT_RED, "#fff"],

	    propTypes: {
	        value: React.PropTypes.string,
	        lightColors: React.PropTypes.bool,
	        onChange: React.PropTypes.func.isRequired
	    },

	    getDefaultProps: function() {
	        return {
	            value: KhanUtil.BLACK,
	            lightColors: false
	        };
	    },

	    render: function() {
	        return React.createElement(ButtonGroup, {value: this.props.value, 
	            allowEmpty: false, 
	            buttons: _.map(this.props.lightColors ? this.LIGHT_COLORS :
	                this.COLORS, function(color)  {
	                    return {
	                        value: color,
	                        content: React.createElement("span", null, React.createElement("span", {
	                            className: "colorpicker-circle", 
	                            style: {background: color}}
	                        ), " ")
	                    };
	                }), 
	            onChange: this.props.onChange});
	    }
	});

	module.exports = ColorPicker;


/***/ },
/* 98 */
/***/ function(module, exports, __webpack_require__) {

	var ButtonGroup = __webpack_require__(78);
	var Changeable = __webpack_require__(66);
	var MathInput = __webpack_require__(76);
	var NumberInput = __webpack_require__(85);
	var TeX = __webpack_require__(65);

	var ConstraintEditor = React.createClass({displayName: 'ConstraintEditor',
	    mixins: [Changeable],

	    propTypes: {
	        constraint: React.PropTypes.string,
	        snap: React.PropTypes.number,
	        constraintFn: React.PropTypes.string,
	        onChange: React.PropTypes.func.isRequired,
	        constraintXMin: React.PropTypes.string,
	        constraintXMax: React.PropTypes.string,
	        constraintYMin: React.PropTypes.string,
	        constraintYMax: React.PropTypes.string
	    },

	    getDefaultProps: function() {
	        return {
	            constraint: "none",
	            snap: 0.5,
	            constraintFn: "0",
	            constraintXMin: "-10",
	            constraintXMax: "10",
	            constraintYMin: "-10",
	            constraintYMax: "10"
	        };
	    },

	    render: function() {
	        return React.createElement("div", null, 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                "Constraint: ", React.createElement(ButtonGroup, {value: this.props.constraint, 
	                    allowEmpty: false, 
	                    buttons: [
	                        {value: "none", content: "None"},
	                        {value: "snap", content: "Snap"},
	                        {value: "x",    content: "x="},
	                        {value: "y",    content: "y="}], 
	                    onChange: this.change("constraint")})
	            ), 
	            this.props.constraint === "snap" &&
	                React.createElement("div", {className: "perseus-widget-row"}, 
	                    "Snap: ", React.createElement(NumberInput, {
	                        value: this.props.snap, 
	                        placeholder: 0, 
	                        onChange: this.change("snap")})
	            ), 
	            this.props.constraint === "x" && React.createElement("div", {className: "graph-settings"}, 
	                React.createElement("div", {className: "perseus-widget-row"}, 
	                    React.createElement(TeX, null, "x="), " ", React.createElement(MathInput, {
	                        buttonSets: [], 
	                        buttonsVisible: "never", 
	                        value: this.props.constraintFn, 
	                        onChange: this.change("constraintFn")})
	                )
	            ), 
	            this.props.constraint === "y" && React.createElement("div", {className: "graph-settings"}, 
	                React.createElement("div", {className: "perseus-widget-row"}, 
	                    React.createElement(TeX, null, "y="), " ", React.createElement(MathInput, {
	                        buttonSets: [], 
	                        buttonsVisible: "never", 
	                        value: this.props.constraintFn, 
	                        onChange: this.change("constraintFn")})
	                )
	            ), 
	            "Ensure these are set so nothing can be dragged off the canvas:", 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                React.createElement("div", {className: "perseus-widget-row"}, 
	                    React.createElement(TeX, null, "x \\in \\Large["), " ", React.createElement(MathInput, {
	                        buttonSets: [], 
	                        buttonsVisible: "never", 
	                        value: this.props.constraintXMin, 
	                        onChange: this.change("constraintXMin")}), 
	                    React.createElement(TeX, null, ", "), " ", React.createElement(MathInput, {
	                        buttonSets: [], 
	                        buttonsVisible: "never", 
	                        value: this.props.constraintXMax, 
	                        onChange: this.change("constraintXMax")}
	                    ), " ", React.createElement(TeX, null, "\\Large]")
	                )
	            ), 
	            React.createElement("div", {className: "perseus-widget-row"}, 
	                React.createElement("div", {className: "perseus-widget-row"}, 
	                    React.createElement(TeX, null, "y \\in \\Large["), " ", React.createElement(MathInput, {
	                        buttonSets: [], 
	                        buttonsVisible: "never", 
	                        value: this.props.constraintYMin, 
	                        onChange: this.change("constraintYMin")}), 
	                    React.createElement(TeX, null, ", "), " ", React.createElement(MathInput, {
	                        buttonSets: [], 
	                        buttonsVisible: "never", 
	                        value: this.props.constraintYMax, 
	                        onChange: this.change("constraintYMax")}
	                    ), " ", React.createElement(TeX, null, "\\Large]")
	                )
	            )
	        );
	    }
	});

	module.exports = ConstraintEditor;


/***/ },
/* 99 */
/***/ function(module, exports, __webpack_require__) {

	var ButtonGroup = __webpack_require__(78);

	var DashPicker = React.createClass({displayName: 'DashPicker',
	    propTypes: {
	        value: React.PropTypes.string,
	        onChange: React.PropTypes.func.isRequired
	    },

	    getDefaultProps: function() {
	        return {
	            value: ""
	        };
	    },

	    render: function() {
	        return React.createElement(ButtonGroup, {value: this.props.value, 
	            allowEmpty: false, 
	            buttons: [
	            {value: "", content: React.createElement("span", null, "—")},
	            {value: "-", content: React.createElement("span", null, "–––")},
	            {value: "- ", content: React.createElement("span", null, "–  –")},
	            {value: ".", content: React.createElement("span", null, "····"
	                )},
	            {value: ". ", content: React.createElement("span", null, "· · ·")}], 
	            onChange: this.props.onChange});
	    }
	});

	module.exports = DashPicker;


/***/ },
/* 100 */
/***/ function(module, exports, __webpack_require__) {

	var ElementContainer = React.createClass({displayName: 'ElementContainer',
	    propTypes: {
	        initiallVisible: React.PropTypes.bool,
	        title: React.PropTypes.node
	    },

	    getDefaultProps: function() {
	        return {
	            initiallyVisible: false,
	            title: "More"
	        };
	    },

	    getInitialState: function() {
	        return {
	            show: this.props.initiallyVisible,
	            title: "More",
	            onUp: null,
	            onDown: null,
	            onDelete: null
	        };
	    },

	    render: function() {
	        return React.createElement("div", {className: "perseus-interaction-element"}, 
	            React.createElement("a", {href: "#", className: "perseus-interaction-element-title " +
	                (this.state.show ? "open" : "closed"), 
	                onClick: this.toggle}, 
	                React.createElement("i", {className: "icon-chevron-" +
	                    (this.state.show ? "down" : "right")}), 
	                this.props.title
	            ), 
	            React.createElement("div", {className: "perseus-interaction-element-content " +
	                    (this.state.show ? "enter" : "leave")}, 
	                this.props.children, 
	                (this.props.onUp != null ||
	                    this.props.onDown != null ||
	                    this.props.onDelete != null) &&
	                    React.createElement("div", {className: "edit-controls"}, 
	                        (this.props.onUp != null) && React.createElement("button", {
	                            onClick: this.props.onUp}, 
	                                React.createElement("i", {className: "icon-circle-arrow-up"})
	                            ), 
	                        (this.props.onDown != null) && React.createElement("button", {
	                            onClick: this.props.onDown}, 
	                                React.createElement("i", {className: "icon-circle-arrow-down"})
	                            ), 
	                        (this.props.onDelete != null) && React.createElement("button", {
	                            onClick: this.props.onDelete}, 
	                                React.createElement("i", {className: "icon-trash"})
	                            )
	                    )
	                
	            )
	        );
	    },

	    toggle: function(e) {
	        e.preventDefault();
	        this.setState({show: !this.state.show});
	    }
	});

	module.exports = ElementContainer;


/***/ },
/* 101 */
/***/ function(module, exports, __webpack_require__) {

	var _ = __webpack_require__(60);

	var SimpleMarkdown = __webpack_require__(113);

	var START_REF_PREFIX = "start-ref-";
	var END_REF_PREFIX = "end-ref-";
	var REF_STYLE = {
	    display: "inline-block",
	    width: 0,
	    visibility: "hidden"
	};

	var LABEL_OUTER_STYLE = {
	    // for some reason we need these to keep the nbsp from wrapping when the
	    // inner circle/square is display: inline-block
	    display: "inline",
	    whiteSpace: "nowrap",
	};

	var SQUARE_LABEL_STYLE = {
	    display: "inline-block",
	    color: "rgb(255, 255, 255)",
	    backgroundColor: "rgb(90, 90, 90)",
	    paddingLeft: 10,
	    paddingRight: 10,
	    userSelect: "none",
	    WebkitUserSelect: "none",
	};

	var CIRCLE_LABEL_STYLE = {
	    display: "inline-block",
	    color: "rgb(255, 255, 255)",
	    backgroundColor: "rgb(90, 90, 90)",
	    userSelect: "none",
	    WebkitUserSelect: "none",
	    width: 22,
	    height: 22,
	    borderRadius: "50%",
	    textAlign: "center",
	};

	var rules = _.extend({}, SimpleMarkdown.defaultRules, {
	    passageFootnote: {
	        regex: /^\^/,
	        parse: function(capture, parse, state)  {
	            // if no footnotes have been seen, we're id 1. otherwise,
	            // we're the next subsequent id
	            var id = state.lastFootnote.id + 1;
	            var footnote = {
	                id: id,
	                // our text is what to output. if there is only one footnote,
	                // it's a *; otherwise it's a superscript number
	                text: id === 1 ? "*" : ("" + id)
	            };

	            // If the previous footnote was a *, we need to adjust it to be
	            // a number, since now we know there is more than one footnote
	            if (state.lastFootnote.text === "*") {
	                state.lastFootnote.text = "" + state.lastFootnote.id;
	            }

	            // and update our last footnote, + return.
	            state.lastFootnote = footnote;
	            return footnote;
	        },
	        output: function(node, output)  {
	            return React.createElement("sup", null, node.text);
	        }
	    },
	    squareLabel: {
	        regex: /^\[\[(\w+)\]\]( *)/,
	        parse: function(capture, parse, state)  {
	            return {
	                content: parse(capture[1]),
	                space: capture[2].length > 0,
	            }
	        },
	        output: function(node, output)  {
	            return [
	                React.createElement("span", {style: LABEL_OUTER_STYLE}, 
	                    React.createElement("span", {style: SQUARE_LABEL_STYLE}, 
	                        output(node.content)
	                    )
	                ),
	                (node.space ? "\u00A0" : null)
	            ];
	        }
	    },
	    circleLabel: {
	        regex: /^\(\((\w+)\)\)( *)/,
	        parse: function(capture, parse, state)  {
	            return {
	                content: parse(capture[1]),
	                space: capture[2].length > 0,
	            }
	        },
	        output: function(node, output)  {
	            return [
	                React.createElement("span", {style: LABEL_OUTER_STYLE}, 
	                    React.createElement("span", {style: CIRCLE_LABEL_STYLE}, 
	                        output(node.content)
	                    )
	                ),
	                (node.space ? "\u00A0" : null)
	            ];
	        }
	    },
	    refStart: {
	        regex: /^\{\{/,
	        parse: function(capture, parse, state)  {
	            var ref = state.lastRef + 1;
	            state.lastRef = ref;
	            state.currentRef.push(ref);
	            return {
	                ref: ref
	            };
	        },
	        output: function(node, output)  {
	            return React.createElement("span", {
	                    ref: START_REF_PREFIX + node.ref, 
	                    style: REF_STYLE}, 
	                "_"
	            );
	        }
	    },
	    refEnd: {
	        regex: /^\}\}/,
	        parse: function(capture, parse, state)  {
	            var ref = state.currentRef.pop() || null;
	            return {
	                ref: ref
	            };
	        },
	        output: function(node, output)  {
	            if (node.ref != null) {
	                return React.createElement("span", {
	                        ref: END_REF_PREFIX + node.ref, 
	                        style: REF_STYLE}, 
	                    "_"
	                );
	            } else {
	                // if we didn't have a matching start reference, don't output
	                // a ref
	                return React.createElement("span", {style: REF_STYLE}, 
	                    "_"
	                );
	            }
	        }
	    }
	});

	var priorities = [
	    "paragraph",
	    "escape",
	    "passageFootnote",
	    "refStart",
	    "refEnd",
	    "squareLabel",
	    "circleLabel",
	    "strong",
	    "u",
	    "em",
	    "del",
	    "newline",
	    "text"
	];

	var builtParser = SimpleMarkdown.parserFor(rules, priorities);
	var parse = function(source)  {
	    var paragraphedSource = source + "\n\n";
	    return builtParser(paragraphedSource, {
	        currentRef: [],
	        lastRef: 0,
	        lastFootnote: {id: 0, text: ""}
	    });
	};

	module.exports = {
	    parse: parse,
	    output: SimpleMarkdown.outputFor(SimpleMarkdown.ruleOutput(rules)),
	    START_REF_PREFIX: START_REF_PREFIX,
	    END_REF_PREFIX: END_REF_PREFIX
	};


/***/ },
/* 102 */
/***/ function(module, exports, __webpack_require__) {

	/*
	Software License Agreement (BSD License)

	Copyright (c) 2009-2011, Kevin Decker <kpdecker@gmail.com>

	All rights reserved.

	Redistribution and use of this software in source and binary forms, with or without modification,
	are permitted provided that the following conditions are met:

	* Redistributions of source code must retain the above
	  copyright notice, this list of conditions and the
	  following disclaimer.

	* Redistributions in binary form must reproduce the above
	  copyright notice, this list of conditions and the
	  following disclaimer in the documentation and/or other
	  materials provided with the distribution.

	* Neither the name of Kevin Decker nor the names of its
	  contributors may be used to endorse or promote products
	  derived from this software without specific prior
	  written permission.

	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
	IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
	FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
	CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
	DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
	DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
	IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
	OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
	*/

	/*
	 * Text diff implementation.
	 *
	 * This library supports the following APIS:
	 * JsDiff.diffChars: Character by character diff
	 * JsDiff.diffWords: Word (as defined by \b regex) diff which ignores whitespace
	 * JsDiff.diffLines: Line based diff
	 *
	 * JsDiff.diffCss: Diff targeted at CSS content
	 *
	 * These methods are based on the implementation proposed in
	 * "An O(ND) Difference Algorithm and its Variations" (Myers, 1986).
	 * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.4.6927
	 */
	var JsDiff = (function() {
	  /*jshint maxparams: 5*/
	  function clonePath(path) {
	    return { newPos: path.newPos, components: path.components.slice(0) };
	  }
	  function removeEmpty(array) {
	    var ret = [];
	    for (var i = 0; i < array.length; i++) {
	      if (array[i]) {
	        ret.push(array[i]);
	      }
	    }
	    return ret;
	  }
	  function escapeHTML(s) {
	    var n = s;
	    n = n.replace(/&/g, '&amp;');
	    n = n.replace(/</g, '&lt;');
	    n = n.replace(/>/g, '&gt;');
	    n = n.replace(/"/g, '&quot;');

	    return n;
	  }

	  var Diff = function(ignoreWhitespace) {
	    this.ignoreWhitespace = ignoreWhitespace;
	  };
	  Diff.prototype = {
	      diff: function(oldString, newString) {
	        // Handle the identity case (this is due to unrolling editLength == 0
	        if (newString === oldString) {
	          return [{ value: newString }];
	        }
	        if (!newString) {
	          return [{ value: oldString, removed: true }];
	        }
	        if (!oldString) {
	          return [{ value: newString, added: true }];
	        }

	        newString = this.tokenize(newString);
	        oldString = this.tokenize(oldString);

	        var newLen = newString.length, oldLen = oldString.length;
	        var maxEditLength = newLen + oldLen;
	        var bestPath = [{ newPos: -1, components: [] }];

	        // Seed editLength = 0
	        var oldPos = this.extractCommon(bestPath[0], newString, oldString, 0);
	        if (bestPath[0].newPos+1 >= newLen && oldPos+1 >= oldLen) {
	          return bestPath[0].components;
	        }

	        for (var editLength = 1; editLength <= maxEditLength; editLength++) {
	          for (var diagonalPath = -1*editLength; diagonalPath <= editLength; diagonalPath+=2) {
	            var basePath;
	            var addPath = bestPath[diagonalPath-1],
	                removePath = bestPath[diagonalPath+1];
	            oldPos = (removePath ? removePath.newPos : 0) - diagonalPath;
	            if (addPath) {
	              // No one else is going to attempt to use this value, clear it
	              bestPath[diagonalPath-1] = undefined;
	            }

	            var canAdd = addPath && addPath.newPos+1 < newLen;
	            var canRemove = removePath && 0 <= oldPos && oldPos < oldLen;
	            if (!canAdd && !canRemove) {
	              bestPath[diagonalPath] = undefined;
	              continue;
	            }

	            // Select the diagonal that we want to branch from. We select the prior
	            // path whose position in the new string is the farthest from the origin
	            // and does not pass the bounds of the diff graph
	            if (!canAdd || (canRemove && addPath.newPos < removePath.newPos)) {
	              basePath = clonePath(removePath);
	              this.pushComponent(basePath.components, oldString[oldPos], undefined, true);
	            } else {
	              basePath = clonePath(addPath);
	              basePath.newPos++;
	              this.pushComponent(basePath.components, newString[basePath.newPos], true, undefined);
	            }

	            var oldPos = this.extractCommon(basePath, newString, oldString, diagonalPath);

	            if (basePath.newPos+1 >= newLen && oldPos+1 >= oldLen) {
	              return basePath.components;
	            } else {
	              bestPath[diagonalPath] = basePath;
	            }
	          }
	        }
	      },

	      pushComponent: function(components, value, added, removed) {
	        var last = components[components.length-1];
	        if (last && last.added === added && last.removed === removed) {
	          // We need to clone here as the component clone operation is just
	          // as shallow array clone
	          components[components.length-1] =
	            {value: this.join(last.value, value), added: added, removed: removed };
	        } else {
	          components.push({value: value, added: added, removed: removed });
	        }
	      },
	      extractCommon: function(basePath, newString, oldString, diagonalPath) {
	        var newLen = newString.length,
	            oldLen = oldString.length,
	            newPos = basePath.newPos,
	            oldPos = newPos - diagonalPath;
	        while (newPos+1 < newLen && oldPos+1 < oldLen && this.equals(newString[newPos+1], oldString[oldPos+1])) {
	          newPos++;
	          oldPos++;

	          this.pushComponent(basePath.components, newString[newPos], undefined, undefined);
	        }
	        basePath.newPos = newPos;
	        return oldPos;
	      },

	      equals: function(left, right) {
	        var reWhitespace = /\S/;
	        if (this.ignoreWhitespace && !reWhitespace.test(left) && !reWhitespace.test(right)) {
	          return true;
	        } else {
	          return left === right;
	        }
	      },
	      join: function(left, right) {
	        return left + right;
	      },
	      tokenize: function(value) {
	        return value;
	      }
	  };

	  var CharDiff = new Diff();

	  var WordDiff = new Diff(true);
	  var WordWithSpaceDiff = new Diff();
	  WordDiff.tokenize = WordWithSpaceDiff.tokenize = function(value) {
	    return removeEmpty(value.split(/(\s+|\b)/));
	  };

	  var CssDiff = new Diff(true);
	  CssDiff.tokenize = function(value) {
	    return removeEmpty(value.split(/([{}:;,]|\s+)/));
	  };

	  var LineDiff = new Diff();
	  LineDiff.tokenize = function(value) {
	    var retLines = [],
	        lines = value.split(/^/m);

	    for(var i = 0; i < lines.length; i++) {
	      var line = lines[i],
	          lastLine = lines[i - 1];

	      // Merge lines that may contain windows new lines
	      if (line == '\n' && lastLine && lastLine[lastLine.length - 1] === '\r') {
	        retLines[retLines.length - 1] += '\n';
	      } else if (line) {
	        retLines.push(line);
	      }
	    }

	    return retLines;
	  };

	  return {
	    Diff: Diff,

	    diffChars: function(oldStr, newStr) { return CharDiff.diff(oldStr, newStr); },
	    diffWords: function(oldStr, newStr) { return WordDiff.diff(oldStr, newStr); },
	    diffWordsWithSpace: function(oldStr, newStr) { return WordWithSpaceDiff.diff(oldStr, newStr); },
	    diffLines: function(oldStr, newStr) { return LineDiff.diff(oldStr, newStr); },

	    diffCss: function(oldStr, newStr) { return CssDiff.diff(oldStr, newStr); },

	    createPatch: function(fileName, oldStr, newStr, oldHeader, newHeader) {
	      var ret = [];

	      ret.push('Index: ' + fileName);
	      ret.push('===================================================================');
	      ret.push('--- ' + fileName + (typeof oldHeader === 'undefined' ? '' : '\t' + oldHeader));
	      ret.push('+++ ' + fileName + (typeof newHeader === 'undefined' ? '' : '\t' + newHeader));

	      var diff = LineDiff.diff(oldStr, newStr);
	      if (!diff[diff.length-1].value) {
	        diff.pop();   // Remove trailing newline add
	      }
	      diff.push({value: '', lines: []});   // Append an empty value to make cleanup easier

	      function contextLines(lines) {
	        return lines.map(function(entry) { return ' ' + entry; });
	      }
	      function eofNL(curRange, i, current) {
	        var last = diff[diff.length-2],
	            isLast = i === diff.length-2,
	            isLastOfType = i === diff.length-3 && (current.added !== last.added || current.removed !== last.removed);

	        // Figure out if this is the last line for the given file and missing NL
	        if (!/\n$/.test(current.value) && (isLast || isLastOfType)) {
	          curRange.push('\\ No newline at end of file');
	        }
	      }

	      var oldRangeStart = 0, newRangeStart = 0, curRange = [],
	          oldLine = 1, newLine = 1;
	      for (var i = 0; i < diff.length; i++) {
	        var current = diff[i],
	            lines = current.lines || current.value.replace(/\n$/, '').split('\n');
	        current.lines = lines;

	        if (current.added || current.removed) {
	          if (!oldRangeStart) {
	            var prev = diff[i-1];
	            oldRangeStart = oldLine;
	            newRangeStart = newLine;

	            if (prev) {
	              curRange = contextLines(prev.lines.slice(-4));
	              oldRangeStart -= curRange.length;
	              newRangeStart -= curRange.length;
	            }
	          }
	          curRange.push.apply(curRange, lines.map(function(entry) { return (current.added?'+':'-') + entry; }));
	          eofNL(curRange, i, current);

	          if (current.added) {
	            newLine += lines.length;
	          } else {
	            oldLine += lines.length;
	          }
	        } else {
	          if (oldRangeStart) {
	            // Close out any changes that have been output (or join overlapping)
	            if (lines.length <= 8 && i < diff.length-2) {
	              // Overlapping
	              curRange.push.apply(curRange, contextLines(lines));
	            } else {
	              // end the range and output
	              var contextSize = Math.min(lines.length, 4);
	              ret.push(
	                  '@@ -' + oldRangeStart + ',' + (oldLine-oldRangeStart+contextSize)
	                  + ' +' + newRangeStart + ',' + (newLine-newRangeStart+contextSize)
	                  + ' @@');
	              ret.push.apply(ret, curRange);
	              ret.push.apply(ret, contextLines(lines.slice(0, contextSize)));
	              if (lines.length <= 4) {
	                eofNL(ret, i, current);
	              }

	              oldRangeStart = 0;  newRangeStart = 0; curRange = [];
	            }
	          }
	          oldLine += lines.length;
	          newLine += lines.length;
	        }
	      }

	      return ret.join('\n') + '\n';
	    },

	    applyPatch: function(oldStr, uniDiff) {
	      var diffstr = uniDiff.split('\n');
	      var diff = [];
	      var remEOFNL = false,
	          addEOFNL = false;

	      for (var i = (diffstr[0][0]==='I'?4:0); i < diffstr.length; i++) {
	        if(diffstr[i][0] === '@') {
	          var meh = diffstr[i].split(/@@ -(\d+),(\d+) \+(\d+),(\d+) @@/);
	          diff.unshift({
	            start:meh[3],
	            oldlength:meh[2],
	            oldlines:[],
	            newlength:meh[4],
	            newlines:[]
	          });
	        } else if(diffstr[i][0] === '+') {
	          diff[0].newlines.push(diffstr[i].substr(1));
	        } else if(diffstr[i][0] === '-') {
	          diff[0].oldlines.push(diffstr[i].substr(1));
	        } else if(diffstr[i][0] === ' ') {
	          diff[0].newlines.push(diffstr[i].substr(1));
	          diff[0].oldlines.push(diffstr[i].substr(1));
	        } else if(diffstr[i][0] === '\\') {
	          if (diffstr[i-1][0] === '+') {
	            remEOFNL = true;
	          } else if(diffstr[i-1][0] === '-') {
	            addEOFNL = true;
	          }
	        }
	      }

	      var str = oldStr.split('\n');
	      for (var i = diff.length - 1; i >= 0; i--) {
	        var d = diff[i];
	        for (var j = 0; j < d.oldlength; j++) {
	          if(str[d.start-1+j] !== d.oldlines[j]) {
	            return false;
	          }
	        }
	        Array.prototype.splice.apply(str,[d.start-1,+d.oldlength].concat(d.newlines));
	      }

	      if (remEOFNL) {
	        while (!str[str.length-1]) {
	          str.pop();
	        }
	      } else if (addEOFNL) {
	        str.push('');
	      }
	      return str.join('\n');
	    },

	    convertChangesToXML: function(changes){
	      var ret = [];
	      for ( var i = 0; i < changes.length; i++) {
	        var change = changes[i];
	        if (change.added) {
	          ret.push('<ins>');
	        } else if (change.removed) {
	          ret.push('<del>');
	        }

	        ret.push(escapeHTML(change.value));

	        if (change.added) {
	          ret.push('</ins>');
	        } else if (change.removed) {
	          ret.push('</del>');
	        }
	      }
	      return ret.join('');
	    },

	    // See: http://code.google.com/p/google-diff-match-patch/wiki/API
	    convertChangesToDMP: function(changes){
	      var ret = [], change;
	      for ( var i = 0; i < changes.length; i++) {
	        change = changes[i];
	        ret.push([(change.added ? 1 : change.removed ? -1 : 0), change.value]);
	      }
	      return ret;
	    }
	  };
	})();

	if (true) {
	    module.exports = JsDiff;
	}


/***/ },
/* 103 */
/***/ function(module, exports, __webpack_require__) {

	module.exports = {
	    number: __webpack_require__(115),
	    vector: __webpack_require__(116),
	    point: __webpack_require__(117),
	    line: __webpack_require__(118),
	    ray: __webpack_require__(119),
	};


/***/ },
/* 104 */
/***/ function(module, exports, __webpack_require__) {

	/* TODO batch *all* mutations
	 * idea: freeze / thaw implementations for all types
	 * lens constructor thaws, freeze delegates to type's freeze
	 */

	var util = __webpack_require__(120);
	    var clone = util.clone;
	    var isObject = util.isObject;
	    var merge = util.merge;

	var arr = __webpack_require__(121);
	var obj = __webpack_require__(122);
	var str = __webpack_require__(123);

	// equivalents, without requiring it
	// find the implementation to use for a given object
	var dispatch = function(x) {
	    if (Array.isArray(x)) {
	        return arr;
	    } else if (isObject(x)) {
	        return obj;
	    } else if (typeof x === "string") {
	        return str;
	    }
	};

	// This is underscore with a different name
	var lens = function(obj) {
	    if (obj instanceof lens) {
	        return obj;
	    }

	    if (!(this instanceof lens)) {
	        return new lens(obj);
	    }

	    var ops = dispatch(obj);
	    this._wrapped = ops.thaw ? ops.thaw(obj) : obj;
	};

	lens.prototype.freeze = function() {
	    var obj = this._wrapped;
	    var ops = dispatch(obj);

	    return ops.freeze ? ops.freeze(obj) : obj;
	};

	lens.prototype.zoom = function(lensArr) {
	    if (this._zoomStack === undefined) {
	        this._zoomStack = [];
	    }

	    this._zoomStack.push({
	        zoom: lensArr,
	        wrapped: this._wrapped
	    });
	    this._wrapped = lens(this._wrapped).get(lensArr);

	    return this;
	};

	lens.prototype.deZoom = function() {
	    var frame = this._zoomStack.pop();
	    this._wrapped = lens(frame.wrapped)
	        .set(frame.zoom, this._wrapped)
	        .freeze();

	    return this;
	};

	lens.prototype.get = function(lensArr) {
	    var obj = this._wrapped;

	    for (var i = 0; i < lensArr.length; i++) {
	        obj = dispatch(obj).get(obj, lensArr[i]);
	    }

	    return obj;
	};

	lens.prototype.mod = function(lensArr, f) {
	    var obj = this._wrapped;
	    var newObj = clone(obj);
	    var ops = dispatch(obj);

	    if (lensArr.length === 0) {
	        this._wrapped = f(this._wrapped);
	    } else if (lensArr.length === 1) {
	        this._wrapped = ops.mod(newObj, lensArr[0], f);
	    } else {
	        var monocle = lensArr[0];
	        var shortLens = lensArr.slice(1);

	        // newObj = ops.mod(obj[monocle], shortLens, f);

	        newObj[monocle] = lens(obj[monocle])
	            .mod(shortLens, f)
	            .freeze();
	        this._wrapped = newObj;
	    }

	    return this;
	};

	// TODO - move to individual files
	lens.prototype.merge = function(lensArr, props) {
	    this._wrapped = lens(this._wrapped).mod(lensArr, function(oldProps) {
	        return merge(oldProps, props);
	    }).freeze();

	    return this;
	};

	// Lens must have length >= 1 or there would be nothing to return
	lens.prototype.del = function(lensArr) {
	    var obj = this._wrapped;
	    var ops = dispatch(obj);

	    if (lensArr.length === 1) {
	        this._wrapped = ops.del(obj, lensArr[0]);
	    } else {
	        var monocle = lensArr[0];
	        var shortLens = lensArr.slice(1);
	        var subObj = ops.get(obj, monocle);

	        this._wrapped = ops.set(obj, monocle, ops.del(subObj, shortLens));
	    }

	    return this;
	};

	lens.prototype.set = function(lensArr, set) {
	    return this.mod(lensArr, function() { return set; });
	};

	module.exports = lens;


/***/ },
/* 105 */
/***/ function(module, exports, __webpack_require__) {

	var Util = __webpack_require__(3);
	var nestedMap = Util.nestedMap;
	var deepEq = Util.deepEq;
	var _ = __webpack_require__(60);

	/**
	 * A base class for all Graphie Movables
	 *
	 * Used for checking that all Graphie children are, in fact,
	 * GraphieMovables
	 */
	function GraphieMovable(descriptor) {
	    _.extend(this, descriptor);
	}

	var abstractMethod = function() {
	    throw new Error("Abstract method! Must be implemented by Graphie Movable" +
	            this.constructor.displayName);
	};

	_.extend(GraphieMovable.prototype, {
	    movableProps: [],
	    add: abstractMethod,
	    modify: abstractMethod,
	    remove: abstractMethod,
	    toFront: function() { /* no op */ }
	});


	/**
	 * returns cloned props modified with `children: childrenArray`
	 */
	var rewriteProps = function(props, childrenArray) {
	    // Clone the props and add `children:`
	    // childrenArray is always an array here because this is only called
	    // from createClass, which initializes childrenArray as _.rest(arguments)
	    return _.extend({}, props, {
	        children: _.filter(_.flatten(childrenArray), _.identity)
	    });
	};


	/**
	 * Create a custom GraphieMovable class
	 */
	var createClass = function(spec) {
	    var GraphieClass = function(descriptor) {
	        this.props = rewriteProps(descriptor.props, descriptor.props.children);
	        return this;
	    };

	    spec.displayName = spec.displayName || _.uniqueId("GraphieClass");

	    // Add the displayName to the constructor for compatibility with
	    // React's myDescriptor.constructor.displayName
	    GraphieClass.displayName = spec.displayName;

	    GraphieClass.prototype = new GraphieMovable(spec);
	    GraphieClass.prototype.constructor = GraphieClass;

	    var factory = function(config) {
	        // TODO(alpert): Remove after 0.12 -- adapted from 
	        // https://github.com/facebook/react/blob/af485d9/src/core/ReactDescriptor.js#L136-L188
	        var props = {};
	        var key = null;
	        var ref = null;
	        var propName;
	        if (config != null) {
	            key = config.key === undefined ? null : '' + config.key;
	            ref = config.ref === undefined ? null : config.ref;
	            for (propName in config) {
	                if (config.hasOwnProperty(propName) &&
	                        propName !== "key" &&
	                        propName !== "ref") {
	                    props[propName] = config[propName];
	                }
	            }
	        }
	        props.children = _.rest(arguments);
	        return {
	            type: GraphieClass,
	            key: key,
	            ref: ref,
	            props: props,
	        };
	    };

	    // TODO(alpert): This is present to trick React.createElement into
	    // believing that graphie movable classes are valid React types. In React
	    // 0.13 (probably) createElement will probably just assume that its
	    // argument is a proper type and we'll be able to get rid of this.
	    factory.isReactLegacyFactory = true;
	    factory.type = GraphieClass;

	    return factory;
	};


	/**
	 * Create a GraphieMovable class from a function that describes
	 * how to add said class to a graphie, and returns an array of
	 * `.remove()`able elements to be used when a remove() or
	 * modify() is called.
	 *
	 * This convenience method creates an inefficient class, although
	 * it does check for a difference in this.props and prevProps before
	 * removing and re-adding itself.
	 *
	 * The primary benefit of this is being able to very easily create
	 * a wrapper for old graphie code to make it interface with <Graphie>
	 *
	 * Commonly used elements should use the fully-fledged createClass
	 * and implement an efficient modify() operation.
	 */
	var createSimpleClass = function(addFunction) {
	    return createClass({
	        displayName: addFunction.name || _.uniqueId("GraphieSimpleClass"),
	        movableProps: ["children"],

	        add: function(graphie) {
	            this._elements = addFunction(graphie, this.props);
	            this._prevProps = this.props;
	        },

	        modify: function(graphie) {
	            if (!deepEq(this.props, this._prevProps)) {
	                this.remove();
	                this.add(graphie);
	                this._prevProps = this.props;
	                return "reordered";
	            }
	        },

	        remove: function() {
	            nestedMap(this._elements, function(elem)  {
	                if (elem) {
	                    elem.remove();
	                }
	            });
	            this._elements = null;
	            this._prevProps = null;
	        },

	        toFront: function() {
	            nestedMap(this._elements, function(elem)  {
	                if (_.isFunction(elem.toFront)) {
	                    elem.toFront();
	                }
	            });
	        }
	    });
	};


	module.exports = {
	    GraphieMovable: GraphieMovable,
	    createClass: createClass,
	    createSimpleClass: createSimpleClass
	};


/***/ },
/* 106 */
/***/ function(module, exports, __webpack_require__) {

	var _ = __webpack_require__(60);
	var GraphieClasses = __webpack_require__(105);
	var Interactive2 = __webpack_require__(80);
	var InteractiveUtil = __webpack_require__(89);

	var assert = InteractiveUtil.assert;

	var MovablePoint = GraphieClasses.createClass({
	    displayName: "MovablePoint",

	    movableProps: ["children"],

	    add: function(graphie) {
	        this.point = Interactive2.addMovablePoint(graphie, this.props);
	    },

	    modify: function() {
	        this.point.modify(this.props);
	    },

	    remove: function() {
	        this.point.remove();
	    },

	    toFront: function() {
	        this.point.toFront();
	    },

	    grab: function(coord) {
	        this.point.grab(coord);
	    }
	});

	// Include helper methods, such as MovablePoint.constrain.snap()
	_.extend(MovablePoint, Interactive2.MovablePoint);

	var MovableLine = GraphieClasses.createClass({
	    displayName: "MovableLine",

	    movableProps: ["children"],

	    add: function(graphie) {
	        // Add MovablePoint children
	        var points = _.pluck(this.props.children, "point");
	        var props = _.extend({}, this.props, {
	            points: points
	        });
	        this.line = Interactive2.addMovableLine(graphie, props);
	    },

	    modify: function() {
	        // Add MovablePoint children
	        var points = _.pluck(this.props.children, "point");
	        var props = _.extend({}, this.props, {
	            points: points
	        });
	        this.line.modify(props);
	    },

	    remove: function() {
	        this.line.remove();
	    },

	    toFront: function() {
	        this.line.toFront();
	    }
	});

	// Include helper methods, such as MovableLine.constrain.snap()
	_.extend(MovableLine, Interactive2.MovableLine);

	var Label = GraphieClasses.createSimpleClass(function(graphie, props)  {
	    var coord = props.coord;
	    if (props.unscaled) {
	        coord = graphie.unscalePoint(coord);
	    }

	    return graphie.label(
	        coord,
	        props.text,
	        props.direction,
	        props.tex,
	        props.style
	    );
	});

	var Line = GraphieClasses.createClass({
	    displayName: "Line",

	    movableProps: ["children"],

	    add: function(graphie) {
	        var props = this.props;
	        this.graphie = graphie;
	        this.line = this.graphie.line(props.start, props.end, props.style);
	    },

	    modify: function() {
	        var props = this.props;
	        var path = this.graphie.svgPath([props.start, props.end]);
	        this.line.attr(_.extend({}, props.style, { path: path }));
	    },

	    remove: function() {
	        this.line.remove();
	    },

	    toFront: function() {
	        this.line.toFront();
	    }
	});

	var Parabola = GraphieClasses.createClass({
	    displayName: "Parabola",

	    movableProps: ["children"],

	    add: function(graphie) {
	        var props = this.props;
	        this.graphie = graphie;
	        this.parabola = this.graphie.parabola(props.a, props.b, props.c,
	            props.style);
	    },

	    modify: function() {
	        var props = this.props;
	        var path = this.graphie.svgParabolaPath(props.a, props.b, props.c);
	        this.parabola.attr(_.extend({}, props.style, { path: path }));
	    },

	    remove: function() {
	        this.parabola.remove();
	    },

	    toFront: function() {
	        this.parabola.toFront();
	    }
	});

	var Sinusoid = GraphieClasses.createClass({
	    displayName: "Sinusoid",

	    movableProps: ["children"],

	    add: function(graphie) {
	        var props = this.props;
	        this.graphie = graphie;
	        this.sinusoid = this.graphie.sinusoid(props.a, props.b, props.c,
	            props.d, props.style);
	    },

	    modify: function() {
	        var props = this.props;
	        var path = this.graphie.svgSinusoidPath(props.a, props.b, props.c,
	            props.d);
	        this.sinusoid.attr(_.extend({}, props.style, { path: path }));
	    },

	    remove: function() {
	        this.sinusoid.remove();
	    },

	    toFront: function() {
	        this.sinusoid.toFront();
	    }
	});

	var Plot = GraphieClasses.createSimpleClass(function(graphie, props)  {
	    return graphie.plot(props.fn, props.range, props.style);
	});

	var PlotParametric = GraphieClasses.createSimpleClass(function(graphie, props)  {
	    return graphie.plotParametric(props.fn, props.range, props.style);
	});

	var Point = GraphieClasses.createSimpleClass(function(graphie, props)  {
	    return graphie.ellipse(props.coord, graphie.unscaleVector([4, 4]), {
	        fill: props.color || KhanUtil.BLACK,
	        stroke: props.color || KhanUtil.BLACK,
	    });
	});

	var Path = GraphieClasses.createClass({
	    displayName: "Path",

	    movableProps: ["children"],

	    add: function(graphie) {
	        var props = this.props;
	        this.graphie = graphie;
	        this.path = this.graphie.path(props.coords, props.style);
	    },

	    modify: function() {
	        var props = this.props;
	        var path = this.graphie.svgPath(props.coords);
	        this.path.attr({ path: path });
	    },

	    remove: function() {
	        this.path.remove();
	    },

	    toFront: function() {
	        this.path.toFront();
	    }
	});

	var Arc = GraphieClasses.createSimpleClass(function(graphie, props)  {
	    var center = props.center;
	    var radius = props.radius;
	    if (props.unscaled) {
	        center = graphie.unscalePoint(center);
	        radius = graphie.unscaleVector(radius);
	    }

	    return graphie.arc(
	        center,
	        radius,
	        props.startAngle,
	        props.endAngle,
	        props.sector,
	        props.style
	    );
	});

	var Circle = GraphieClasses.createSimpleClass(function(graphie, props)  {
	    return graphie.circle(
	        props.center,
	        props.radius,
	        props.style
	    );
	});

	var Rect = GraphieClasses.createSimpleClass(function(graphie, props)  {
	    return graphie.rect(
	        props.x, props.y, props.width, props.height, props.style);
	});

	module.exports = {
	    Arc: Arc,
	    Circle: Circle,
	    Label: Label,
	    Line: Line,
	    MovableLine: MovableLine,
	    MovablePoint: MovablePoint,
	    Parabola: Parabola,
	    Path: Path,
	    Plot: Plot,
	    PlotParametric: PlotParametric,
	    Point: Point,
	    Sinusoid: Sinusoid,
	    Rect: Rect
	};


/***/ },
/* 107 */
/***/ function(module, exports, __webpack_require__) {

	var button = {
	    buttonStyle: {
	        backgroundColor: 'white',
	        border: '1px solid #ccc',
	        borderLeft: '0',
	        cursor: 'pointer',
	        margin: '0',
	        padding: '5px 10px',
	        position: 'relative', // for hover

	        ':first-child': {
	            borderLeft: '1px solid #ccc',
	            borderTopLeftRadius: '3px',
	            borderBottomLeftRadius: '3px'
	        },

	        ':last-child': {
	            borderRight: '1px solid #ccc',
	            borderTopRightRadius: '3px',
	            borderBottomRightRadius: '3px'
	        },

	        ':hover': {
	            backgroundColor: '#ccc'
	        },

	        ':focus': {
	            zIndex: '2'
	        }
	    },

	    selectedStyle: {
	        backgroundColor: '#ddd'
	    }
	};

	module.exports = {
	    button: button
	};


/***/ },
/* 108 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Movable
	 *
	 * A Movable Something, that sends onMove events based on the
	 * mouse coordinate (graphie unscaled, non-pixel-value) of the
	 * move.
	 *
	 * Other MovableThings should generally have a Movable field, and
	 * let this class handle all of the virtual mouse events, and then
	 * take appropriate action in onMoveStart, onMove, onMoveEnd
	 */
	var _ = __webpack_require__(60);

	var InteractiveUtil = __webpack_require__(89);
	var normalizeOptions = InteractiveUtil.normalizeOptions;

	var assert = InteractiveUtil.assert;
	var kpoint = __webpack_require__(103).point;

	// state parameters that should be converted into an array of
	// functions
	var FUNCTION_ARRAY_OPTIONS = [
	    "add",
	    "modify",
	    "draw",
	    "remove",
	    "onMoveStart",
	    "onMove",
	    "onMoveEnd",
	    "onClick"
	];

	// Default "props" and "state". Both are added to this.state and
	// receive magic getter methods (this.isHovering() etc).
	// However, properties in DEFAULT_PROPS are updated on `modify()`,
	// while those in DEFAULT_STATE persist and are not updated.
	// Things that the user might want to change should be on "props",
	// while things used to render the movable should be on "state".
	var DEFAULT_PROPS = {
	    cursor: null
	};
	var DEFAULT_STATE = {
	    added: false,
	    isHovering: false,
	    isMouseOver: false,
	    isDragging: false,
	    mouseTarget: null
	};

	var Movable = function(graphie, options) {
	    _.extend(this, {
	        graphie: graphie,
	        state: {
	            // Set here because this must be unique for each instance
	            id: _.uniqueId("movable")
	        }
	    });

	    // We only set DEFAULT_STATE once, here
	    this.modify(_.extend({}, DEFAULT_STATE, options));
	};

	InteractiveUtil.createGettersFor(Movable, _.extend({},
	    DEFAULT_PROPS,
	    DEFAULT_STATE
	));
	InteractiveUtil.addMovableHelperMethodsTo(Movable);

	_.extend(Movable.prototype, {

	    cloneState: function() {
	        return _.clone(this.state);
	    },

	    _createDefaultState: function() {
	        return _.extend({
	            id: this.state.id,
	            add: [],
	            modify: [],
	            draw: [],
	            remove: [],
	            onMoveStart: [],
	            onMove: [],
	            onMoveEnd: [],
	            onClick: []

	        // We only update props here, because we want things on state to
	        // be persistent, and updated appropriately in modify()
	        }, DEFAULT_PROPS);
	    },

	    /**
	     * Resets the object to its state as if it were constructed with
	     * `options` originally. The only state maintained is `state.id`
	     *
	     * Analogous to React.js's replaceProps
	     */
	    modify: function(options) {
	        this.update(_.extend({}, this._createDefaultState(), options));
	    },

	    /**
	     * Simulates a mouse grab event on the movable object.
	     */
	    grab: function(coord) {
	        assert(kpoint.is(coord));
	        var self = this;
	        var graphie = self.graphie;
	        var state = self.state;

	        state.isHovering = true;
	        state.isDragging = true;
	        graphie.isDragging = true;

	        var startMouseCoord = coord;
	        var prevMouseCoord = startMouseCoord;
	        self._fireEvent(
	            state.onMoveStart,
	            startMouseCoord,
	            startMouseCoord
	        );

	        var moveHandler = function(e) {
	            e.preventDefault();

	            var mouseCoord = graphie.getMouseCoord(e);
	            self._fireEvent(
	                state.onMove,
	                mouseCoord,
	                prevMouseCoord
	            );
	            self.draw();
	            prevMouseCoord = mouseCoord;
	        };

	        var upHandler = function(e) {
	            $(document).unbind("vmousemove", moveHandler);
	            $(document).unbind("vmouseup", upHandler);
	            if (state.isHovering) {
	                self._fireEvent(
	                    state.onClick,
	                    prevMouseCoord,
	                    startMouseCoord
	                );
	            }
	            state.isHovering = self.state.isMouseOver;
	            state.isDragging = false;
	            graphie.isDragging = false;
	            self._fireEvent(
	                state.onMoveEnd,
	                prevMouseCoord,
	                startMouseCoord
	            );
	            self.draw();
	        };

	        $(document).bind("vmousemove", moveHandler);
	        $(document).bind("vmouseup", upHandler);
	    },

	    /**
	     * Adjusts constructor parameters without changing previous settings
	     * for any option not specified
	     *
	     * Analogous to React.js's setProps
	     */
	    update: function(options) {
	        var self = this;
	        var graphie = self.graphie;

	        var prevState = self.cloneState();
	        var state = _.extend(
	            self.state,
	            normalizeOptions(FUNCTION_ARRAY_OPTIONS, options)
	        );

	        // the invisible shape in front of the point that gets mouse events
	        if (state.mouseTarget && !prevState.mouseTarget) {
	            var $mouseTarget;
	            if (state.mouseTarget.getMouseTarget) {
	                $mouseTarget = $(state.mouseTarget.getMouseTarget());
	            } else {
	                $mouseTarget = $(state.mouseTarget[0]);
	            }

	            var isMouse = !('ontouchstart' in window);

	            if (isMouse) {
	                $mouseTarget.on("vmouseover", function() {
	                    state.isMouseOver = true;
	                    if (!graphie.isDragging) {
	                        state.isHovering = true;
	                    }
	                    if (self.state.added) {
	                        // Avoid drawing if the point has been removed
	                        self.draw();
	                    }
	                });

	                $mouseTarget.on("vmouseout", function() {
	                    state.isMouseOver = false;
	                    if (!state.isDragging) {
	                        state.isHovering = false;
	                    }
	                    if (self.state.added) {
	                        // Avoid drawing if the point has been removed
	                        self.draw();
	                    }
	                });
	            }

	            $mouseTarget.on("vmousedown", function(e) {
	                if (e.which !== 0 && e.which !== 1) {
	                    return;
	                }
	                e.preventDefault();

	                var mouseCoord = graphie.getMouseCoord(e);
	                self.grab(mouseCoord);
	            });
	        }

	        if (state.mouseTarget && state.cursor !== undefined) {
	            var $mouseTarget;
	            if (state.mouseTarget.getMouseTarget) {
	                $mouseTarget = $(state.mouseTarget.getMouseTarget());
	            } else {
	                $mouseTarget = $(state.mouseTarget[0]);
	            }

	            // "" removes the css cursor if state.cursor is null
	            $mouseTarget.css("cursor", state.cursor || "");
	        }


	        // Trigger an add event if this hasn't been added before
	        if (!state.added) {
	            self._fireEvent(state.modify, self.cloneState(), {});
	            state.added = true;

	            // Update the state for `added` and in case the add event
	            // changed it
	            self.prevState = self.cloneState();
	        }

	        // Trigger a modify event
	        self._fireEvent(state.modify, self.cloneState(), self.prevState);
	    },

	    remove: function() {
	        this.state.added = false;
	        this._fireEvent(this.state.remove);
	        if (this.state.mouseTarget) {
	            $(this.state.mouseTarget).off();
	            this.state.mouseTarget.remove();
	            this.state.mouseTarget = null;
	        }
	    },

	    // Change z-order to back
	    toBack: function() {
	        if (this.state.mouseTarget) {
	            this.state.mouseTarget.toBack();
	        }
	    },

	    // Change z-order to front
	    toFront: function() {
	        if (this.state.mouseTarget) {
	            this.state.mouseTarget.toFront();
	        }
	    }
	});

	module.exports = Movable;


/***/ },
/* 109 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Creates and adds a point to the graph that can be dragged around.
	 * It allows constraints on its movement and draws when moves happen.
	 *
	 * Options can be passed to the constructor to control how the point behaves:
	 *   coord: [x, y]
	 *     The initial position of the point
	 *   pointSize:
	 *     changes the size of the point. defaults to 4
	 *   static: boolean
	 *     draw the point, but don't let it be interactable
	 *   cursor: "move", "pointer"
	 *     css cursor for this point
	 *   add: [function(state)]
	 *     called immediately when this movablePoint is added
	 *     default: apply any constraints and draw
	 *   draw: [function(prevState, currentState)]
	 *     drawing functions. default to [basic, highlight]
	 *   remove: [function(state)]
	 *     called when this movablePoint is removed
	 *   onMoveStart: [function(coord)]
	 *     called when this point is clicked on
	 *   constraints: [function(coord)]
	 *     called when this point is dragged
	 *     return true or nothing to accept the move
	 *     return false to cancel the move
	 *     return an [x, y] coordinate to override the move
	 *   onMove: [function(coord)]
	 *     called after all constraints functions pass and the point
	 *     is moved to a new location
	 *   onMoveEnd: [function(coord)]
	 *     called when the mouse is released from a click or move
	 *   onClick: [function(coord)]
	 *     called when someone mouses down, doesn't move the point,
	 *     and mouses up.
	 *   normalStyle:
	 *     the raphael/graphie style of the point when not hovering
	 *   highlightStyle:
	 *     the raphael/graphie style of the point when hovering, if
	 *     MovablePoint.draw.highlight is used
	 *
	 * This creates a MovablePoint object with the following methods:
	 *   setCoord: [x, y]
	 *     changes the point's coordinate
	 *   draw:
	 *     redraws the coord
	 *   modify: {options}
	 *     modifies the original options passed to the point
	 *   remove:
	 *     removes the point from graphie
	 */
	var _ = __webpack_require__(60);

	var MovablePointOptions = __webpack_require__(124);
	var WrappedEllipse = __webpack_require__(125);
	var InteractiveUtil = __webpack_require__(89);
	var objective_ = __webpack_require__(63);
	var assert = InteractiveUtil.assert;
	var normalizeOptions = InteractiveUtil.normalizeOptions;

	var kpoint = __webpack_require__(103).point;

	// State parameters that should be converted into an array of
	// functions
	var FUNCTION_ARRAY_OPTIONS = _.keys(MovablePointOptions);

	// Default "props" and "state". Both are added to this.state and
	// receive magic getter methods (this.coord() etc).
	// However, properties in DEFAULT_PROPS are updated on `modify()`,
	// while those in DEFAULT_STATE persist and are not updated.
	// Things that the user might want to change should be on "props",
	// while things used to render the point should be on "state".
	var DEFAULT_PROPS = {
	    coord: [0, 0],
	    pointSize: 4,
	    static: false,
	    cursor: "move",
	    normalStyle: null,    // turned into an object in this.modify
	    highlightStyle: null  // likewise
	};
	var DEFAULT_STATE = {
	    added: false,
	    hasMoved: false,
	    visibleShape: null,
	    mouseTarget: null
	};

	var MovablePoint = function(graphie, movable, options) {
	    _.extend(this, {
	        graphie: graphie,
	        movable: movable,
	        state: {
	            // Set here because this must be unique for each instance
	            id: _.uniqueId("movablePoint")
	        }
	    });

	    // We only set DEFAULT_STATE once, here
	    this.modify(_.extend({}, DEFAULT_STATE, options));
	};

	_.extend(MovablePoint, MovablePointOptions);
	InteractiveUtil.createGettersFor(MovablePoint, _.extend({},
	    DEFAULT_PROPS,
	    DEFAULT_STATE
	));
	InteractiveUtil.addMovableHelperMethodsTo(MovablePoint);

	_.extend(MovablePoint.prototype, {

	    cloneState: function() {
	        return _.extend(this.movable.cloneState(), this.state);
	    },

	    _createDefaultState: function() {
	        return _.extend({
	            id: this.state.id,
	        }, normalizeOptions(
	            FUNCTION_ARRAY_OPTIONS,
	            // Defaults are copied from MovablePointOptions.*.standard
	            // These defaults are set here instead of DEFAULT_PROPS/STATE
	            // because they:
	            //    - are objects, not primitives (and need a deeper copy)
	            //    - they don't need getters created for them
	            // TODO(jack): Consider "default" once we es3ify perseus
	            objective_.pluck(MovablePointOptions, "standard")

	        // We only update props here, because we want things on state to
	        // be persistent, and updated appropriately in modify()
	        ), DEFAULT_PROPS);
	    },

	    /**
	     * Resets the object to its state as if it were constructed with
	     * `options` originally. state not on DEFAULT_PROPS is maintained.
	     *
	     * Analogous to React.js's replaceProps
	     */
	    modify: function(options) {
	        this.update(_.extend(this._createDefaultState(), options));
	    },

	    /**
	     * Adjusts constructor parameters without changing previous settings
	     * for any option not specified
	     *
	     * Analogous to React.js's setProps
	     */
	    update: function(options) {
	        var self = this;
	        var graphie = self.graphie;
	        var state = _.extend(
	            self.state,
	            normalizeOptions(FUNCTION_ARRAY_OPTIONS, options)
	        );

	        assert(kpoint.is(state.coord));

	        // Default things inside the state.normalStyle object, because
	        // _.extend is not deep.
	        // We use _.extend instead of _.defaults because we don't want
	        // to modify the passed-in copy (especially if it's from
	        // DEFAULT_PROPS/STATE!)
	        var normalColor = (state.static) ? KhanUtil.DYNAMIC
	                                         : KhanUtil.INTERACTIVE;
	        state.normalStyle = _.extend({
	            fill: normalColor,
	            stroke: normalColor,
	            scale: 1
	        }, state.normalStyle);

	        state.highlightStyle = _.extend({
	            fill: KhanUtil.INTERACTING,
	            stroke: KhanUtil.INTERACTING,
	            scale: 2
	        }, state.highlightStyle);

	        if (!state.static) {
	            // the invisible shape in front of the point that gets mouse events
	            if (!state.mouseTarget) {
	                var center = self.state.coord;
	                var radii = graphie.unscaleVector(15);
	                var options = {
	                    mouselayer: true
	                };
	                state.mouseTarget = new WrappedEllipse(graphie, center, radii,
	                    options);
	                state.mouseTarget.attr({fill: "#000", opacity: 0.0});
	            }
	        }

	        // The starting coord of any move, sent to onMoveEnd as the previous
	        // value
	        var startCoord = state.coord;

	        // The Movable representing this movablePoint's representation
	        // This handles mouse events for us, which we propagate in
	        // onMove
	        self.movable.modify(_.extend({}, state, {
	            add: null,
	            modify: null,
	            draw: self.draw.bind(self),
	            remove: null,
	            onMoveStart: function() {
	                state.hasMoved = false;
	                startCoord = state.coord;
	                self._fireEvent(state.onMoveStart, startCoord, startCoord);
	                self.draw();
	            },
	            onMove: self.moveTo.bind(self),
	            onMoveEnd: function() {
	                if (self.isHovering() && !state.hasMoved) {
	                    self._fireEvent(state.onClick, state.coord, startCoord);
	                }
	                self._fireEvent(state.onMoveEnd, state.coord, startCoord);
	                state.hasMoved = false;
	                self.draw();
	            }
	        }));

	        // Trigger an add event if this hasn't been added before
	        if (!state.added) {
	            self.prevState = {};
	            self._fireEvent(state.add, self.cloneState(), self.prevState);
	            state.added = true;

	            // Update the state for `added` and in case the add event
	            // changed it
	            self.prevState = self.cloneState();
	        }

	        // Trigger a modify event
	        self._fireEvent(state.modify, self.cloneState(), self.prevState);
	    },

	    remove: function() {
	        this.state.added = false;
	        this._fireEvent(this.state.remove);
	        if (this.movable) {
	            this.movable.remove();
	        }
	        // TODO(jack): This should really be moved off of
	        // movablePoint.state and only kept on movable.state
	        this.state.mouseTarget = null;
	    },

	    constrain: function() {
	        var result = this._applyConstraints(this.coord(), this.coord());
	        if (kpoint.is(result)) {
	            this.setCoord(result);
	        }
	        return result !== false;
	    },

	    setCoord: function(coord) {
	        assert(kpoint.is(coord, 2));
	        this.state.coord = _.clone(coord);
	        this.draw();
	    },

	    setCoordConstrained: function(coord) {
	        assert(kpoint.is(coord, 2));
	        var result = this._applyConstraints(coord, this.coord());
	        if (result !== false) {
	            this.state.coord = _.clone(result);
	            this.draw();
	        }
	    },

	    moveTo: function(coord) {
	        // The caller has the option of adding an onMove() method to the
	        // movablePoint object we return as a sort of event handler
	        // By returning false from onMove(), the move can be vetoed,
	        // providing custom constraints on where the point can be moved.
	        // By returning array [x, y], the move can be overridden

	        var state = this.state;
	        var result = this._applyConstraints(coord, state.coord);
	        if (result === false) {
	            return;
	        } else if (kpoint.is(result)) {
	            coord = result;
	        }
	        if (!kpoint.equal(coord, state.coord)) {
	            var prevCoord = state.coord;
	            state.coord = coord;
	            state.hasMoved = true;
	            this._fireEvent(state.onMove, state.coord, prevCoord);
	            this.draw();
	        }
	    },

	    // Clone these for use with raphael, which modifies the input
	    // style parameters
	    normalStyle: function() {
	        return _.clone(this.state.normalStyle);
	    },

	    highlightStyle: function() {
	        return _.clone(this.state.highlightStyle);
	    },

	    // Change z-order to back
	    toBack: function() {
	        this.movable.toBack();
	        if (this.state.visibleShape) {
	            this.state.visibleShape.toBack();
	        }
	    },

	    // Change z-order to front
	    toFront: function() {
	        if (this.state.visibleShape) {
	            this.state.visibleShape.toFront();
	        }
	        this.movable.toFront();
	    },

	    /**
	     * Forwarding methods to this.movable:
	     */
	    isHovering: function() {
	        return this.movable.isHovering();
	    },

	    isDragging: function() {
	        return this.movable.isDragging();
	    },

	    mouseTarget: function() {
	        return this.movable.mouseTarget();
	    },

	    grab: function(coord) {
	        this.movable.grab(coord);
	        this.moveTo(coord);
	    }
	});

	module.exports = MovablePoint;


/***/ },
/* 110 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * MovableLine
	 */
	var _ = __webpack_require__(60);

	var MovableLineOptions = __webpack_require__(126);
	var WrappedLine = __webpack_require__(127);
	var InteractiveUtil = __webpack_require__(89);
	var objective_ = __webpack_require__(63);
	var assert = InteractiveUtil.assert;
	var normalizeOptions = InteractiveUtil.normalizeOptions;

	var kvector = __webpack_require__(103).vector;

	var FUNCTION_ARRAY_OPTIONS = [
	    "add",
	    "draw",
	    "remove",
	    "onMoveStart",
	    "constraints",
	    "onMove",
	    "onMoveEnd"
	];

	// Default "props" and "state". Both are added to this.state and
	// receive magic getter methods (this.cursor() etc).
	// However, properties in DEFAULT_PROPS are updated on `modify()`,
	// while those in DEFAULT_STATE persist and are not updated.
	// Things that the user might want to change should be on "props",
	// while things used to render the point should be on "state".
	var DEFAULT_PROPS = {
	    points: null,
	    static: false,
	    cursor: "move",
	    normalStyle: null,     // turned into an object in this.modify
	    highlightStyle: null,  // likewise
	    extendLine: false,
	    extendRay: false
	};
	var DEFAULT_STATE = {
	    visibleShape: null,
	    mouseTarget: null
	};

	var MovableLine = function(graphie, movable, options) {
	    assert(graphie != null);
	    assert(options != null);

	    _.extend(this, {
	        graphie: graphie,
	        movable: movable,
	        state: {
	            // Set here because this must be unique for each instance
	            id: _.uniqueId("movableLine")
	        }
	    });

	    // We only set DEFAULT_STATE once, here
	    this.modify(_.extend({}, DEFAULT_STATE, options));
	};

	_.extend(MovableLine, MovableLineOptions);
	InteractiveUtil.createGettersFor(MovableLine, _.extend({},
	    DEFAULT_PROPS,
	    DEFAULT_STATE
	));
	InteractiveUtil.addMovableHelperMethodsTo(MovableLine);

	_.extend(MovableLine.prototype, {

	    cloneState: function() {
	        return _.extend(this.movable.cloneState(), {
	            coords: this.coords(),
	        }, this.state);
	    },

	    _createDefaultState: function() {
	        return _.extend({
	            id: this.state.id,
	        }, normalizeOptions(
	            FUNCTION_ARRAY_OPTIONS,
	            // Defaults are copied from MovableLineOptions.*.standard
	            // These defaults are set here instead of DEFAULT_PROPS/STATE
	            // because they:
	            //    - are objects, not primitives (and need a deeper copy)
	            //    - they don't need getters created for them
	            // TODO(jack): Consider "default" once we es3ify perseus
	            objective_.pluck(MovableLineOptions, "standard")
	        ), DEFAULT_PROPS);
	    },

	    /**
	     * Resets the object to its state as if it were constructed with
	     * `options` originally. state not on DEFAULT_PROPS is maintained.
	     *
	     * Analogous to React.js's replaceProps
	     */
	    modify: function(options) {
	        this.update(_.extend(this._createDefaultState(), options));
	    },

	    /**
	     * Adjusts constructor parameters without changing previous settings
	     * for any option not specified
	     *
	     * Analogous to React.js's setProps
	     */
	    update: function(options) {
	        var self = this;
	        var graphie = this.graphie;
	        var state = self.state = _.extend(
	            self.state,
	            normalizeOptions(FUNCTION_ARRAY_OPTIONS, options)
	        );

	        // Default things inside the state.normalStyle object, because
	        // _.extend is not deep.
	        // We use _.extend instead of _.defaults because we don't want
	        // to modify the passed-in copy (especially if it's from
	        // DEFAULT_PROPERTIES!)
	        var normalColor = (state.static) ? KhanUtil.DYNAMIC :
	                                           KhanUtil.INTERACTIVE;
	        state.normalStyle = _.extend({
	            stroke: normalColor,
	            "stroke-width": 2
	        }, state.normalStyle);

	        state.highlightStyle = _.extend({
	            stroke: KhanUtil.INTERACTING,
	            "stroke-width": 3
	        }, state.highlightStyle);

	        if (!state.static) {
	            // the invisible shape in front of the line that gets mouse events
	            if (!state.mouseTarget) {
	                var options = {
	                    thickness: 30,
	                    mouselayer: true
	                };
	                state.mouseTarget = new WrappedLine(graphie, this.coord(0),
	                    this.coord(1), options);
	                state.mouseTarget.attr({fill: "#000", "opacity": 0.0});
	            }
	        }

	        if (state.static && state.mouseTarget) {
	            // state.static was specified, remove any previously
	            // existing mousetarget (from a previous modify)
	            state.mouseTarget.remove();
	            state.mouseTarget = null;
	        }

	        // The movable that handles mouse events for us
	        self.movable.modify(_.extend({}, state, {
	            mouseTarget: state.mouseTarget,

	            // We null out the add/modify/remove to avoid propagating our
	            // state.add... to the movable, so that we can fire those
	            // events ourselves, rather than letting the movable handle
	            // them
	            add: null,
	            modify: null,
	            draw: self.draw.bind(self),
	            remove: null,

	            onMoveStart: function() {
	                self._initialRefCoord = self.coord(0);
	                self._prevRefCoord = self._initialRefCoord;
	                self._totalDelta = [0, 0];

	                self._fireEvent(self.state.onMoveStart,
	                    self.coord(0),
	                    self.coord(0)
	                );
	            },

	            onMove: function(mouseCoord, prevMouseCoord) {
	                var delta = kvector.subtract(mouseCoord, prevMouseCoord);
	                self._totalDelta = kvector.add(self._totalDelta, delta);
	                var refCoord = kvector.add(self._initialRefCoord, self._totalDelta);

	                refCoord = self._applyConstraints(refCoord, self._prevRefCoord);
	                if (refCoord === false) {
	                    return;
	                }

	                self._fireEvent(self.state.onMove, refCoord, self._prevRefCoord);
	                self._prevRefCoord = refCoord;
	            },

	            onMoveEnd: function() {
	                self._fireEvent(self.state.onMoveEnd,
	                    self._prevRefCoord,
	                    self._initialRefCoord
	                );
	            },
	        }));

	        // Trigger an add event if this hasn't been added before
	        if (!state.added) {
	            self.prevState = {};
	            self._fireEvent(state.add, self.cloneState(), self.prevState);
	            state.added = true;

	            // Update the line with the points' movement
	            _.invoke(state.points, "listen", "onMove", state.id,
	                    self.draw.bind(self));

	            // Update the state for `added` and in case the add event
	            // changed it
	            self.prevState = self.cloneState();
	        }

	        // Trigger a modify event
	        self._fireEvent(state.modify, self.cloneState(), self.prevState);
	    },

	    coords: function() {
	        return _.invoke(this.state.points, "coord");
	    },

	    point: function(index) {
	        return this.state.points[index];
	    },

	    coord: function(index) {
	        return this.point(index).coord();
	    },

	    remove: function() {
	        this.state.added = false;
	        this._fireEvent(this.state.remove);
	        if (this.points) {
	            _.invoke(this.points, "unlisten", "onMove", this.state.id);
	        }

	        if (this.movable) {
	            // We need this to be guarded because it is called on the initial
	            // constructor/modify call, before this.movable is created
	            this.movable.remove();
	        }
	    },

	    // Change z-order to back
	    toBack: function() {
	        this.movable.toBack();
	        if (this.state.visibleShape) {
	            this.state.visibleShape.toBack();
	        }
	    },

	    // Change z-order to front
	    toFront: function() {
	        if (this.state.visibleShape) {
	            this.state.visibleShape.toFront();
	        }
	        this.movable.toFront();
	    },

	    /**
	     * Forwarding methods to this.movable:
	     */
	    isHovering: function() {
	        return this.movable.isHovering();
	    },

	    isDragging: function() {
	        return this.movable.isDragging();
	    },

	    mouseTarget: function() {
	        return this.movable.mouseTarget();
	    }
	});

	module.exports = MovableLine;


/***/ },
/* 111 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Creates and adds a polygon to the graph that can be dragged around.
	 * It allows constraints on its movement and draws when moves happen.
	 */

	var MovablePolygonOptions = __webpack_require__(128);
	var InteractiveUtil = __webpack_require__(89);
	var objective_ = __webpack_require__(63);
	var assert = InteractiveUtil.assert;
	var normalizeOptions = InteractiveUtil.normalizeOptions;

	var kvector = __webpack_require__(103).vector;

	// State parameters that should be converted into an array of
	// functions
	var FUNCTION_ARRAY_OPTIONS = _.keys(MovablePolygonOptions);

	// Default "props" and "state". Both are added to this.state and
	// receive magic getter methods (this.points() etc).
	// However, properties in DEFAULT_PROPS are updated on `modify()`,
	// while those in DEFAULT_STATE persist and are not updated.
	// Things that the user might want to change should be on "props",
	// while things used to render the point should be on "state".
	var DEFAULT_PROPS = {
	    points: null,
	    angleLabels: [],
	    showRightAngleMarkers: [],
	    sideLabels: [],
	    vertexLabels: [],
	    numArcs: [],
	    numArrows: [],
	    numTicks: [],
	    closed: true,
	    static: false,
	    cursor: "move",
	    normalStyle: null,    // turned into an object in this.modify
	    highlightStyle: null, // likewise
	    labelStyle: null      // likewise
	};
	var DEFAULT_STATE = {
	    added: false,
	    hasMoved: false,
	    visibleShape: null,
	    mouseTarget: null
	};

	var MovablePolygon = function(graphie, movable, options) {
	    assert(graphie != null);
	    assert(options != null);

	    _.extend(this, {
	        graphie: graphie,
	        movable: movable,
	        state: {
	            // Set here because this must be unique for each instance
	            id: _.uniqueId("movablePolygon")
	        }
	    });

	    // We only set DEFAULT_STATE once, here
	    this.modify(_.extend({}, DEFAULT_STATE, options));
	};

	_.extend(MovablePolygon, MovablePolygonOptions);
	InteractiveUtil.createGettersFor(MovablePolygon, _.extend({},
	    DEFAULT_PROPS,
	    DEFAULT_STATE
	));
	InteractiveUtil.addMovableHelperMethodsTo(MovablePolygon);

	_.extend(MovablePolygon.prototype, {

	    cloneState: function() {
	        return _.extend(this.movable.cloneState(), this.state);
	    },

	    _createDefaultState: function() {
	        return _.extend({
	            id: this.state.id,
	        }, normalizeOptions(
	            FUNCTION_ARRAY_OPTIONS,
	            // Defaults are copied from MovablePolygonOptions.*.standard
	            // These defaults are set here instead of DEFAULT_PROPS/STATE
	            // because they:
	            //    - are objects, not primitives (and need a deeper copy)
	            //    - they don't need getters created for them
	            // TODO(jack): Consider "default" once we es3ify perseus
	            objective_.pluck(MovablePolygonOptions, "standard")

	        // We only update props here, because we want things on state to
	        // be persistent, and updated appropriately in modify()
	        ), DEFAULT_PROPS);
	    },

	    /**
	     * Resets the object to its state as if it were constructed with
	     * `options` originally. state not on DEFAULT_PROPS is maintained.
	     *
	     * Analogous to React.js's replaceProps
	     */
	    modify: function(options) {
	        this.update(_.extend(this._createDefaultState(), options));
	    },

	    /**
	     * Adjusts constructor parameters without changing previous settings
	     * for any option not specified
	     *
	     * Analogous to React.js's setProps
	     */
	    update: function(options) {
	        var self = this;
	        var graphie = self.graphie;
	        var state = _.extend(
	            self.state,
	            normalizeOptions(FUNCTION_ARRAY_OPTIONS, options)
	        );

	        // Default things inside the state.normalStyle object, because
	        // _.extend is not deep.
	        // We use _.extend instead of _.defaults because we don't want
	        // to modify the passed-in copy (especially if it's from
	        // DEFAULT_PROPS/STATE!)
	        var normalColor = (state.static) ? KhanUtil.DYNAMIC :
	                                           KhanUtil.INTERACTIVE;
	        state.normalStyle = _.extend({}, state.normalStyle, {
	            "stroke-width": 2,
	            "fill-opacity": 0,
	            "fill": normalColor,
	            "stroke": normalColor
	        }, options.normalStyle);

	        state.highlightStyle = _.extend({}, {
	            "stroke": KhanUtil.INTERACTING,
	            "stroke-width": 2,
	            "fill": KhanUtil.INTERACTING,
	            "fill-opacity": 0.05
	        }, state.highlightStyle);

	        state.labelStyle = _.extend({}, {
	            "stroke": KhanUtil.DYNAMIC,
	            "stroke-width": 1,
	            "color": KhanUtil.DYNAMIC
	        }, state.labelStyle);

	        if (!state.static) {
	            // the invisible shape in front of the point that gets mouse events
	            if (!state.mouseTarget) {
	                state.mouseTarget = graphie.mouselayer.path(this.path());
	                state.mouseTarget.attr({fill: "#000", opacity: 0,
	                    cursor: "move"});
	            }
	        }

	        // The Movable representing this MovablePolygon's representation
	        // This handles mouse events for us, which we propagate in
	        // onMove. The onMoveStart-onMove-onMoveEnd logic is borrowed from
	        // movable-line.js.
	        self.movable.modify(_.extend({}, state, {
	            modify: null,
	            draw: self.draw.bind(self),
	            remove: null,
	            onMoveStart: function() {
	                self._initialRefCoord = self.coord(0);
	                self._prevRefCoord = self._initialRefCoord;
	                self._totalDelta = [0, 0];

	                self._fireEvent(self.state.onMoveStart,
	                    self.coord(0),
	                    self.coord(0)
	                );
	            },
	            onMove: function(mouseCoord, prevMouseCoord) {
	                var delta = kvector.subtract(mouseCoord, prevMouseCoord);
	                self._totalDelta = kvector.add(self._totalDelta, delta);
	                var refCoord = kvector.add(self._initialRefCoord, self._totalDelta);

	                refCoord = self._applyConstraints(refCoord, self._prevRefCoord);
	                if (refCoord === false) {
	                    return;
	                }

	                self._fireEvent(self.state.onMove, refCoord, self._prevRefCoord);
	                self._prevRefCoord = refCoord;
	            },
	            onMoveEnd: function() {
	                self._fireEvent(self.state.onMoveEnd,
	                    self._prevRefCoord,
	                    self._initialRefCoord
	                );
	            },
	        }));

	        // Update the polygon with the points' movement
	        _.invoke(state.points, "listen", "onMove", state.id,
	            self.draw.bind(self));

	        // Trigger an add event if this hasn't been added before
	        if (!state.added) {
	            self.prevState = {};
	            self._fireEvent(state.add, self.cloneState(), self.prevState);
	            state.added = true;

	            // Update the state for `added` and in case the add event
	            // changed it
	            self.prevState = self.cloneState();
	        }

	        // Trigger a modify event
	        self._fireEvent(state.modify, self.cloneState(), self.prevState);
	    },

	    path: function(state) {
	        var graphie = this.graphie;
	        state = state || this.state;

	        var coords = _.map(state.points, function(point) {
	            return graphie.scalePoint(point.coord());
	        });

	        // Create path
	        if (state.closed) {
	            coords.push(true);
	        } else {
	            // For open polygons, concatenate a reverse of the path,
	            // to remove the inside area of the path, which would
	            // otherwise be clickable (even if the closing line segment
	            // wasn't drawn
	            coords = coords.concat(
	                _.clone(coords).reverse()
	            );
	        }

	        return KhanUtil.unscaledSvgPath(coords);
	    },

	    coords: function() {
	        return _.invoke(this.state.points, "coord");
	    },

	    point: function(index) {
	        return this.state.points[index];
	    },

	    coord: function(index) {
	        return this.point(index).coord();
	    },

	    remove: function() {
	        this.state.added = false;
	        this._fireEvent(this.state.remove);
	        if (this.state.points) {
	            _.invoke(this.state.points, "unlisten", "onMove", this.state.id);
	        }

	        if (this.movable) {
	            // We need this to be guarded because it is called on the initial
	            // constructor/modify call, before this.movable is created
	            this.movable.remove();
	        }

	        // TODO(jack): This should really be moved off of
	        // movablePolygon.state and only kept on movable.state
	        this.state.mouseTarget = null;
	    },

	    constrain: function() {
	        if (this.points == null || this.points.length === 0) {
	            return;
	        }

	        var prevRefCoord = this.coord(0);
	        var refCoord = this._applyConstraints(prevRefCoord, prevRefCoord);
	        if (refCoord !== false) {
	            this._fireEvent(this.state.onMove, refCoord, prevRefCoord);
	        }
	    },

	    // Clone these for use with raphael, which modifies the input
	    // style parameters
	    normalStyle: function() {
	        return _.clone(this.state.normalStyle);
	    },

	    highlightStyle: function() {
	        return _.clone(this.state.highlightStyle);
	    },

	    // Change z-order to back
	    toBack: function() {
	        this.movable.toBack();
	        if (this.state.visibleShape) {
	            this.state.visibleShape.toBack();
	        }
	    },

	    // Change z-order to front
	    toFront: function() {
	        if (this.state.visibleShape) {
	            this.state.visibleShape.toFront();
	        }
	        this.movable.toFront();
	    },

	    /**
	     * Forwarding methods to this.movable:
	     */
	    isHovering: function() {
	        return this.movable.isHovering();
	    },

	    isDragging: function() {
	        return this.movable.isDragging();
	    },

	    mouseTarget: function() {
	        return this.movable.mouseTarget();
	    }
	});

	module.exports = MovablePolygon;


/***/ },
/* 112 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * MovableThing convenience methods
	 *
	 * Usually added to a Movable* class through
	 * InteractiveUtils.addMovableHelperMethodsTo(), but these implementations
	 * are simply for convenience.
	 */

	var _ = __webpack_require__(60);
	var kpoint = __webpack_require__(103).point;

	/* Local helper methods. */

	function getKey(eventName, id) {
	    return eventName + ":" + id;
	}

	function getEventName(key) {
	    return key.split(":")[0];
	}

	var MovableHelperMethods = {
	    /**
	     * Fire an onSomething type event to all functions in listeners
	     */
	    _fireEvent: function(listeners, currentValue, previousValue) {
	        _.invoke(listeners, "call", this, currentValue, previousValue);
	    },

	    /**
	     * Combine the array of constraints functions
	     * Returns either an [x, y] coordinate or false
	     */
	    _applyConstraints: function(current, previous) {
	        return _.reduce(this.state.constraints, function(memo, constraint) {
	            // A move that has been cancelled won't be propagated to later
	            // constraints calls
	            if (memo === false) {
	                return false;
	            }

	            var result = constraint.call(this, memo, previous);
	            if (result === false) {
	                // Returning false cancels the move
	                return false;

	            } else if (kpoint.is(result, 2)) {
	                // Returning a coord from constraints overrides the move
	                return result;

	            } else if (result === true || result == null) {
	                // Returning true or undefined allow the move to occur
	                return memo;

	            } else {
	                // Anything else is an error
	                throw new Error("Constraint returned invalid result: " +
	                        result);
	            }
	        }, current, this);
	    },

	    /**
	     * Call all draw functions, and update our prevState for the next
	     * draw function
	     */
	    draw: function() {
	        var currState = this.cloneState();
	        MovableHelperMethods._fireEvent.call(this,
	            this.state.draw,
	            currState,
	            this.prevState
	        );
	        this.prevState = currState;
	    },

	    /**
	     * Add a listener to any event: startMove, constraints, onMove, onMoveEnd,
	     * etc. If a listener is already bound to the given eventName and id, then
	     * it is overwritten by func.
	     *
	     * eventName: the string name of the event to listen to. one of:
	     *   "onMoveStart", "onMove", "onMoveEnd", "draw", "remove"
	     *
	     * id: a string id that can be used to remove this event at a later time
	     *   note: adding multiple listeners with the same id is undefined behavior
	     *
	     * func: the function to call when the event happens, which is called
	     *   with the event's standard parameters [usually (coord, prevCoord) or
	     *   (state, prevState)]
	     */
	    listen: function(eventName, id, func) {
	        this._listenerMap = this._listenerMap || {};

	        // If there's an existing handler, replace it by using its index in
	        // `this.state[eventName]`; otherwise, add this handler to the end
	        var key = getKey(eventName, id);
	        var index = this._listenerMap[key] =
	                this._listenerMap[key] || this.state[eventName].length;
	        this.state[eventName][index] = func;
	    },

	    /**
	     * Remove a previously added listener, by the id specified in the
	     * corresponding listen() call
	     *
	     * If the given id has not been registered already, this is a no-op
	     */
	    unlisten: function(eventName, id) {
	        this._listenerMap = this._listenerMap || {};

	        var key = getKey(eventName, id);
	        var index = this._listenerMap[key];
	        if (index !== undefined) {
	            // Remove handler from list of event handlers and listenerMap
	            this.state[eventName].splice(index, 1);
	            delete this._listenerMap[key];

	            // Re-index existing events: if they occur after `index`, decrement
	            var keys = _.keys(this._listenerMap);
	            _.each(keys, function(key) {
	                if (getEventName(key) === eventName &&
	                        this._listenerMap[key] > index) {
	                    this._listenerMap[key]--;
	                }
	            }, this);
	        }
	    }
	};

	module.exports = MovableHelperMethods;


/***/ },
/* 113 */
/***/ function(module, exports, __webpack_require__) {

	/* WEBPACK VAR INJECTION */(function(global) {/* @flow */

	/**
	 * Simple-Markdown
	 * ===============
	 *
	 * Simple-Markdown's primary goal is to be easy to adapt. It aims
	 * to be compliant with John Gruber's [Markdown Syntax page][1],
	 * but compatiblity with other markdown implementations' edge-cases
	 * will be sacrificed where it conflicts with simplicity or
	 * extensibility.
	 *
	 * If your goal is to simply embed a standard markdown implementation
	 * in your website, simple-markdown is probably not the best library
	 * for you (although it should work). But if you have struggled to
	 * customize an existing library to meet your needs, simple-markdown
	 * might be able to help.
	 *
	 * Many of the regexes and original logic has been adapted from
	 * the wonderful [marked.js](https://github.com/chjj/marked)
	 *
	 * LICENSE (MIT):
	 * New code copyright (c) 2014 Khan Academy.
	 *
	 * Portions adapted from marked.js copyright (c) 2011-2014
	 * Christopher Jeffrey (https://github.com/chjj/).
	 *
	 * Permission is hereby granted, free of charge, to any person obtaining a copy
	 * of this software and associated documentation files (the "Software"), to deal
	 * in the Software without restriction, including without limitation the rights
	 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
	 * copies of the Software, and to permit persons to whom the Software is
	 * furnished to do so, subject to the following conditions:
	 *
	 * The above copyright notice and this permission notice shall be included in
	 * all copies or substantial portions of the Software.
	 *
	 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
	 * THE SOFTWARE.
	 */
	(function() {

	// Load dependencies from the global namespace or require them
	var find = function(globalName) {
	    if (typeof window !== "undefined" && window[globalName]) {
	        return window[globalName];
	    } else if (typeof global !== "undefined" && global[globalName]) {
	        return global[globalName];
	    } else {
	        return undefined;
	    }
	};

	var _ = find("_") || __webpack_require__(60);
	var React = find("React") || __webpack_require__(61);

	/**
	 * Creates a parser for a given set of rules, with the precedence
	 * specified as a list of rules.
	 *
	 * @rules: an object containing rule type -> {regex, parse} objects
	 * @ruleList: an array of rule types, specifying the precedence rules
	 *   are evaluated in (earlier in the array is higher precendence)
	 *
	 * @returns The resulting parse function, with the following parameters:
	 *   @source: the input source string to be parsed
	 *   @state: an optional object to be threaded through parse
	 *     calls. Allows clients to add stateful operations to
	 *     parsing, such as keeping track of how many levels deep
	 *     some nesting is. For an example use-case, see passage-ref
	 *     parsing in src/widgets/passage/passage-markdown.jsx
	 */
	var parserFor = function(rules, ruleList) {
	    var nestedParse = function(source, state) {
	        var result = [];
	        state = state || {};
	        while (source) {
	            var i = 0;
	            while (i < ruleList.length) {
	                var ruleType = ruleList[i];
	                var rule = rules[ruleType];
	                var capture = rule.regex.exec(source);
	                if (capture) {
	                    source = source.substring(capture[0].length);
	                    var parsed = rule.parse(capture, nestedParse, state);
	                    // We maintain the same object here so that rules can
	                    // store references to the objects they return and
	                    // modify them later. (oops sorry! but this adds a lot
	                    // of power--see reflinks.)
	                    // We also let rules override the default type of
	                    // their parsed node if they would like to, so that
	                    // there can be a single output function for all links,
	                    // even if there are several rules to parse them.
	                    if (parsed.type == null) {
	                        parsed.type = ruleType;
	                    }
	                    result.push(parsed);
	                    break;
	                }
	                i++;
	            }
	            if (i === rules.length) {
	                throw new Error(
	                    "could not find rule to match content: " + source
	                );
	            }
	        }
	        return result;
	    };
	    return nestedParse;
	};

	var outputFor = function(outputFunc) {
	    var nestedOutput = function(ast) {
	        if (_.isArray(ast)) {
	            return _.map(ast, nestedOutput);
	        } else {
	            return outputFunc(ast, nestedOutput);
	        }
	    };
	    return nestedOutput;
	};

	var sanitizeUrl = function(url) {
	    if (url == null) {
	        return null;
	    }
	    try {
	        var prot = decodeURIComponent(url)
	            .replace(/[^A-Za-z0-9/:]/g, '')
	            .toLowerCase();
	        if (prot.indexOf('javascript:') === 0) {
	            return null;
	        }
	    } catch (e) {
	        // decodeURIComponent sometimes throws a URIError
	        // See `decodeURIComponent('a%AFc');`
	        // http://stackoverflow.com/questions/9064536/javascript-decodeuricomponent-malformed-uri-exception
	        return null;
	    }
	    return url;
	};

	var parseCapture = function(capture, parse, state) {
	    return {
	        content: parse(capture[1], state)
	    };
	};
	var ignoreCapture = function() { return {}; };

	// recognize a `*` `-`, `+`, `1.`, `2.`... list bullet
	var LIST_BULLET = "(?:[*+-]|\\d+\\.)";
	// recognize the start of a list item:
	// leading space plus a bullet plus a space (`   * `)
	var LIST_ITEM_PREFIX = "( *)(" + LIST_BULLET + ") +";
	var LIST_ITEM_PREFIX_R = new RegExp("^" + LIST_ITEM_PREFIX);
	// recognize an individual list item:
	//  * hi
	//    this is part of the same item
	//
	//    as is this, which is a new paragraph in the same item
	//
	//  * but this is not part of the same item
	var LIST_ITEM_R = new RegExp(
	    LIST_ITEM_PREFIX +
	    "[^\\n]*(?:\\n" +
	    "(?!\\1" + LIST_BULLET + " )[^\\n]*)*\n",
	    "gm"
	);
	// recognize the end of a paragraph block inside a list item:
	// two or more newlines at end end of the item
	var LIST_BLOCK_END_R = /\n{2,}$/;
	var LIST_ITEM_END_R = / *\n+$/;
	// check whether a list item has paragraphs: if it does,
	// we leave the newlines at the end
	var LIST_IS_MULTI_PARAGRAPH_R = /\n{2,}./;

	var TABLES = (function() {
	    // predefine regexes so we don't have to create them inside functions
	    // sure, regex literals should be fast, even inside functions, but they
	    // aren't in all browsers.
	    var TABLE_HEADER_TRIM = /^ *| *\| *$/g;
	    var TABLE_ALIGN_TRIM = /^ *|\| *$/g;

	    var TABLE_CELLS_TRIM = /(?: *\| *)?\n$/;
	    var NPTABLE_CELLS_TRIM = /\n$/;
	    var PLAIN_TABLE_ROW_TRIM = /^ *\| *| *\| *$/g;
	    var TABLE_ROW_SPLIT = / *\| */;

	    var TABLE_RIGHT_ALIGN = /^ *-+: *$/;
	    var TABLE_CENTER_ALIGN = /^ *:-+: *$/;
	    var TABLE_LEFT_ALIGN = /^ *:-+ *$/;

	    var parseTableAlignCapture = function(alignCapture) {
	        if (TABLE_RIGHT_ALIGN.test(alignCapture)) {
	            return "right";
	        } else if (TABLE_CENTER_ALIGN.test(alignCapture)) {
	            return "center";
	        } else if (TABLE_LEFT_ALIGN.test(alignCapture)) {
	            return "left";
	        } else {
	            return null;
	        }
	    };

	    var parseTableHeader = function(capture, parse, state) {
	        var headerText = capture[1]
	            .replace(TABLE_HEADER_TRIM, "")
	            .split(TABLE_ROW_SPLIT);
	        return _.map(headerText, function(text) {
	            return parse(text, state);
	        });
	    };

	    var parseTableAlign = function(capture, parse, state) {
	        var alignText = capture[2]
	            .replace(TABLE_ALIGN_TRIM, "")
	            .split(TABLE_ROW_SPLIT);

	        return _.map(alignText, parseTableAlignCapture);
	    };

	    var parseTableCells = function(capture, parse, state) {
	        var rowsText = capture[3]
	            .replace(TABLE_CELLS_TRIM, "")
	            .split("\n");

	        return _.map(rowsText, function(rowText) {
	            var cellText = rowText
	                .replace(PLAIN_TABLE_ROW_TRIM, "")
	                .split(TABLE_ROW_SPLIT);
	            return _.map(cellText, function(text) {
	                return parse(text, state);
	            });
	        });
	    };

	    var parseNpTableCells = function(capture, parse, state) {
	        var rowsText = capture[3]
	            .replace(NPTABLE_CELLS_TRIM, "")
	            .split("\n");

	        return _.map(rowsText, function(rowText) {
	            var cellText = rowText.split(TABLE_ROW_SPLIT);
	            return _.map(cellText, function(text) {
	                return parse(text, state);
	            });
	        });
	    };

	    var parseTable = function(capture, parse, state) {
	        var header = parseTableHeader(capture, parse, state);
	        var align = parseTableAlign(capture, parse, state);
	        var cells = parseTableCells(capture, parse, state);

	        return {
	            type: "table",
	            header: header,
	            align: align,
	            cells: cells
	        };
	    };

	    var parseNpTable = function(capture, parse, state) {
	        var header = parseTableHeader(capture, parse, state);
	        var align = parseTableAlign(capture, parse, state);
	        var cells = parseNpTableCells(capture, parse, state);

	        return {
	            type: "table",
	            header: header,
	            align: align,
	            cells: cells
	        };
	    };

	    return {
	        parseTable: parseTable,
	        parseNpTable: parseNpTable
	    };
	})();

	var LINK_INSIDE = "(?:\\[[^\\]]*\\]|[^\\]]|\\](?=[^\\[]*\\]))*";
	var LINK_HREF_AND_TITLE =
	        "\\s*<?([^\\s]*?)>?(?:\\s+['\"]([\\s\\S]*?)['\"])?\\s*";
	var AUTOLINK_MAILTO_CHECK_R = /mailto:/i;

	var parseRef = function(capture, state, refNode) {
	    var ref = (capture[2] || capture[1])
	        .replace(/\s+/g, ' ')
	        .toLowerCase();

	    // We store information about previously seen defs on
	    // state._defs (_ to deconflict with client-defined
	    // state). If the def for this reflink/refimage has
	    // already been seen, we can use its target/source
	    // and title here:
	    if (state._defs && state._defs[ref]) {
	        _.extend(refNode, state._defs[ref]);
	    }

	    // In case we haven't seen our def yet (or if someone
	    // overwrites that def later on), we add this node
	    // to the list of ref nodes for that def. Then, when
	    // we find the def, we can modify this link/image AST
	    // node :).
	    // I'm sorry.
	    state._refs = state._refs || {};
	    state._refs[ref] = state._refs[ref] || [];
	    state._refs[ref].push(refNode);

	    return refNode;
	};

	var defaultRules = {
	    heading: {
	        regex: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n *)+\n/,
	        parse: function(capture, parse, state) {
	            return {
	                level: capture[1].length,
	                content: parse(capture[2], state)
	            };
	        },
	        output: function(node, output) {
	            return React.DOM["h" + node.level](
	                null,
	                output(node.content)
	            );
	        }
	    },
	    nptable: {
	        regex: /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,
	        parse: TABLES.parseNpTable
	    },
	    lheading: {
	        regex: /^([^\n]+)\n *(=|-){3,} *(?:\n *)+\n/,
	        parse: function(capture, parse, state) {
	            return {
	                type: "heading",
	                level: capture[2] === '=' ? 1 : 2,
	                content: parse(capture[1], state)
	            };
	        }
	    },
	    hr: {
	        regex: /^( *[-*_]){3,} *(?:\n *)+\n/,
	        parse: ignoreCapture,
	        output: function() { return React.DOM.hr(null); }
	    },
	    codeBlock: {
	        regex: /^(?:    [^\n]+\n*)+(?:\n *)+\n/,
	        parse: function(capture, parse, state) {
	            var content = capture[0]
	                .replace(/^    /gm, '')
	                .replace(/\n+$/, '');
	            return {
	                lang: undefined,
	                content: content
	            };
	        },
	        output: function(node, output) {
	            var className = node.lang ?
	                "markdown-code-" + node.lang :
	                undefined;
	            return React.DOM.pre(null,
	                React.DOM.code({className: className},
	                    node.content
	                )
	            );
	        }
	    },
	    fence: {
	        regex: /^ *(`{3,}|~{3,}) *(\S+)? *\n([\s\S]+?)\s*\1 *(?:\n *)+\n/,
	        parse: function(capture, parse, state) {
	            return {
	                type: "codeBlock",
	                lang: capture[2] || undefined,
	                content: capture[3]
	            };
	        }
	    },
	    blockQuote: {
	        regex: /^( *>[^\n]+(\n[^\n]+)*\n*)+\n{2,}/,
	        parse: function(capture, parse, state) {
	            var content = capture[0].replace(/^ *> ?/gm, '');
	            return {
	                content: parse(content, state)
	            };
	        },
	        output: function(node, output) {
	            return React.DOM.blockquote(null, output(node.content));
	        }
	    },
	    list: {
	        regex: new RegExp(
	            "^( *)(" + LIST_BULLET + ") " +
	            "[\\s\\S]+?(?:\n{2,}(?! )" +
	            "(?!\\1" + LIST_BULLET + " )\\n*" +
	            // the \\s*$ here is so that we can parse the inside of nested
	            // lists, where our content might end before we receive two `\n`s
	            "|\\s*\n$)"
	        ),
	        parse: function(capture, parse, state) {
	            var bullet = capture[2];
	            var ordered = bullet.length > 1;
	            var start = ordered ? +bullet : undefined;
	            var items = capture[0]
	                .replace(LIST_BLOCK_END_R, "\n")
	                .match(LIST_ITEM_R);

	            var lastItemWasAParagraph = false;
	            var itemContent = _.map(items, function(item, i) {
	                // We need to see how far indented this item is:
	                var space = LIST_ITEM_PREFIX_R.exec(item)[0].length;
	                // And then we construct a regex to "unindent" the subsequent
	                // lines of the items by that amount:
	                var spaceRegex = new RegExp("^ {1," + space + "}", "gm");

	                // Before processing the item, we need a couple things
	                var content = item
	                         // remove indents on trailing lines:
	                        .replace(spaceRegex, '')
	                         // remove the bullet:
	                        .replace(LIST_ITEM_PREFIX_R, '');

	                // Handling "loose" lists, like:
	                //
	                //  * this is wrapped in a paragraph
	                //
	                //  * as is this
	                //
	                //  * as is this
	                var isLastItem = (i === items.length - 1);
	                var containsBlocks = content.indexOf("\n\n") !== -1;

	                // Any element in a list is a block if it contains multiple
	                // newlines. The last element in the list can also be a block
	                // if the previous item in the list was a block (this is
	                // because non-last items in the list can end with \n\n, but
	                // the last item can't, so we just "inherit" this property
	                // from our previous element).
	                var thisItemIsAParagraph = containsBlocks ||
	                        (isLastItem && lastItemWasAParagraph);
	                lastItemWasAParagraph = thisItemIsAParagraph;

	                var adjustedContent = content.replace(LIST_ITEM_END_R, "\n");
	                if (thisItemIsAParagraph) {
	                    adjustedContent += "\n";
	                }

	                return parse(adjustedContent, state);
	            });

	            return {
	                ordered: ordered,
	                start: start,
	                items: itemContent
	            };
	        },
	        output: function(node, output) {
	            var ListWrapper = node.ordered ? "ol" : "ul";
	            return React.DOM[ListWrapper]({
	                start: node.start
	            }, _.map(node.items, function(item) {
	                    return React.DOM.li(null, output(item));
	                })
	            );
	        }
	    },
	    def: {
	        // TODO(aria): This is super hacky, and basically looks for a def
	        // either inline or block that is followed by two newlines OR a
	        // newline and another def. This isn't quite correct, since it
	        // doesn't work on things that look like two defs within a
	        // paragraph but separated by a newline.
	        // TODO(aria): fix this
	        // TODO(aria): fix 80 char line width
	        regex: /^ *\[([^\]]+)\]: *<?([^\s>]*)>?(?: +["(]([^\n]+)[")])? *\n(?=\n|\[[^\]]+\]: )\n?/,
	        parse: function(capture, parse, state) {
	            var def = capture[1]
	                .replace(/\s+/g, ' ')
	                .toLowerCase();
	            var target = capture[2];
	            var title = capture[3];
	            var defAttrs = {
	                target: target,
	                title: title
	            };

	            // Look for previous links/images using this def
	            // If any links/images using this def have already been declared,
	            // they will have added themselves to the state._refs[def] list
	            // (_ to deconflict with client-defined state). We look through
	            // that list of reflinks for this def, and modify those AST nodes
	            // with our newly found information now.
	            // Sorry :(.
	            if (state._refs && state._refs[def]) {
	                _.each(state._refs[def], function(link) {
	                    _.extend(link, defAttrs);
	                });
	            }

	            // Add this def to our map of defs for any future links/images
	            // In case we haven't found any or all of the refs referring to
	            // this def yet, we add our def to the table of known defs, so
	            // that future reflinks can modify themselves appropriately with
	            // this information.
	            state._defs = state._defs || {};
	            state._defs[def] = defAttrs;

	            // return the relevant parsed information
	            // for debugging only.
	            return _.extend({
	                def: def
	            }, defAttrs);
	        },
	        output: function() { return null; }
	    },
	    table: {
	        regex: /^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/,
	        parse: TABLES.parseTable,
	        output: function(node, output) {
	            var getStyle = function(colIndex) {
	                return node.align[colIndex] == null ? {} : {
	                    textAlign: node.align[colIndex]
	                };
	            };

	            var headers = _.map(node.header, function(content, i) {
	                return React.DOM.th({style: getStyle(i)},
	                    output(content)
	                );
	            });

	            var rows = _.map(node.cells, function(row, r) {
	                return React.DOM.tr(null,
	                    _.map(row, function(content, c) {
	                        return React.DOM.td({style: getStyle(c)},
	                            output(content)
	                        );
	                    })
	                );
	            });

	            return React.DOM.table(null,
	                React.DOM.thead(null,
	                    React.DOM.tr(null,
	                        headers
	                    )
	                ),
	                React.DOM.tbody(null,
	                    rows
	                )
	            );
	        }
	    },
	    newline: {
	        regex: /^(?:\n *)*\n/,
	        parse: ignoreCapture,
	        output: function(node, output) { return "\n"; }
	    },
	    paragraph: {
	        regex: /^((?:[^\n]|\n(?! *\n))+)(?:\n *)+\n/,
	        parse: parseCapture,
	        output: function(node, output) {
	            return React.DOM.div({className: "paragraph"}, output(node.content));
	        }
	    },
	    escape: {
	        // We don't allow escaping numbers, letters, or spaces here so that
	        // backslashes used in plain text still get rendered. But allowing
	        // escaping anything else provides a very flexible escape mechanism,
	        // regardless of how this grammar is extended.
	        regex: /^\\([^0-9A-Za-z\s])/,
	        parse: function(capture, parse, state) {
	            return {
	                type: "text",
	                content: capture[1]
	            };
	        }
	    },
	    autolink: {
	        regex: /^<([^ >]+:\/[^ >]+)>/,
	        parse: function(capture, parse, state) {
	            return {
	                type: "link",
	                content: [{
	                    type: "text",
	                    content: capture[1]
	                }],
	                target: capture[1]
	            };
	        }
	    },
	    mailto: {
	        regex: /^<([^ >]+@[^ >]+)>/,
	        parse: function(capture, parse, state) {
	            var address = capture[1];
	            var target = capture[1];

	            // Check for a `mailto:` already existing in the link:
	            if (!AUTOLINK_MAILTO_CHECK_R.test(target)) {
	                target = "mailto:" + target;
	            }

	            return {
	                type: "link",
	                content: [{
	                    type: "text",
	                    content: address
	                }],
	                target: target
	            };
	        }
	    },
	    url: {
	        regex: /^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,
	        parse: function(capture, parse, state) {
	            return {
	                type: "link",
	                content: [{
	                    type: "text",
	                    content: capture[1]
	                }],
	                target: capture[1],
	                title: undefined
	            };
	        }
	    },
	    link: {
	        regex: new RegExp(
	            "^\\[(" + LINK_INSIDE + ")\\]\\(" + LINK_HREF_AND_TITLE + "\\)"
	        ),
	        parse: function(capture, parse, state) {
	            var link ={
	                content: parse(capture[1], state),
	                target: capture[2],
	                title: capture[3]
	            };
	            return link;
	        },
	        output: function(node, output) {
	            return React.DOM.a({
	                href: sanitizeUrl(node.target),
	                title: node.title
	            }, output(node.content));
	        }
	    },
	    image: {
	        regex: new RegExp(
	            "^!\\[(" + LINK_INSIDE + ")\\]\\(" + LINK_HREF_AND_TITLE + "\\)"
	        ),
	        parse: function(capture, parse, state) {
	            var image = {
	                alt: capture[1],
	                target: capture[2],
	                title: capture[3]
	            };
	            return image;
	        },
	        output: function(node, output) {
	            return React.DOM.img({
	                src: sanitizeUrl(node.target),
	                alt: node.alt,
	                title: node.title});
	        }
	    },
	    reflink: {
	        regex: new RegExp(
	            // The first [part] of the link
	            "^\\[(" + LINK_INSIDE + ")\\]" +
	            // The [ref] target of the link
	            "\\s*\\[([^\\]]*)\\]"
	        ),
	        parse: function(capture, parse, state) {
	            return parseRef(capture, state, {
	                type: "link",
	                content: parse(capture[1], state)
	            });
	        }
	    },
	    refimage: {
	        regex: new RegExp(
	            // The first [part] of the link
	            "^!\\[(" + LINK_INSIDE + ")\\]" +
	            // The [ref] target of the link
	            "\\s*\\[([^\\]]*)\\]"
	        ),
	        parse: function(capture, parse, state) {
	            return parseRef(capture, state, {
	                type: "image",
	                alt: capture[1]
	            });
	        }
	    },
	    strong: {
	        regex: /^\*\*([\s\S]+?)\*\*(?!\*)/,
	        parse: parseCapture,
	        output: function(node, output) {
	            return React.DOM.strong(null, output(node.content));
	        }
	    },
	    u: {
	        regex: /^__([\s\S]+?)__(?!_)/,
	        parse: parseCapture,
	        output: function(node, output) {
	            return React.DOM.u(null, output(node.content));
	        }
	    },
	    em: {
	        regex: /^\b_((?:__|[\s\S])+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,
	        parse: function(capture, parse, state) {
	            return {
	                content: parse(capture[2] || capture[1], state)
	            };
	        },
	        output: function(node, output) {
	            return React.DOM.em(null, output(node.content));
	        }
	    },
	    del: {
	        regex: /^~~(?=\S)([\s\S]*?\S)~~/,
	        parse: parseCapture,
	        output: function(node, output) {
	            return React.DOM.del(null, output(node.content));
	        }
	    },
	    inlineCode: {
	        regex: /^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/,
	        parse: function(capture, parse, state) {
	            return {
	                content: capture[2]
	            };
	        },
	        output: function(node, output) {
	            return React.DOM.code(null, node.content);
	        }
	    },
	    br: {
	        regex: /^ {2,}\n/,
	        parse: ignoreCapture,
	        output: function() { return React.DOM.br(null); }
	    },
	    text: {
	        // Here we look for anything followed by non-symbols,
	        // double newlines, or double-space-newlines
	        // We break on any symbol characters so that this grammar
	        // is easy to extend without needing to modify this regex
	        // HACK: the `-` is included in the list of text characters
	        // so that you can't start a list inside a list without a
	        // newline preceding the hyphen (that is, so '- a - b' is
	        // parsed as a single list item instead of a second list
	        // inside a list item). The `-\S` rule is so that we break
	        // dashes that couldn't initialize lists, so that headings
	        // with `------` underlines are not parsed as one text
	        // blob.
	        // TODO(aria): add block vs inline regexes so that we
	        // can define lists as block and not consider them for
	        // inline text.
	        regex: /^[\s\S]+?(?=[^0-9A-Za-z\s\u00ff-\uffff\-]|-\S|\n\n| {2,}\n|\w+:|$)/,
	        parse: function(capture, parse, state) {
	            return {
	                content: capture[0]
	            };
	        },
	        output: function(node, output) {
	            return node.content;
	        }
	    }
	};

	var defaultPriorities = Object.keys(defaultRules);

	var ruleOutput = function(rules) {
	    var nestedRuleOutput = function(ast, outputFunc) {
	        return rules[ast.type].output(ast, outputFunc);
	    };
	    return nestedRuleOutput;
	};

	var defaultParse = parserFor(defaultRules, defaultPriorities);
	var defaultOutput = outputFor(ruleOutput(defaultRules));

	var SimpleMarkdown = {
	    parserFor: parserFor,
	    outputFor: outputFor,
	    defaultRules: defaultRules,
	    defaultPriorities: defaultPriorities,
	    ruleOutput: ruleOutput,
	    defaultParse: defaultParse,
	    defaultOutput: defaultOutput,
	    sanitizeUrl: sanitizeUrl
	};

	if (typeof module !== "undefined" && module.exports) {
	    module.exports = SimpleMarkdown;
	} else if (typeof global !== "undefined") {
	    global.SimpleMarkdown = SimpleMarkdown;
	} else {
	    window.SimpleMarkdown = SimpleMarkdown;
	}

	})();
	
	/* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))

/***/ },
/* 114 */
/***/ function(module, exports, __webpack_require__) {

	var assign = __webpack_require__(133);

	var styleRuleValidator = __webpack_require__(129);
	var styleRuleConverter = __webpack_require__(130);
	var mediaQueryValidator = __webpack_require__(134);

	var existingClasses = {};
	var styleTag = createStyleTag();

	var classNameId = 0;
	var randomSuffix = Math.random().toString(36).slice(-5);

	function generateValidCSSClassName() {
	  // CSS classNames can't start with a number.
	  return 'c' + (classNameId++) + '-' + randomSuffix;
	}

	function objToCSS(style) {
	  var serialized = '';
	  for (var propName in style) {
	    // we put that ourselves
	    if (propName == 'className') continue;

	    var cssPropName = styleRuleConverter.hyphenateProp(propName);
	    if (!styleRuleValidator.isValidProp(cssPropName)) {
	      console.warn(
	        '%s (transformed into %s) is not a valid CSS property name.', propName, cssPropName
	      );
	      continue;
	    }

	    var styleValue = style[propName];
	    if (!styleRuleValidator.isValidValue(styleValue)) continue;

	    if (styleValue !== null) {
	      serialized += cssPropName + ':';
	      serialized += styleRuleConverter.escapeValueForProp(styleValue,
	        cssPropName) + ';';
	    }
	  }
	  return serialized || null;
	}

	function createStyleTag() {
	  var tag = document.createElement('style');
	  document.getElementsByTagName('head')[0].appendChild(tag);
	  return tag;
	}

	function styleToCSS(style) {
	  var styleStr = '.' + style.className + '{';
	  styleStr += objToCSS(style.value);
	  styleStr += '}';

	  if (style.media) {
	    if (!mediaQueryValidator(style.media)) {
	      console.log('%s is not a valid media query.', style.media);
	    }
	    styleStr = style.media + '{' + styleStr + '}';
	  }

	  return styleStr;
	}

	// TODO: support media queries
	function parseStyles(className, styleObj) {
	  var mainStyle = {
	    className: className,
	    value: {}
	  };
	  var styles = [mainStyle];

	  Object.keys(styleObj).forEach(function(k){
	    // pseudo-selector, insert a new rule
	    if (k[0] === ':') {
	      styles.push({
	        className: className+k,
	        value: styleObj[k]
	      });
	      return;
	    } else if (k.substring(0, 6) === '@media') {
	      styles.push({
	        className: className,
	        value: styleObj[k],
	        media: k
	      });
	      return;
	    }

	    // normal rule, insert into main one
	    mainStyle.value[k] = styleObj[k];
	  });

	  return styles;
	}

	function insertStyle(className, styleObj) {
	  var styles = parseStyles(className, styleObj);
	  var styleStr = styles.map(styleToCSS).join('');
	  styleTag.innerHTML += styleStr;
	}

	var RCSS = {
	  merge: function(a, b, c, d, e) {
	    return assign({}, a, b, c, d, e);
	  },

	  createClass: function(styleObj) {
	    var styleId = JSON.stringify(styleObj);
	    var className;

	    if (existingClasses[styleId]) {
	      // already exists, use the existing className
	      className = existingClasses[styleId];
	    } else {
	      // generate a new class and insert it
	      className = generateValidCSSClassName();
	      existingClasses[styleId] = className;
	      insertStyle(className, styleObj);
	    }

	    styleObj.className = className;
	    return styleObj;
	  }
	};

	module.exports = RCSS;


/***/ },
/* 115 */
/***/ function(module, exports, __webpack_require__) {

	/*
	 * Number Utils
	 * A number is a js-number, e.g. 5.12
	 */

	var _ = __webpack_require__(60);

	var DEFAULT_TOLERANCE = 1e-9;
	var EPSILON = Math.pow(2, -42);

	var knumber = {

	    DEFAULT_TOLERANCE: DEFAULT_TOLERANCE,
	    EPSILON: EPSILON,

	    is: function(x) {
	        return _.isNumber(x) && !_.isNaN(x);
	    },

	    equal: function(x, y, tolerance) {
	        // Checking for undefined makes this function behave nicely
	        // with vectors of different lengths that are _.zip'd together
	        if (x == null || y == null) {
	            return x === y;
	        }
	        if (tolerance == null) {
	            tolerance = DEFAULT_TOLERANCE;
	        }
	        return Math.abs(x - y) < tolerance;
	    },

	    sign: function(x, tolerance) {
	        return knumber.equal(x, 0, tolerance) ? 0 : Math.abs(x) / x;
	    },

	    // Round a number to a certain number of decimal places
	    round: function(num, precision) {
	        var factor = Math.pow(10, precision);
	        return Math.round(num * factor) / factor;
	    },

	    // Round num to the nearest multiple of increment
	    // i.e. roundTo(83, 5) -> 85
	    roundTo: function(num, increment) {
	        return Math.round(num / increment) * increment;
	    },

	    floorTo: function(num, increment) {
	        return Math.floor(num / increment) * increment;
	    },

	    ceilTo: function(num, increment) {
	        return Math.ceil(num / increment) * increment;
	    },

	    isInteger: function(num, tolerance) {
	        return knumber.equal(Math.round(num), num, tolerance);
	    },

	    /**
	     * toFraction
	     *
	     * Returns a [numerator, denominator] array rational representation
	     * of `decimal`
	     *
	     * See http://en.wikipedia.org/wiki/Continued_fraction for implementation
	     * details
	     *
	     * toFraction(4/8) => [1, 2]
	     * toFraction(0.66) => [33, 50]
	     * toFraction(0.66, 0.01) => [2/3]
	     * toFraction(283 + 1/3) => [850, 3]
	     */
	    toFraction: function(decimal, tolerance, max_denominator) {
	        max_denominator = max_denominator || 1000;
	        tolerance = tolerance || EPSILON; // can't be 0

	        // Initialize everything to compute successive terms of
	        // continued-fraction approximations via recurrence relation
	        var n = [1, 0], d = [0, 1];
	        var a = Math.floor(decimal), t;
	        var rem = decimal - a;

	        while (d[0] <= max_denominator) {
	            if (knumber.equal(n[0] / d[0], decimal, tolerance)) {
	                return [n[0], d[0]];
	            }
	            n = [a*n[0] + n[1], n[0]];
	            d = [a*d[0] + d[1], d[0]];
	            a = Math.floor(1 / rem);
	            rem = 1/rem - a;
	        }

	        // We failed to find a nice rational representation,
	        // so return an irrational "fraction"
	        return [decimal, 1];
	    }
	};

	module.exports = knumber;



/***/ },
/* 116 */
/***/ function(module, exports, __webpack_require__) {

	/*
	 * Vector Utils 
	 * A vector is an array of numbers e.g. [0, 3, 4].
	 */

	var _ = __webpack_require__(60);
	var knumber = __webpack_require__(115);

	function arraySum(array) {
	    return _.reduce(array, function(memo, arg) { return memo + arg; }, 0);
	}

	function arrayProduct(array) {
	    return _.reduce(array, function(memo, arg) { return memo * arg; }, 1);
	}

	var kvector = {

	    is: function(vec, length) {
	        if (!_.isArray(vec)) {
	            return false;
	        }
	        if (length !== undefined && vec.length !== length) {
	            return false;
	        }
	        return _.all(vec, knumber.is);
	    },

	    // Normalize to a unit vector
	    normalize: function(v) {
	        return kvector.scale(v, 1 / kvector.length(v));
	    },

	    // Length/magnitude of a vector
	    length: function(v) {
	        return Math.sqrt(kvector.dot(v, v));
	    },

	    // Dot product of two vectors
	    dot: function(a, b) {
	        var vecs = _.toArray(arguments);
	        var zipped = _.zip.apply(_, vecs);
	        var multiplied = _.map(zipped, arrayProduct);
	        return arraySum(multiplied);
	    },

	    /* vector-add multiple [x, y] coords/vectors
	     *
	     * kvector.add([1, 2], [3, 4]) -> [4, 6]
	     */
	    add: function() {
	        var points = _.toArray(arguments);
	        var zipped = _.zip.apply(_, points);
	        return _.map(zipped, arraySum);
	    },

	    subtract: function(v1, v2) {
	        return _.map(_.zip(v1, v2), function(dim) {
	            return dim[0] - dim[1];
	        });
	    },

	    negate: function(v) {
	        return _.map(v, function(x) {
	            return -x;
	        });
	    },

	    // Scale a vector
	    scale: function(v1, scalar) {
	        return _.map(v1, function(x) {
	            return x * scalar;
	        });
	    },

	    equal: function(v1, v2, tolerance) {
	        // _.zip will nicely deal with the lengths, going through
	        // the length of the longest vector. knumber.equal then
	        // returns false for any number compared to the undefined
	        // passed in if one of the vectors is shorter.
	        return _.all(_.zip(v1, v2), function(pair) {
	            return knumber.equal(pair[0], pair[1], tolerance);
	        });
	    },

	    codirectional: function(v1, v2, tolerance) {
	        // The origin is trivially codirectional with all other vectors.
	        // This gives nice semantics for codirectionality between points when
	        // comparing their difference vectors.
	        if (knumber.equal(kvector.length(v1), 0, tolerance) ||
	                knumber.equal(kvector.length(v2), 0, tolerance)) {
	            return true;
	        }

	        v1 = kvector.normalize(v1);
	        v2 = kvector.normalize(v2);

	        return kvector.equal(v1, v2, tolerance);
	    },

	    collinear: function(v1, v2, tolerance) {
	        return kvector.codirectional(v1, v2, tolerance) ||
	                kvector.codirectional(v1, kvector.negate(v2), tolerance);
	    },

	    // Convert a cartesian coordinate into a radian polar coordinate
	    polarRadFromCart: function(v) {
	        var radius = kvector.length(v);
	        var theta = Math.atan2(v[1], v[0]);

	        // Convert angle range from [-pi, pi] to [0, 2pi]
	        if (theta < 0) {
	            theta += 2 * Math.PI;
	        }

	        return [radius, theta];
	    },

	    // Converts a cartesian coordinate into a degree polar coordinate
	    polarDegFromCart: function(v) {
	        var polar = kvector.polarRadFromCart(v);
	        return [polar[0], polar[1] * 180 / Math.PI];
	    },

	    /* Convert a polar coordinate into a cartesian coordinate
	     *
	     * Examples:
	     * cartFromPolarRad(5, Math.PI)
	     * cartFromPolarRad([5, Math.PI])
	     */
	    cartFromPolarRad: function(radius, theta) {
	        if (_.isUndefined(theta)) {
	            theta = radius[1];
	            radius = radius[0];
	        }

	        return [radius * Math.cos(theta), radius * Math.sin(theta)];
	    },

	    /* Convert a polar coordinate into a cartesian coordinate
	     *
	     * Examples:
	     * cartFromPolarDeg(5, 30)
	     * cartFromPolarDeg([5, 30])
	     */
	    cartFromPolarDeg: function(radius, theta) {
	        if (_.isUndefined(theta)) {
	            theta = radius[1];
	            radius = radius[0];
	        }

	        return kvector.cartFromPolarRad(radius, theta * Math.PI / 180);
	    },

	    // Rotate vector
	    rotateRad: function(v, theta) {
	        var polar = kvector.polarRadFromCart(v);
	        var angle = polar[1] + theta;
	        return kvector.cartFromPolarRad(polar[0], angle);
	    },

	    rotateDeg: function(v, theta) {
	        var polar = kvector.polarDegFromCart(v);
	        var angle = polar[1] + theta;
	        return kvector.cartFromPolarDeg(polar[0], angle);
	    },

	    // Angle between two vectors
	    angleRad: function(v1, v2) {
	        return Math.acos(kvector.dot(v1, v2) /
	            (kvector.length(v1) * kvector.length(v2)));
	    },

	    angleDeg: function(v1, v2) {
	        return kvector.angleRad(v1, v2) * 180 / Math.PI;
	    },

	    // Vector projection of v1 onto v2
	    projection: function(v1, v2) {
	        var scalar = kvector.dot(v1, v2) / kvector.dot(v2, v2);
	        return kvector.scale(v2, scalar);
	    },

	    // Round each number to a certain number of decimal places
	    round: function(vec, precision) {
	        return _.map(vec, function(elem, i) {
	            return knumber.round(elem, precision[i] || precision);
	        });
	    },

	    // Round each number to the nearest increment
	    roundTo: function(vec, increment) {
	        return _.map(vec, function(elem, i) {
	            return knumber.roundTo(elem, increment[i] || increment);
	        });
	    },

	    floorTo: function(vec, increment) {
	        return _.map(vec, function(elem, i) {
	            return knumber.floorTo(elem, increment[i] || increment);
	        });
	    },

	    ceilTo: function(vec, increment) {
	        return _.map(vec, function(elem, i) {
	            return knumber.ceilTo(elem, increment[i] || increment);
	        });
	    }
	};

	module.exports = kvector;



/***/ },
/* 117 */
/***/ function(module, exports, __webpack_require__) {

	/*
	 * Point Utils
	 * A point is an array of two numbers e.g. [0, 0].
	 */

	var _ = __webpack_require__(60);
	var kvector = __webpack_require__(116);
	var knumber = __webpack_require__(115);

	var kpoint = {

	    // Rotate point (around origin unless a center is specified)
	    rotateRad: function(point, theta, center) {
	        if (center === undefined) {
	            return kvector.rotateRad(point, theta);
	        } else {
	            return kvector.add(
	                center,
	                kvector.rotateRad(
	                    kvector.subtract(point, center),
	                    theta
	                )
	            );
	        }
	    },

	    rotateDeg: function(point, theta, center) {
	        if (center === undefined) {
	            return kvector.rotateDeg(point, theta);
	        } else {
	            return kvector.add(
	                center,
	                kvector.rotateDeg(
	                    kvector.subtract(point, center),
	                    theta
	                )
	            );
	        }
	    },

	    // Distance between two points
	    distanceToPoint: function(point1, point2) {
	        return kvector.length(kvector.subtract(point1, point2));
	    },

	    // Distance between point and line
	    distanceToLine: function(point, line) {
	        var lv = kvector.subtract(line[1], line[0]);
	        var pv = kvector.subtract(point, line[0]);
	        var projectedPv = kvector.projection(pv, lv);
	        var distancePv = kvector.subtract(projectedPv, pv);
	        return kvector.length(distancePv);
	    },

	    // Reflect point over line
	    reflectOverLine: function(point, line) {
	        var lv = kvector.subtract(line[1], line[0]);
	        var pv = kvector.subtract(point, line[0]);
	        var projectedPv = kvector.projection(pv, lv);
	        var reflectedPv = kvector.subtract(kvector.scale(projectedPv, 2), pv);
	        return kvector.add(line[0], reflectedPv);
	    },

	    /**
	     * Compares two points, returning -1, 0, or 1, for use with
	     * Array.prototype.sort
	     *
	     * Note: This technically doesn't satisfy the total-ordering
	     * requirements of Array.prototype.sort unless equalityTolerance
	     * is 0. In some cases very close points that compare within a
	     * few equalityTolerances could appear in the wrong order.
	     */
	    compare: function(point1, point2, equalityTolerance) {
	        if (point1.length !== point2.length) {
	            return point1.length - point2.length;
	        }
	        for (var i = 0; i < point1.length; i++) {
	            if (!knumber.equal(point1[i], point2[i], equalityTolerance)) {
	                return point1[i] - point2[i];
	            }
	        }
	        return 0;
	    }
	};

	_.extend(kpoint, {
	    // Check if a value is a point
	    is: kvector.is,

	    // Add and subtract vector(s)
	    addVector: kvector.add,
	    addVectors: kvector.add,
	    subtractVector: kvector.subtract,
	    equal: kvector.equal,

	    // Convert from cartesian to polar and back
	    polarRadFromCart: kvector.polarRadFromCart,
	    polarDegFromCart: kvector.polarDegFromCart,
	    cartFromPolarRad: kvector.cartFromPolarRad,
	    cartFromPolarDeg: kvector.cartFromPolarDeg,

	    // Rounding
	    round: kvector.round,
	    roundTo: kvector.roundTo,
	    floorTo: kvector.floorTo,
	    ceilTo: kvector.ceilTo
	});

	module.exports = kpoint;



/***/ },
/* 118 */
/***/ function(module, exports, __webpack_require__) {

	/*
	 * Line Utils
	 * A line is an array of two points e.g. [[-5, 0], [5, 0]].
	 */

	var kpoint = __webpack_require__(117);
	var kvector = __webpack_require__(116);

	var kline = {

	    distanceToPoint: function(line, point) {
	        return kpoint.distanceToLine(point, line);
	    },

	    reflectPoint: function(line, point) {
	        return kpoint.reflectOverLine(point, line);
	    },

	    midpoint: function(line) {
	        return [
	            (line[0][0] + line[1][0]) / 2,
	            (line[0][1] + line[1][1]) / 2
	        ];
	    },

	    equal: function(line1, line2, tolerance) {
	        // TODO: A nicer implementation might just check collinearity of
	        // vectors using underscore magick
	        // Compare the directions of the lines
	        var v1 = kvector.subtract(line1[1],line1[0]);
	        var v2 = kvector.subtract(line2[1],line2[0]);
	        if (!kvector.collinear(v1, v2, tolerance)) {
	            return false;
	        }
	        // If the start point is the same for the two lines, then they are the same
	        if (kpoint.equal(line1[0], line2[0])) {
	            return true;
	        }
	        // Make sure that the direction to get from line1 to
	        // line2 is the same as the direction of the lines
	        var line1ToLine2Vector = kvector.subtract(line2[0], line1[0]);
	        return kvector.collinear(v1, line1ToLine2Vector, tolerance);
	    }
	};

	module.exports = kline;



/***/ },
/* 119 */
/***/ function(module, exports, __webpack_require__) {

	/*
	 * Ray Utils
	 * A ray is an array of an endpoint and another point along the ray.
	 * For example, [[0, 0], [1, 0]] is the ray starting at the origin and
	 * traveling along the positive x-axis.
	 */

	var kvector = __webpack_require__(116);
	var kpoint = __webpack_require__(117);

	var kray = {

	    equal: function(ray1, ray2, tolerance) {
	        // Compare the directions of the rays
	        var v1 = kvector.subtract(ray1[1],ray1[0]);
	        var v2 = kvector.subtract(ray2[1],ray2[0]);

	        var sameOrigin = kpoint.equal(ray1[0], ray2[0]);
	        var codirectional = kvector.codirectional(v1, v2, tolerance);

	        return sameOrigin && codirectional;
	    }
	};

	module.exports = kray;



/***/ },
/* 120 */
/***/ function(module, exports, __webpack_require__) {

	var isObject = function(obj) {
	    return obj === Object(obj);;
	}

	var merge = function() {
	    var obj = {};

	    for (var i = 0; i < arguments.length; i++) {
	        var source = arguments[i];
	        if (source) {
	            for (var prop in source) {
	                obj[prop] = source[prop];
	            }
	        }
	    }

	    return obj;
	};

	var clone = function(obj) {
	    if (!isObject(obj)) {
	        return obj;
	    }

	    return Array.isArray(obj) ? obj.slice() : merge(obj);
	};

	module.exports = { isObject: isObject, merge: merge, clone: clone };


/***/ },
/* 121 */
/***/ function(module, exports, __webpack_require__) {

	var get = function(arr, monocle) {
	    return arr[monocle];
	};

	var set = function(arr, monocle, val) {
	    var newArr = arr.splice();
	    newArr[monocle] = val;
	    return newArr;
	};

	var mod = function(arr, monocle, f) {
	    var newArr = arr.slice();
	    newArr[monocle] = f(arr[monocle]);
	    return newArr;
	};

	var del = function(arr, monocle) {
	    var newArr = arr.slice();
	    newArr.splice(monocle, 1);
	    return newArr;
	};

	/*
	// Lens must point to a member of an array. We'll insert into that array.
	lens.prototype.insertAt = function(lensArr, toInsert) {
	    var obj = this._wrapped;

	    var arrLens = lensArr.slice(0, -1);
	    var arr = lens(obj).get(arrLens).slice(); // slice to copy

	    var arrIdx = lensArr[lensArr.length-1];
	    arr.splice(arrIdx, 0, toInsert);
	    return lens(obj).set(arrLens, arr);
	};

	lens.prototype.insertBefore = lens.prototype.insertAt;
	lens.prototype.insertAfter = function(lensArr, toInsert) {
	    var newLens = lensArr.slice();
	    newLens[newLens.length-1] += 1;
	    return lens(this._wrapped).insertAt(newLens, toInsert);
	};
	*/

	module.exports = { get: get, set: set, mod: mod, del: del };


/***/ },
/* 122 */
/***/ function(module, exports, __webpack_require__) {

	var clone = __webpack_require__(120).clone;

	var get = function(obj, monocle) {
	    return obj[monocle];
	};

	var set = function(obj, monocle, val) {
	    var newObj = clone(obj);
	    newObj[monocle] = val;
	    return newObj;
	};

	var mod = function(obj, monocle, f) {
	    var newObj = clone(obj);
	    newObj[monocle] = f(obj[monocle]);
	    return newObj;
	};

	var del = function(obj, monocle) {
	    var newObj = clone(obj);
	    delete newObj[monocle];
	    return newObj;
	};

	module.exports = { get: get, set: set, mod: mod, del: del };


/***/ },
/* 123 */
/***/ function(module, exports, __webpack_require__) {

	var get = function(arr, monocle) {
	    return arr[monocle];
	};

	var set = function(arr, monocle, val) {
	    var newArr = arr.splice();
	    newArr[monocle] = val;
	    return newArr;
	};

	var mod = function(arr, monocle, f) {
	    var newArr = arr.splice();
	    newArr[monocle] = f(arr[monocle]);
	    return newArr;
	};

	var del = function(arr, monocle) {
	    var newArr = arr.slice();
	    newArr.splice(monocle);
	    return newArr;
	};

	module.exports = { get: get, set: set, mod: mod, del: del };


/***/ },
/* 124 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * A library of options to pass to add/draw/remove/constraints
	 */
	var _ = __webpack_require__(60);

	var WrappedEllipse = __webpack_require__(125);
	var kpoint = __webpack_require__(103).point;

	var add = {
	    constrain: function() {
	        this.constrain();
	    }
	};

	add.standard = [add.constrain];


	var modify = {
	    draw: function() {
	        this.draw();
	    }
	};

	modify.standard = [modify.draw];


	var draw = {
	    basic: function(state, prevState) {
	        var graphie = this.graphie;
	        if (!this.state.visibleShape) {
	            var radii = [
	                this.pointSize() / graphie.scale[0],
	                this.pointSize() / graphie.scale[1]
	            ];
	            var options = {
	                maxScale: Math.max(
	                    this.highlightStyle().scale, this.normalStyle().scale)
	            };
	            this.state.visibleShape = new WrappedEllipse(graphie, this.coord(),
	                radii, options);
	            this.state.visibleShape.attr(_.omit(this.normalStyle(), "scale"));
	            this.state.visibleShape.toFront();

	            // Keep mouseTarget in front of visible shape
	            if (this.mouseTarget()) {
	                this.mouseTarget().toFront();
	            }
	        }
	        if (state.normalStyle !== prevState.normalStyle &&
	                !_.isEqual(state.normalStyle, prevState.normalStyle)) {
	            this.state.visibleShape.attr(this.normalStyle());
	        }

	        this.state.visibleShape.moveTo(this.coord());
	        if (this.mouseTarget()) {
	            this.mouseTarget().moveTo(this.coord());
	        }
	    },

	    highlight: function(state, prevState) {
	        if (state.isHovering && !prevState.isHovering) {
	            state.visibleShape.animate(
	                this.highlightStyle(),
	                50
	            );
	        } else if (!state.isHovering && prevState.isHovering) {
	            state.visibleShape.animate(
	                this.normalStyle(),
	                50
	            );
	        }
	    }
	};

	draw.standard = [draw.basic, draw.highlight];


	var remove = {
	    basic: function() {
	        if (this.state.visibleShape) {
	            this.state.visibleShape.remove();
	            this.state.visibleShape = null;
	        }
	    }
	};

	remove.standard = remove.basic;


	var constraints = {
	    fixed: function() {
	        return function() { return false; };
	    },

	    snap: function(snap) {
	        return function(coord) {
	            if (snap === null) {
	                return true;
	            }
	            snap = snap || this.graphie.snap;
	            return kpoint.roundTo(coord, snap);
	        };
	    },

	    bound: function(range, snap, paddingPx) {
	        if (paddingPx === undefined) {
	            if (range === undefined) {
	                paddingPx = 10;
	            } else {
	                paddingPx = 0;
	            }
	        }
	        return function(coord) {
	            var graphie = this.graphie;
	            range = range || graphie.range;
	            if (snap === undefined) {
	                snap = graphie.snap;
	            }

	            var lower = graphie.unscalePoint([
	                paddingPx,
	                graphie.ypixels - paddingPx
	            ]);
	            var upper = graphie.unscalePoint([
	                graphie.xpixels - paddingPx,
	                paddingPx
	            ]);
	            if (snap) {
	                lower = kpoint.ceilTo(lower, snap);
	                upper = kpoint.floorTo(upper, snap);
	            }
	            var coordX = Math.max(lower[0], Math.min(upper[0], coord[0]));
	            var coordY = Math.max(lower[1], Math.min(upper[1], coord[1]));
	            return [coordX, coordY];
	        };
	    }
	};

	constraints.standard = null;

	module.exports = {
	    add: add,
	    modify: modify,
	    draw: draw,
	    remove: remove,

	    onMoveStart: {standard: null},
	    constraints: constraints,
	    onMove: {standard: null},
	    onMoveEnd: {standard: null},
	    onClick: {standard: null}
	};


/***/ },
/* 125 */
/***/ function(module, exports, __webpack_require__) {

	var _ = __webpack_require__(60);
	var WrappedDefaults = __webpack_require__(131);

	var DEFAULT_OPTIONS = {
	    maxScale: 1,
	    mouselayer: false
	};

	var WrappedEllipse = function(graphie, center, radii, options) {
	    options = _.extend({}, DEFAULT_OPTIONS, options);

	    // Add `wrapper`, `visibleShape`, and remaining properties
	    _.extend(this, graphie.fixedEllipse(center, radii, options.maxScale), {
	        graphie: graphie,
	        initialPoint: center
	    });

	    // Add to appropriate graphie layer
	    if (options.mouselayer) {
	        this.graphie.addToMouseLayerWrapper(this.wrapper);
	    } else {
	        this.graphie.addToVisibleLayerWrapper(this.wrapper);
	    }
	};

	_.extend(WrappedEllipse.prototype,  WrappedDefaults);

	module.exports = WrappedEllipse;


/***/ },
/* 126 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * A library of options to pass to add/draw/remove/constraints
	 */
	var _ = __webpack_require__(60);
	var WrappedLine = __webpack_require__(127);
	var WrappedPath = __webpack_require__(132);
	var kvector = __webpack_require__(103).vector;
	var kpoint = __webpack_require__(103).point;

	/**
	 * Helper functions
	 */
	var getScaledAngle = function(line) {
	    var scaledA = line.graphie.scalePoint(line.coord(0));
	    var scaledZ = line.graphie.scalePoint(line.coord(1));
	    return kvector.polarDegFromCart(
	        kvector.subtract(
	            scaledZ,
	            scaledA
	        )
	    )[1];
	};

	// Given `coord` and `angle`, find the point where a line extended
	// from `coord` in the direction of `angle` would be clipped by the
	// edge of the graphie canvas. Then draw an arrowhead at that point
	// pointing in the direction of `angle`.
	var getClipPoint = function(graph, coord, angle) {
	    // Actually put the arrowheads 4px from the edge so they have
	    // a bit of room
	    var xExtent = graph.range[0][1] - graph.range[0][0];
	    var yExtent = graph.range[1][1] - graph.range[1][0];

	    // shoot a point off into the distance ...
	    var distance = xExtent + yExtent;
	    // we need to scale the point according to the scale of the axes
	    var angleVec = graph.unscaleVector(
	        kvector.cartFromPolarDeg([1, angle])
	    );
	    var distVec = kvector.scale(
	        kvector.normalize(angleVec),
	        distance
	    );
	    var farCoord = kvector.add(coord, distVec);
	    var scaledAngle = kvector.polarDegFromCart(angleVec)[1];
	    // ... and then bring it back
	    var clipPoint = graph.constrainToBoundsOnAngle(farCoord, 4,
	                                  scaledAngle * Math.PI / 180);
	    return clipPoint;
	};

	// Given `coord` and `angle`, find the point where a line extended
	// from `coord` in the direction of `angle` would be clipped by the
	// edge of the graphie canvas. Then draw an arrowhead at that point
	// pointing in the direction of `angle`.
	var createArrow = function(graph, style) {
	    // Points that define the arrowhead
	    var center = [0.75, 0];
	    var points = [
	        [-3, 4],
	        [-2.75, 2.5],
	        [0, 0.25],
	        center,
	        [0, -0.25],
	        [-2.75, -2.5],
	        [-3, -4]
	    ];

	    // Scale points by 1.4 around (0.75, 0)
	    var scale = 1.4;
	    points = _.map(points, function(point) {
	        var pv = kvector.subtract(point, center);
	        var pvScaled = kvector.scale(pv, scale);
	        return kvector.add(center, pvScaled);
	    });

	    // We can't just pass in a path to `graph.fixedPath` as we need to modify
	    // the points in some way, so instead we provide a function for creating
	    // the path once the points have been transformed
	    var createCubicPath = function(points) {
	        var path = "M" + points[0][0] + " " + points[0][1];
	        for (var i = 1; i < points.length; i += 3) {
	            path += "C" + points[i][0] + " " + points[i][1] + " " +
	                          points[i + 1][0] + " " + points[i + 1][1] + " " +
	                          points[i + 2][0] + " " + points[i + 2][1];
	        }
	        return path;
	    };

	    // Create arrowhead
	    var unscaledPoints = _.map(points, graph.unscalePoint);
	    var options = {
	        center: graph.unscalePoint(center),
	        createPath: createCubicPath
	    };
	    var arrowHead = new WrappedPath(graph, unscaledPoints, options);
	    arrowHead.attr(_.extend({
	        "stroke-linejoin": "round",
	        "stroke-linecap": "round",
	        "stroke-dasharray": ""
	    }, style));

	    // Add custom function for transforming arrowheads that accounts for
	    // center, scaling, etc.
	    arrowHead.toCoordAtAngle = function(coord, angle) {
	        var clipPoint = graph.scalePoint(getClipPoint(graph, coord, angle));
	        arrowHead.transform(
	            "translateX(" + (clipPoint[0] + scale * center[0]) + "px) " +
	            "translateY(" + (clipPoint[1] + scale * center[1]) + "px) " +
	            "translateZ(0) " +
	            "rotate(" + (360 - KhanUtil.bound(angle)) + "deg)");
	    };

	    return arrowHead;
	};


	/**
	 * MovableLine option functions
	 */
	var add = {
	    // We do this in add as well as in standard so that we can call
	    // pointsToFront after the first draw (which adds `this.visibleShape`)
	    draw: function() {
	        this.draw();
	    },

	    pointsToFront: function(state) {
	        _.invoke(state.points, "toFront");
	    }
	};

	add.standard = [add.draw, add.pointsToFront];

	var modify = {
	    draw: function() {
	        this.draw();
	    }
	};

	modify.standard = [modify.draw];

	var draw = {
	    basic: function(state) {
	        var graphie = this.graphie;
	        var start = this.coord(0);
	        var end = this.coord(1);

	        if (!this.state.visibleShape) {
	            var options = {
	                thickness: 10
	            };
	            this.state.visibleShape = new WrappedLine(graphie, start, end,
	                options);
	            this.state.visibleShape.attr(this.normalStyle());
	            this.state.visibleShape.toFront();

	            if (this.mouseTarget()) {
	                this.mouseTarget().toFront();
	            }
	        }

	        // Compute angle
	        var angle = getScaledAngle(this);

	        // Extend start, end if necessary (i.e., if not a line segment)
	        if (state.extendLine) {
	            start = getClipPoint(graphie, start, 360 - angle);
	            end = getClipPoint(graphie, end, (540 - angle) % 360);
	        } else if (state.extendRay) {
	            end = getClipPoint(graphie, end, 360 - angle);
	        }

	        // Move elements
	        var elements = [this.state.visibleShape];

	        if (this.mouseTarget()) {
	            elements.push(this.mouseTarget());
	        }
	        _.each(elements, function(element) {
	            element.moveTo(start, end);
	        });
	    },

	    arrows: function(state) {
	        // Create arrows, if not yet created
	        if (this._arrows == null) {
	            this._arrows = [];
	            if (state.extendLine) {
	                this._arrows.push(createArrow(
	                    this.graphie, this.normalStyle()));
	                this._arrows.push(createArrow(
	                    this.graphie, this.normalStyle()));
	            } else if (state.extendRay) {
	                this._arrows.push(createArrow(
	                    this.graphie, this.normalStyle()));
	            }
	        }

	        // Transform arrows
	        var angle = getScaledAngle(this);
	        var angleForArrow = [360 - angle, (540 - angle) % 360];
	        _.each(this._arrows, function(arrow, i) {
	            arrow.toCoordAtAngle(this.coord(i), angleForArrow[i]);
	        }, this);
	    },

	    highlight: function(state, prevState) {
	        // TODO(jack): Figure out a way to highlight the points attached to
	        // the line. Maybe this means an additional isHovering: []
	        // function to state of movable/movablepoint to define [additional?]
	        // times it should be highlighted
	        if (state.isHovering && !prevState.isHovering) {
	            state.visibleShape.animate(
	                state.highlightStyle,
	                50
	            );
	        } else if (!state.isHovering && prevState.isHovering) {
	            state.visibleShape.animate(
	                state.normalStyle,
	                50
	            );
	        }
	    }
	};

	draw.standard = [draw.basic, draw.arrows, draw.highlight];


	var remove = {
	    basic: function() {
	        if (this.state.visibleShape) {
	            this.state.visibleShape.remove();
	        }
	    },

	    arrows: function() {
	        if (this._arrows != null) {
	            _.invoke(this._arrows, "remove");
	        }
	        this._arrows = null;
	    }
	};

	remove.standard = [remove.basic, remove.arrows];


	var constraints = {
	    fixed: function() {
	        return function() { return false; };
	    },

	    snap: function(snap) {
	        return function(coord, prevCoord) {
	            if (snap === null) {
	                return true;
	            }
	            var delta = kvector.subtract(coord, prevCoord);
	            snap = snap || this.graphie.snap;
	            delta = kpoint.roundTo(delta, snap);
	            return kvector.add(prevCoord, delta);
	        };
	    },

	    bound: function(range, snap, paddingPx) {
	        if (paddingPx === undefined) {
	            if (range === undefined) {
	                paddingPx = 10;
	            } else {
	                paddingPx = 0;
	            }
	        }
	        return function(coord, prevCoord) {
	            var graphie = this.graphie;
	            var delta = kvector.subtract(coord, prevCoord);
	            var range = range || graphie.range;
	            // A null snap means no snap; an undefined snap means
	            // default to graphie's
	            if (snap === undefined) {
	                snap = graphie.snap;
	            }

	            // Calculate the bounds for both points
	            var absoluteLower = graphie.unscalePoint([
	                paddingPx,
	                graphie.ypixels - paddingPx
	            ]);
	            var absoluteUpper = graphie.unscalePoint([
	                graphie.xpixels - paddingPx,
	                paddingPx
	            ]);
	            if (snap) {
	                absoluteLower = kpoint.ceilTo(absoluteLower, snap);
	                absoluteUpper = kpoint.floorTo(absoluteUpper, snap);
	            }

	            // Calculate the bounds for the delta.
	            var deltaBounds = _.map(this.coords(), function(coord, i) {
	                var max = kvector.subtract(absoluteUpper, coord);
	                var min = kvector.subtract(absoluteLower, coord);
	                return [min, max];
	            });

	            // bound the delta by the calculated bounds
	            var boundedDelta = _.reduce(deltaBounds,
	                    function(delta, bound) {
	                var lower = bound[0];
	                var upper = bound[1];
	                var deltaX = Math.max(lower[0], Math.min(upper[0], delta[0]));
	                var deltaY = Math.max(lower[1], Math.min(upper[1], delta[1]));
	                return [deltaX, deltaY];
	            }, delta);

	            return kvector.add(prevCoord, boundedDelta);
	        };
	    }
	};

	constraints.standard = null;


	var onMove = {
	    updatePoints: function(coord, prevCoord) {
	        var actualDelta = kvector.subtract(coord, prevCoord);
	        _.each(this.state.points, function(point) {
	            point.setCoord(kvector.add(
	                point.coord(),
	                actualDelta
	            ));
	        });
	    }
	};

	onMove.standard = null;


	module.exports = {
	    add: add,
	    modify: modify,
	    draw: draw,
	    remove: remove,

	    onMoveStart: {standard: null},
	    constraints: constraints,
	    onMove: onMove,
	    onMoveEnd: {standard: null},
	};


/***/ },
/* 127 */
/***/ function(module, exports, __webpack_require__) {

	var _ = __webpack_require__(60);
	var InteractiveUtil = __webpack_require__(89);
	var WrappedDefaults = __webpack_require__(131);
	var kpoint = __webpack_require__(103).point;
	var kvector = __webpack_require__(103).vector;

	var DEFAULT_OPTIONS = {
	    thickness: 2,
	    mouselayer: false
	};

	var WrappedLine = function(graphie, start, end, options) {
	    options = _.extend({}, DEFAULT_OPTIONS, options);

	    // Always make the line as large as possible and horizontal; this
	    // simplifies a lot of the transforms, e.g., we can rotate by exactly the
	    // angle of the argument points in `moveTo`.
	    var initialStart = [graphie.range[0][0], 0];
	    var initialEnd = [graphie.range[0][1], 0];

	    // Add `wrapper` and `visibleShape`
	    _.extend(this,
	        graphie.fixedLine(initialStart, initialEnd, options.thickness));

	    // Save properties for computing transformations
	    _.extend(this, {
	        graphie: graphie,
	        initialPoint: graphie.scalePoint(initialStart),
	        initialLength: kpoint.distanceToPoint(
	            graphie.scalePoint(initialStart),
	            graphie.scalePoint(initialEnd)
	        )
	    });

	    // Add to appropriate graphie layer
	    if (options.mouselayer) {
	        this.graphie.addToMouseLayerWrapper(this.wrapper);
	    } else {
	        this.graphie.addToVisibleLayerWrapper(this.wrapper);
	    }

	    // Move to argument points
	    this.moveTo(start, end);
	};

	_.extend(WrappedLine.prototype, WrappedDefaults, {
	    getMouseTarget: function() {
	        return this.wrapper;
	    },

	    moveTo: function(start, end) {
	        var scaledStart = this.graphie.scalePoint(start);
	        var scaledEnd = this.graphie.scalePoint(end);

	        // Compute transformation parameters
	        var polarDiff = kvector.polarDegFromCart(
	            kvector.subtract(
	                scaledEnd,
	                scaledStart
	            )
	        );
	        var lineLength = polarDiff[0];
	        var angle = KhanUtil.bound(polarDiff[1]);
	        var delta = kvector.subtract(scaledStart, this.initialPoint);
	        var scale = KhanUtil.bound(lineLength / this.initialLength);

	        // Construct and apply transformation string
	        var do3dTransform = InteractiveUtil.getCanUse3dTransform();
	        var transformation = "translateX(" + delta[0] + "px) " +
	                             "translateY(" + delta[1] + "px) " +
	                             (do3dTransform ? " translateZ(0)" : "") +
	                             "rotate(" + angle + "deg) " +
	                             "scaleX(" + scale + ") scaleY(1)";
	        this.transform(transformation);
	    }
	});

	module.exports = WrappedLine;


/***/ },
/* 128 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * A library of options to pass to add/draw/remove/constraints
	 */

	var kpoint = __webpack_require__(103).point;
	var kvector = __webpack_require__(103).vector;

	function sum(array) {
	    return _.reduce(array, function(memo, arg) { return memo + arg; }, 0);
	}

	function clockwise(points) {
	    var segments = _.zip(points, points.slice(1).concat(points.slice(0, 1)));
	    var areas = _.map(segments, function(segment) {
	        var p1 = segment[0], p2 = segment[1];
	        return (p2[0] - p1[0]) * (p2[1] + p1[1]);
	    });
	    return sum(areas) > 0;
	}

	var add = {
	    constrain: function() {
	        this.constrain();
	    },

	    pointsToFront: function(state) {
	        _.invoke(state.points, "toFront");
	    }
	};

	add.standard = [add.constrain, add.pointsToFront];

	var modify = {
	    draw: function() {
	        this.draw();
	    }
	};

	modify.standard = [modify.draw];


	var draw = {
	    basic: function(state, prevState) {
	        var graphie = this.graphie;
	        var path = this.path(state);

	        if (!this.state.visibleShape) {
	            this.state.visibleShape = graphie.raphael.path(path);
	            this.state.visibleShape.attr(this.normalStyle());
	        }
	        if (state.normalStyle !== prevState.normalStyle &&
	                !_.isEqual(state.normalStyle, prevState.normalStyle)) {
	            this.state.visibleShape.attr(this.normalStyle());
	        }
	        this.state.visibleShape.attr({ path: path });
	        if (this.mouseTarget()) {
	            this.mouseTarget().attr({ path: path });
	        }
	    },

	    /* Labels are handled primarily by label objects, but sometimes require
	     * extra movables, e.g., for the arcs drawn at labeled angles. These extra
	     * movables are stored in the label cache. */
	    labels: function(state, prevState) {
	        var graphie = this.graphie;
	        var self = this;

	        var coords = _.invoke(state.points, "coord");
	        var isClockwise = clockwise(coords);
	        var n = coords.length;

	        // graphie.labelAngle and similar methods attempt to re-use the label
	        // provided, which will have been stored on state._labeledAngles.
	        // If they cannot re-use the label, they make a new one, which will
	        // get stored on state._labelCache. These will all be cleared out when
	        // we remove the polygon.
	        // (This logic is borrowed from graphie:addMovablePolygon.)
	        if (self.state._labelCache != null) {
	            _.invoke(self.state._labelCache, "remove");
	        }
	        self.state._labelCache = [];

	        // Update angle labels
	        if (state.angleLabels.length || state.showRightAngleMarkers.length) {
	            // Generate labels
	            if (self.state._labeledAngles == null) {
	                self.state._labeledAngles = _.times(
	                    Math.max(
	                        state.angleLabels.length,
	                        state.showRightAngleMarkers.length),
	                    function() {
	                        return graphie.label([0, 0], "", "center",
	                            state.labelStyle);
	                        }
	                    );
	            }

	            _.each(self.state._labeledAngles, function(label, i) {
	                self.state._labelCache.push(graphie.labelAngle({
	                    point1: coords[(i - 1 + n) % n],
	                    vertex: coords[i],
	                    point3: coords[(i + 1) % n],
	                    label: label,
	                    text: state.angleLabels[i],
	                    showRightAngleMarker: state.showRightAngleMarkers[i],
	                    numArcs: state.numArcs[i],
	                    clockwise: isClockwise,
	                    style: state.labelStyle
	                }));
	            });
	        }

	        // Update side labels
	        if (state.sideLabels.length) {
	            // Generate labels
	            if (self.state._labeledSides == null) {
	                self.state._labeledSides = _.map(state.sideLabels,
	                    function(label) {
	                        return graphie.label([0, 0], "", "center",
	                            state.labelStyle);
	                    }
	                );
	            }

	            _.each(self.state._labeledSides, function(label, i) {
	                self.state._labelCache.push(graphie.labelSide({
	                    point1: coords[i],
	                    point2: coords[(i + 1) % n],
	                    label: label,
	                    text: state.sideLabels[i],
	                    numArrows: state.numArrows[i],
	                    numTicks: state.numTicks[i],
	                    clockwise: isClockwise,
	                    style: state.labelStyle
	                }));
	            });
	        }

	        // Update vertex labels
	        if (state.vertexLabels.length) {
	            // Generate labels
	            if (self.state._labeledVertices == null) {
	                self.state._labeledVertices = _.map(state.vertexLabels,
	                    function(label) {
	                        return graphie.label([0, 0], "", "center",
	                            state.labelStyle);
	                    }
	                );
	            }

	            _.each(labeledVertices, function(label, i) {
	                self.state._labelCache.push(graphie.labelVertex({
	                    point1: coords[(i - 1 + n) % n],
	                    vertex: coords[i],
	                    point3: coords[(i + 1) % n],
	                    label: label,
	                    text: state.vertexLabels[i],
	                    clockwise: isClockwise,
	                    style: state.labelStyle
	                }));
	            });
	        }

	        // graphie.labelVertex and its peer methods return an array of movables
	        self.state._labelCache = _.flatten(self.state._labelCache);
	    },

	    highlight: function(state, prevState) {
	        if (state.isHovering && !prevState.isHovering) {
	            state.visibleShape.animate(
	                this.highlightStyle(),
	                50
	            );
	        } else if (!state.isHovering && prevState.isHovering) {
	            state.visibleShape.animate(
	                this.normalStyle(),
	                50
	            );
	        }
	    }
	};

	draw.standard = [draw.basic, draw.labels, draw.highlight];


	var remove = {
	    basic: function() {
	        if (this.state.visibleShape) {
	            this.state.visibleShape.remove();
	        }
	    },
	    labels: function() {
	        var labels = [this.state._labeledSides, this.state._labeledVertices,
	            this.state._labeledAngles, this.state._labelCache];

	        _.each(labels, function(labelType) {
	            if (labelType != null && labelType.length) {
	                _.invoke(labelType, "remove");
	            }
	        });
	    }
	};

	remove.standard = [remove.basic, remove.labels];


	var constraints = {
	    fixed: function() {
	        return function() { return false; };
	    },

	    snap: function(snap) {
	        return function(coord) {
	            if (snap === null) {
	                return true;
	            }
	            snap = snap || this.graphie.snap;
	            return kpoint.roundTo(coord, snap);
	        };
	    },

	    bound: function(range, snap, paddingPx) {
	        if (paddingPx === undefined) {
	            if (range === undefined) {
	                paddingPx = 10;
	            } else {
	                paddingPx = 0;
	            }
	        }
	        return function(coord, prevCoord) {
	            var graphie = this.graphie;
	            var delta = kvector.subtract(coord, prevCoord);
	            var range = range || graphie.range;
	            // A null snap means no snap; an undefined snap means
	            // default to graphie's
	            if (snap === undefined) {
	                snap = graphie.snap;
	            }

	            // Calculate the bounds for both points
	            var absoluteLower = graphie.unscalePoint([
	                paddingPx,
	                graphie.ypixels - paddingPx
	            ]);
	            var absoluteUpper = graphie.unscalePoint([
	                graphie.xpixels - paddingPx,
	                paddingPx
	            ]);
	            if (snap) {
	                absoluteLower = kpoint.ceilTo(absoluteLower, snap);
	                absoluteUpper = kpoint.floorTo(absoluteUpper, snap);
	            }

	            // Calculate the bounds for the delta.
	            var deltaBounds = _.map(this.coords(), function(coord, i) {
	                var max = kvector.subtract(absoluteUpper, coord);
	                var min = kvector.subtract(absoluteLower, coord);
	                return [min, max];
	            });

	            // bound the delta by the calculated bounds
	            var boundedDelta = _.reduce(deltaBounds,
	                    function(delta, bound) {
	                var lower = bound[0];
	                var upper = bound[1];
	                var deltaX = Math.max(lower[0], Math.min(upper[0], delta[0]));
	                var deltaY = Math.max(lower[1], Math.min(upper[1], delta[1]));
	                return [deltaX, deltaY];
	            }, delta);

	            return kvector.add(prevCoord, boundedDelta);
	        };
	    }
	};

	constraints.standard = null;


	var onMove = {
	    updatePoints: function(coord, prevCoord) {
	        var actualDelta = kvector.subtract(coord, prevCoord);
	        _.each(this.state.points, function(point) {
	            point.setCoord(kvector.add(
	                point.coord(),
	                actualDelta
	            ));
	        });
	    }
	};

	onMove.standard = null;


	module.exports = {
	    add: add,
	    modify: modify,
	    draw: draw,
	    remove: remove,

	    onMoveStart: {standard: null},
	    constraints: constraints,
	    onMove: onMove,
	    onMoveEnd: {standard: null},
	    onClick: {standard: null}
	};


/***/ },
/* 129 */
/***/ function(module, exports, __webpack_require__) {

	var isValidCSSProps = __webpack_require__(135);

	function isValidProp(prop) {
	  return isValidCSSProps(prop);
	}

	function isValidValue(value) {
	  return value != null && typeof value !== 'boolean' && value !== '';
	}

	module.exports = {
	  isValidProp: isValidProp,
	  isValidValue: isValidValue
	};


/***/ },
/* 130 */
/***/ function(module, exports, __webpack_require__) {

	var escape = __webpack_require__(136);

	var _uppercasePattern = /([A-Z])/g;

	function hyphenateProp(string) {
	  return string.replace(_uppercasePattern, '-$1').toLowerCase();
	}

	function escapeValueForProp(value, prop) {
	  // 'content' is a special property that must be quoted
	  if (prop === 'content') {
	    return '"' + value + '"';
	  }
	  return escape(value);
	}

	module.exports = {
	  hyphenateProp: hyphenateProp,
	  escapeValueForProp: escapeValueForProp
	};


/***/ },
/* 131 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Default methods for a wrapped movable.
	 */

	var _ = __webpack_require__(60);
	var InteractiveUtil = __webpack_require__(89);
	var objective_ = __webpack_require__(63);
	var kvector = __webpack_require__(103).vector;

	/*
	 * These functions, when called on the wrapped object, simply pass the
	 * arguments to the underlying Raphael object.
	 */
	var PASS_TO_RAPHAEL = [
	    "attr",
	    "animate"
	];

	var WrappedDefaults = _.extend({
	    transform: function(transformation) {
	        var prefixedTransform = InteractiveUtil.getPrefixedTransform();
	        this.wrapper.style[prefixedTransform] = transformation;
	    },

	    toFront: function () {
	        var parentNode = this.wrapper.parentNode;
	        parentNode.appendChild(this.wrapper);
	    },

	    toBack: function () {
	        var parentNode = this.wrapper.parentNode;
	        if (parentNode.firstChild !== this.wrapper) {
	            parentNode.insertBefore(
	                this.wrapper,
	                parentNode.firstChild
	            );
	        }
	    },

	    remove: function() {
	        this.visibleShape.remove();
	        $(this.wrapper).remove();
	    },

	    getMouseTarget: function() {
	        return this.visibleShape[0];
	    },

	    moveTo: function(point) {
	        var delta = kvector.subtract(
	            this.graphie.scalePoint(point),
	            this.graphie.scalePoint(this.initialPoint)
	        );
	        var do3dTransform = InteractiveUtil.getCanUse3dTransform();
	        var transformation = "translateX(" + delta[0] + "px) " +
	                             "translateY(" + delta[1] + "px)" +
	                             (do3dTransform ? " translateZ(0)" : "");
	        this.transform(transformation);
	    },

	    hide: function() {
	        this.visibleShape.hide();
	    },

	    show: function() {
	        this.visibleShape.show();
	    }
	}, objective_.mapObjectFromArray(PASS_TO_RAPHAEL, function(attribute) {
	    return function() {
	        this.visibleShape[attribute].apply(this.visibleShape, arguments);
	    };
	}));

	module.exports = WrappedDefaults;


/***/ },
/* 132 */
/***/ function(module, exports, __webpack_require__) {

	var _ = __webpack_require__(60);
	var WrappedDefaults = __webpack_require__(131);

	var DEFAULT_OPTIONS = {
	    center: null, // gets ignored in `graphie.fixedPath` if `null`
	    createPath: null, // gets defaulted in `graphie.fixedPath` if `null`
	    mouselayer: false
	};

	var WrappedPath = function(graphie, points, options) {
	    options = _.extend({}, DEFAULT_OPTIONS, options);

	    // Add `wrapper` and `visibleShape`
	    _.extend(this, graphie.fixedPath(points, options.center,
	        options.createPath));

	    // Add remaining properties
	    _.extend(this, {
	        graphie: graphie,
	        initialPoint: graphie.scalePoint(_.head(points))
	    });

	    // Add to appropriate graphie layer
	    if (options.mouselayer) {
	        this.graphie.addToMouseLayerWrapper(this.wrapper);
	    } else {
	        this.graphie.addToVisibleLayerWrapper(this.wrapper);
	    }
	};

	_.extend(WrappedPath.prototype, WrappedDefaults);

	module.exports = WrappedPath;


/***/ },
/* 133 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var baseCreateCallback = __webpack_require__(137),
	    keys = __webpack_require__(138),
	    objectTypes = __webpack_require__(139);

	/**
	 * Assigns own enumerable properties of source object(s) to the destination
	 * object. Subsequent sources will overwrite property assignments of previous
	 * sources. If a callback is provided it will be executed to produce the
	 * assigned values. The callback is bound to `thisArg` and invoked with two
	 * arguments; (objectValue, sourceValue).
	 *
	 * @static
	 * @memberOf _
	 * @type Function
	 * @alias extend
	 * @category Objects
	 * @param {Object} object The destination object.
	 * @param {...Object} [source] The source objects.
	 * @param {Function} [callback] The function to customize assigning values.
	 * @param {*} [thisArg] The `this` binding of `callback`.
	 * @returns {Object} Returns the destination object.
	 * @example
	 *
	 * _.assign({ 'name': 'fred' }, { 'employer': 'slate' });
	 * // => { 'name': 'fred', 'employer': 'slate' }
	 *
	 * var defaults = _.partialRight(_.assign, function(a, b) {
	 *   return typeof a == 'undefined' ? b : a;
	 * });
	 *
	 * var object = { 'name': 'barney' };
	 * defaults(object, { 'name': 'fred', 'employer': 'slate' });
	 * // => { 'name': 'barney', 'employer': 'slate' }
	 */
	var assign = function(object, source, guard) {
	  var index, iterable = object, result = iterable;
	  if (!iterable) return result;
	  var args = arguments,
	      argsIndex = 0,
	      argsLength = typeof guard == 'number' ? 2 : args.length;
	  if (argsLength > 3 && typeof args[argsLength - 2] == 'function') {
	    var callback = baseCreateCallback(args[--argsLength - 1], args[argsLength--], 2);
	  } else if (argsLength > 2 && typeof args[argsLength - 1] == 'function') {
	    callback = args[--argsLength];
	  }
	  while (++argsIndex < argsLength) {
	    iterable = args[argsIndex];
	    if (iterable && objectTypes[typeof iterable]) {
	    var ownIndex = -1,
	        ownProps = objectTypes[typeof iterable] && keys(iterable),
	        length = ownProps ? ownProps.length : 0;

	    while (++ownIndex < length) {
	      index = ownProps[ownIndex];
	      result[index] = callback ? callback(result[index], iterable[index]) : iterable[index];
	    }
	    }
	  }
	  return result
	};

	module.exports = assign;


/***/ },
/* 134 */
/***/ function(module, exports, __webpack_require__) {

	var every = __webpack_require__(140);

	function isValidRatio(ratio) {
	    var re = /\d+\/\d+/;
	    return !!ratio.match(re);
	}

	function isValidInteger(integer) {
	    var re = /\d+/;
	    return !!integer.match(re);
	}

	function isValidLength(length) {
	    var re = /\d+(?:ex|em|ch|rem|vh|vw|vmin|vmax|px|mm|cm|in|pt|pc)?$/;
	    return !!length.match(re);
	}

	function isValidOrientation(orientation) {
	    return orientation === 'landscape' || orientation === 'portrait';
	}

	function isValidScan(scan) {
	    return scan === 'progressive' || scan === 'interlace';
	}

	function isValidResolution(resolution) {
	    var re = /(?:\+|-)?(?:\d+|\d*\.\d+)(?:e\d+)?(?:dpi|dpcm|dppx)$/;
	    return !!resolution.match(re);
	}

	function isValidValue(value) {
	  return value != null && typeof value !== 'boolean' && value !== '';
	}

	var _mediaFeatureValidator = {
	    'width': isValidLength,
	    'min-width': isValidLength,
	    'max-width': isValidLength,
	    'height': isValidLength,
	    'min-height': isValidLength,
	    'max-height': isValidLength,
	    'device-width': isValidLength,
	    'min-device-width': isValidLength,
	    'max-device-width': isValidLength,
	    'device-height': isValidLength,
	    'min-device-height': isValidLength,
	    'max-device-height': isValidLength,
	    'aspect-ratio': isValidRatio,
	    'min-aspect-ratio': isValidRatio,
	    'max-aspect-ratio': isValidRatio,
	    'device-aspect-ratio': isValidRatio,
	    'min-device-aspect-ratio': isValidRatio,
	    'max-device-aspect-ratio': isValidRatio,
	    'color': isValidValue,
	    'min-color': isValidValue,
	    'max-color': isValidValue,
	    'color-index': isValidInteger,
	    'min-color-index': isValidInteger,
	    'max-color-index': isValidInteger,
	    'monochrome': isValidInteger,
	    'min-monochrome': isValidInteger,
	    'max-monochrome': isValidInteger,
	    'resolution': isValidResolution,
	    'min-resolution': isValidResolution,
	    'max-resolution': isValidResolution,
	    'scan': isValidScan,
	    'grid': isValidInteger,
	    'orientation': isValidOrientation
	};

	var _validMediaFeatures = {
	    'width': true,
	    'min-width': true,
	    'max-width': true,
	    'height': true,
	    'min-height': true,
	    'max-height': true,
	    'device-width': true,
	    'min-device-width': true,
	    'max-device-width': true,
	    'device-height': true,
	    'min-device-height': true,
	    'max-device-height': true,
	    'aspect-ratio': true,
	    'min-aspect-ratio': true,
	    'max-aspect-ratio': true,
	    'device-aspect-ratio': true,
	    'min-device-aspect-ratio': true,
	    'max-device-aspect-ratio': true,
	    'color': true,
	    'min-color': true,
	    'max-color': true,
	    'color-index': true,
	    'min-color-index': true,
	    'max-color-index': true,
	    'monochrome': true,
	    'min-monochrome': true,
	    'max-monochrome': true,
	    'resolution': true,
	    'min-resolution': true,
	    'max-resolution': true,
	    'scan': true,
	    'grid': true,
	    'orientation': true
	};

	var _validMediaTypes = {
	    'all': true,
	    'aural': true,
	    'braille': true,
	    'handheld': true,
	    'print': true,
	    'projection': true,
	    'screen': true,
	    'tty': true,
	    'tv': true,
	    'embossed': true
	};

	var _validQualifiers = {
	    'not': true,
	    'only': true
	};

	function isValidFeature(feature) {
	    return !!_validMediaFeatures[feature];
	}

	function isValidQualifier(qualifier) {
	    return !!_validQualifiers[qualifier];
	}

	function isValidMediaType(mediaType) {
	    return !!_validMediaTypes[mediaType];
	}

	function isValidQualifiedMediaType(mediaType) {
	    var terms = mediaType.trim().split(/\s+/);
	    switch (terms.length) {
	        case 1:
	            return isValidMediaType(terms[0]);
	        case 2:
	            return isValidQualifier(terms[0]) && isValidMediaType(terms[1]);
	        default:
	            return false;
	    }
	}

	function isValidExpression(expression) {
	    if (expression.length < 2) {
	        return false;
	    }

	    // Parentheses are required around expressions
	    if (expression[0] !== '(' || expression[expression.length - 1] !== ')') {
	        return false;
	    }

	    // Remove parentheses and spacess
	    expression = expression.substring(1, expression.length - 1);

	    // Is there a value to accompany the media feature?
	    var featureAndValue = expression.split(/\s*:\s*/);
	    switch (featureAndValue.length) {
	        case 1:
	            var feature = featureAndValue[0].trim();
	            return isValidFeature(feature);
	        case 2:
	            var feature = featureAndValue[0].trim();
	            var value = featureAndValue[1].trim();
	            return isValidFeature(feature) &&
	                   _mediaFeatureValidator[feature](value);
	        default:
	            return false;
	    }
	}

	function isValidMediaQuery(query) {
	    var andSplitter = /\s+and\s+/;
	    var queryTerms = query.split(andSplitter);
	    return (isValidQualifiedMediaType(queryTerms[0]) ||
	        isValidExpression(queryTerms[0])) &&
	        every(queryTerms.slice(1), isValidExpression);
	}

	function isValidMediaQueryList(mediaQuery) {
	    mediaQuery = mediaQuery.toLowerCase();

	    if (mediaQuery.substring(0, 6) !== '@media') {
	        return false;
	    }

	    var commaSplitter = /\s*,\s*/;
	    var queryList = mediaQuery.substring(7, mediaQuery.length)
	                              .split(commaSplitter);
	    return every(queryList, isValidMediaQuery);
	}

	module.exports = isValidMediaQueryList


/***/ },
/* 135 */
/***/ function(module, exports, __webpack_require__) {

	var _validCSSProps = {
	  'alignment-adjust': true,
	  'alignment-baseline': true,
	  'animation': true,
	  'animation-delay': true,
	  'animation-direction': true,
	  'animation-duration': true,
	  'animation-iteration-count': true,
	  'animation-name': true,
	  'animation-play-state': true,
	  'animation-timing-function': true,
	  'appearance': true,
	  'backface-visibility': true,
	  'background': true,
	  'background-attachment': true,
	  'background-clip': true,
	  'background-color': true,
	  'background-image': true,
	  'background-origin': true,
	  'background-position': true,
	  'background-repeat': true,
	  'background-size': true,
	  'baseline-shift': true,
	  'bookmark-label': true,
	  'bookmark-level': true,
	  'bookmark-target': true,
	  'border': true,
	  'border-bottom': true,
	  'border-bottom-color': true,
	  'border-bottom-left-radius': true,
	  'border-bottom-right-radius': true,
	  'border-bottom-style': true,
	  'border-bottom-width': true,
	  'border-collapse': true,
	  'border-color': true,
	  'border-image': true,
	  'border-image-outset': true,
	  'border-image-repeat': true,
	  'border-image-slice': true,
	  'border-image-source': true,
	  'border-image-width': true,
	  'border-left': true,
	  'border-left-color': true,
	  'border-left-style': true,
	  'border-left-width': true,
	  'border-radius': true,
	  'border-right': true,
	  'border-right-color': true,
	  'border-right-style': true,
	  'border-right-width': true,
	  'border-spacing': true,
	  'border-style': true,
	  'border-top': true,
	  'border-top-color': true,
	  'border-top-left-radius': true,
	  'border-top-right-radius': true,
	  'border-top-style': true,
	  'border-top-width': true,
	  'border-width': true,
	  'bottom': true,
	  'box-align': true,
	  'box-decoration-break': true,
	  'box-direction': true,
	  'box-flex': true,
	  'box-flex-group': true,
	  'box-lines': true,
	  'box-ordinal-group': true,
	  'box-orient': true,
	  'box-pack': true,
	  'box-shadow': true,
	  'box-sizing': true,
	  'caption-side': true,
	  'clear': true,
	  'clip': true,
	  'color': true,
	  'color-profile': true,
	  'column-count': true,
	  'column-fill': true,
	  'column-gap': true,
	  'column-rule': true,
	  'column-rule-color': true,
	  'column-rule-style': true,
	  'column-rule-width': true,
	  'column-span': true,
	  'column-width': true,
	  'columns': true,
	  'content': true,
	  'counter-increment': true,
	  'counter-reset': true,
	  'crop': true,
	  'cursor': true,
	  'direction': true,
	  'display': true,
	  'dominant-baseline': true,
	  'drop-initial-after-adjust': true,
	  'drop-initial-after-align': true,
	  'drop-initial-before-adjust': true,
	  'drop-initial-before-align': true,
	  'drop-initial-size': true,
	  'drop-initial-value': true,
	  'empty-cells': true,
	  'fit': true,
	  'fit-position': true,
	  'float': true,
	  'float-offset': true,
	  'font': true,
	  'font-family': true,
	  'font-size': true,
	  'font-size-adjust': true,
	  'font-stretch': true,
	  'font-style': true,
	  'font-variant': true,
	  'font-weight': true,
	  'grid-columns': true,
	  'grid-rows': true,
	  'hanging-punctuation': true,
	  'height': true,
	  'hyphenate-after': true,
	  'hyphenate-before': true,
	  'hyphenate-character': true,
	  'hyphenate-lines': true,
	  'hyphenate-resource': true,
	  'hyphens': true,
	  'icon': true,
	  'image-orientation': true,
	  'image-resolution': true,
	  'inline-box-align': true,
	  'left': true,
	  'letter-spacing': true,
	  'line-height': true,
	  'line-stacking': true,
	  'line-stacking-ruby': true,
	  'line-stacking-shift': true,
	  'line-stacking-strategy': true,
	  'list-style': true,
	  'list-style-image': true,
	  'list-style-position': true,
	  'list-style-type': true,
	  'margin': true,
	  'margin-bottom': true,
	  'margin-left': true,
	  'margin-right': true,
	  'margin-top': true,
	  'mark': true,
	  'mark-after': true,
	  'mark-before': true,
	  'marks': true,
	  'marquee-direction': true,
	  'marquee-play-count': true,
	  'marquee-speed': true,
	  'marquee-style': true,
	  'max-height': true,
	  'max-width': true,
	  'min-height': true,
	  'min-width': true,
	  'move-to': true,
	  'nav-down': true,
	  'nav-index': true,
	  'nav-left': true,
	  'nav-right': true,
	  'nav-up': true,
	  'opacity': true,
	  'orphans': true,
	  'outline': true,
	  'outline-color': true,
	  'outline-offset': true,
	  'outline-style': true,
	  'outline-width': true,
	  'overflow': true,
	  'overflow-style': true,
	  'overflow-x': true,
	  'overflow-y': true,
	  'padding': true,
	  'padding-bottom': true,
	  'padding-left': true,
	  'padding-right': true,
	  'padding-top': true,
	  'page': true,
	  'page-break-after': true,
	  'page-break-before': true,
	  'page-break-inside': true,
	  'page-policy': true,
	  'perspective': true,
	  'perspective-origin': true,
	  'phonemes': true,
	  'position': true,
	  'punctuation-trim': true,
	  'quotes': true,
	  'rendering-intent': true,
	  'resize': true,
	  'rest': true,
	  'rest-after': true,
	  'rest-before': true,
	  'right': true,
	  'rotation': true,
	  'rotation-point': true,
	  'ruby-align': true,
	  'ruby-overhang': true,
	  'ruby-position': true,
	  'ruby-span': true,
	  'size': true,
	  'string-set': true,
	  'table-layout': true,
	  'target': true,
	  'target-name': true,
	  'target-new': true,
	  'target-position': true,
	  'text-align': true,
	  'text-align-last': true,
	  'text-decoration': true,
	  'text-height': true,
	  'text-indent': true,
	  'text-justify': true,
	  'text-outline': true,
	  'text-overflow': true,
	  'text-shadow': true,
	  'text-transform': true,
	  'text-wrap': true,
	  'top': true,
	  'transform': true,
	  'transform-origin': true,
	  'transform-style': true,
	  'transition': true,
	  'transition-delay': true,
	  'transition-duration': true,
	  'transition-property': true,
	  'transition-timing-function': true,
	  'unicode-bidi': true,
	  'user-select': true,
	  'vertical-align': true,
	  'visibility': true,
	  'voice-balance': true,
	  'voice-duration': true,
	  'voice-pitch': true,
	  'voice-pitch-range': true,
	  'voice-rate': true,
	  'voice-stress': true,
	  'voice-volume': true,
	  'white-space': true,
	  'widows': true,
	  'width': true,
	  'word-break': true,
	  'word-spacing': true,
	  'word-wrap': true,
	  'z-index': true
	}

	var vendorPrefixRegEx = /^-.+-/;

	module.exports = function(prop) {
	  if (prop[0] === '-') {
	    return !!_validCSSProps[prop.replace(vendorPrefixRegEx, '')];
	  }
	  return !!_validCSSProps[prop];
	};


/***/ },
/* 136 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var escapeHtmlChar = __webpack_require__(141),
	    keys = __webpack_require__(142),
	    reUnescapedHtml = __webpack_require__(143);

	/**
	 * Converts the characters `&`, `<`, `>`, `"`, and `'` in `string` to their
	 * corresponding HTML entities.
	 *
	 * @static
	 * @memberOf _
	 * @category Utilities
	 * @param {string} string The string to escape.
	 * @returns {string} Returns the escaped string.
	 * @example
	 *
	 * _.escape('Fred, Wilma, & Pebbles');
	 * // => 'Fred, Wilma, &amp; Pebbles'
	 */
	function escape(string) {
	  return string == null ? '' : String(string).replace(reUnescapedHtml, escapeHtmlChar);
	}

	module.exports = escape;


/***/ },
/* 137 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var bind = __webpack_require__(145),
	    identity = __webpack_require__(144),
	    setBindData = __webpack_require__(146),
	    support = __webpack_require__(147);

	/** Used to detected named functions */
	var reFuncName = /^\s*function[ \n\r\t]+\w/;

	/** Used to detect functions containing a `this` reference */
	var reThis = /\bthis\b/;

	/** Native method shortcuts */
	var fnToString = Function.prototype.toString;

	/**
	 * The base implementation of `_.createCallback` without support for creating
	 * "_.pluck" or "_.where" style callbacks.
	 *
	 * @private
	 * @param {*} [func=identity] The value to convert to a callback.
	 * @param {*} [thisArg] The `this` binding of the created callback.
	 * @param {number} [argCount] The number of arguments the callback accepts.
	 * @returns {Function} Returns a callback function.
	 */
	function baseCreateCallback(func, thisArg, argCount) {
	  if (typeof func != 'function') {
	    return identity;
	  }
	  // exit early for no `thisArg` or already bound by `Function#bind`
	  if (typeof thisArg == 'undefined' || !('prototype' in func)) {
	    return func;
	  }
	  var bindData = func.__bindData__;
	  if (typeof bindData == 'undefined') {
	    if (support.funcNames) {
	      bindData = !func.name;
	    }
	    bindData = bindData || !support.funcDecomp;
	    if (!bindData) {
	      var source = fnToString.call(func);
	      if (!support.funcNames) {
	        bindData = !reFuncName.test(source);
	      }
	      if (!bindData) {
	        // checks if `func` references the `this` keyword and stores the result
	        bindData = reThis.test(source);
	        setBindData(func, bindData);
	      }
	    }
	  }
	  // exit early if there are no `this` references or `func` is bound
	  if (bindData === false || (bindData !== true && bindData[1] & 1)) {
	    return func;
	  }
	  switch (argCount) {
	    case 1: return function(value) {
	      return func.call(thisArg, value);
	    };
	    case 2: return function(a, b) {
	      return func.call(thisArg, a, b);
	    };
	    case 3: return function(value, index, collection) {
	      return func.call(thisArg, value, index, collection);
	    };
	    case 4: return function(accumulator, value, index, collection) {
	      return func.call(thisArg, accumulator, value, index, collection);
	    };
	  }
	  return bind(func, thisArg);
	}

	module.exports = baseCreateCallback;


/***/ },
/* 138 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var isNative = __webpack_require__(148),
	    isObject = __webpack_require__(149),
	    shimKeys = __webpack_require__(150);

	/* Native method shortcuts for methods with the same name as other `lodash` methods */
	var nativeKeys = isNative(nativeKeys = Object.keys) && nativeKeys;

	/**
	 * Creates an array composed of the own enumerable property names of an object.
	 *
	 * @static
	 * @memberOf _
	 * @category Objects
	 * @param {Object} object The object to inspect.
	 * @returns {Array} Returns an array of property names.
	 * @example
	 *
	 * _.keys({ 'one': 1, 'two': 2, 'three': 3 });
	 * // => ['one', 'two', 'three'] (property order is not guaranteed across environments)
	 */
	var keys = !nativeKeys ? shimKeys : function(object) {
	  if (!isObject(object)) {
	    return [];
	  }
	  return nativeKeys(object);
	};

	module.exports = keys;


/***/ },
/* 139 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/** Used to determine if values are of the language type Object */
	var objectTypes = {
	  'boolean': false,
	  'function': true,
	  'object': true,
	  'number': false,
	  'string': false,
	  'undefined': false
	};

	module.exports = objectTypes;


/***/ },
/* 140 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var createCallback = __webpack_require__(151),
	    forOwn = __webpack_require__(152);

	/**
	 * Checks if the given callback returns truey value for **all** elements of
	 * a collection. The callback is bound to `thisArg` and invoked with three
	 * arguments; (value, index|key, collection).
	 *
	 * If a property name is provided for `callback` the created "_.pluck" style
	 * callback will return the property value of the given element.
	 *
	 * If an object is provided for `callback` the created "_.where" style callback
	 * will return `true` for elements that have the properties of the given object,
	 * else `false`.
	 *
	 * @static
	 * @memberOf _
	 * @alias all
	 * @category Collections
	 * @param {Array|Object|string} collection The collection to iterate over.
	 * @param {Function|Object|string} [callback=identity] The function called
	 *  per iteration. If a property name or object is provided it will be used
	 *  to create a "_.pluck" or "_.where" style callback, respectively.
	 * @param {*} [thisArg] The `this` binding of `callback`.
	 * @returns {boolean} Returns `true` if all elements passed the callback check,
	 *  else `false`.
	 * @example
	 *
	 * _.every([true, 1, null, 'yes']);
	 * // => false
	 *
	 * var characters = [
	 *   { 'name': 'barney', 'age': 36 },
	 *   { 'name': 'fred',   'age': 40 }
	 * ];
	 *
	 * // using "_.pluck" callback shorthand
	 * _.every(characters, 'age');
	 * // => true
	 *
	 * // using "_.where" callback shorthand
	 * _.every(characters, { 'age': 36 });
	 * // => false
	 */
	function every(collection, callback, thisArg) {
	  var result = true;
	  callback = createCallback(callback, thisArg, 3);

	  var index = -1,
	      length = collection ? collection.length : 0;

	  if (typeof length == 'number') {
	    while (++index < length) {
	      if (!(result = !!callback(collection[index], index, collection))) {
	        break;
	      }
	    }
	  } else {
	    forOwn(collection, function(value, index, collection) {
	      return (result = !!callback(value, index, collection));
	    });
	  }
	  return result;
	}

	module.exports = every;


/***/ },
/* 141 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var htmlEscapes = __webpack_require__(156);

	/**
	 * Used by `escape` to convert characters to HTML entities.
	 *
	 * @private
	 * @param {string} match The matched character to escape.
	 * @returns {string} Returns the escaped character.
	 */
	function escapeHtmlChar(match) {
	  return htmlEscapes[match];
	}

	module.exports = escapeHtmlChar;


/***/ },
/* 142 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var isNative = __webpack_require__(153),
	    isObject = __webpack_require__(154),
	    shimKeys = __webpack_require__(155);

	/* Native method shortcuts for methods with the same name as other `lodash` methods */
	var nativeKeys = isNative(nativeKeys = Object.keys) && nativeKeys;

	/**
	 * Creates an array composed of the own enumerable property names of an object.
	 *
	 * @static
	 * @memberOf _
	 * @category Objects
	 * @param {Object} object The object to inspect.
	 * @returns {Array} Returns an array of property names.
	 * @example
	 *
	 * _.keys({ 'one': 1, 'two': 2, 'three': 3 });
	 * // => ['one', 'two', 'three'] (property order is not guaranteed across environments)
	 */
	var keys = !nativeKeys ? shimKeys : function(object) {
	  if (!isObject(object)) {
	    return [];
	  }
	  return nativeKeys(object);
	};

	module.exports = keys;


/***/ },
/* 143 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var htmlEscapes = __webpack_require__(157),
	    keys = __webpack_require__(142);

	/** Used to match HTML entities and HTML characters */
	var reUnescapedHtml = RegExp('[' + keys(htmlEscapes).join('') + ']', 'g');

	module.exports = reUnescapedHtml;


/***/ },
/* 144 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/**
	 * This method returns the first argument provided to it.
	 *
	 * @static
	 * @memberOf _
	 * @category Utilities
	 * @param {*} value Any value.
	 * @returns {*} Returns `value`.
	 * @example
	 *
	 * var object = { 'name': 'fred' };
	 * _.identity(object) === object;
	 * // => true
	 */
	function identity(value) {
	  return value;
	}

	module.exports = identity;


/***/ },
/* 145 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var createWrapper = __webpack_require__(158),
	    slice = __webpack_require__(159);

	/**
	 * Creates a function that, when called, invokes `func` with the `this`
	 * binding of `thisArg` and prepends any additional `bind` arguments to those
	 * provided to the bound function.
	 *
	 * @static
	 * @memberOf _
	 * @category Functions
	 * @param {Function} func The function to bind.
	 * @param {*} [thisArg] The `this` binding of `func`.
	 * @param {...*} [arg] Arguments to be partially applied.
	 * @returns {Function} Returns the new bound function.
	 * @example
	 *
	 * var func = function(greeting) {
	 *   return greeting + ' ' + this.name;
	 * };
	 *
	 * func = _.bind(func, { 'name': 'fred' }, 'hi');
	 * func();
	 * // => 'hi fred'
	 */
	function bind(func, thisArg) {
	  return arguments.length > 2
	    ? createWrapper(func, 17, slice(arguments, 2), null, thisArg)
	    : createWrapper(func, 1, null, null, thisArg);
	}

	module.exports = bind;


/***/ },
/* 146 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var isNative = __webpack_require__(160),
	    noop = __webpack_require__(161);

	/** Used as the property descriptor for `__bindData__` */
	var descriptor = {
	  'configurable': false,
	  'enumerable': false,
	  'value': null,
	  'writable': false
	};

	/** Used to set meta data on functions */
	var defineProperty = (function() {
	  // IE 8 only accepts DOM elements
	  try {
	    var o = {},
	        func = isNative(func = Object.defineProperty) && func,
	        result = func(o, o, o) && func;
	  } catch(e) { }
	  return result;
	}());

	/**
	 * Sets `this` binding data on a given function.
	 *
	 * @private
	 * @param {Function} func The function to set data on.
	 * @param {Array} value The data array to set.
	 */
	var setBindData = !defineProperty ? noop : function(func, value) {
	  descriptor.value = value;
	  defineProperty(func, '__bindData__', descriptor);
	};

	module.exports = setBindData;


/***/ },
/* 147 */
/***/ function(module, exports, __webpack_require__) {

	/* WEBPACK VAR INJECTION */(function(global) {/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var isNative = __webpack_require__(162);

	/** Used to detect functions containing a `this` reference */
	var reThis = /\bthis\b/;

	/**
	 * An object used to flag environments features.
	 *
	 * @static
	 * @memberOf _
	 * @type Object
	 */
	var support = {};

	/**
	 * Detect if functions can be decompiled by `Function#toString`
	 * (all but PS3 and older Opera mobile browsers & avoided in Windows 8 apps).
	 *
	 * @memberOf _.support
	 * @type boolean
	 */
	support.funcDecomp = !isNative(global.WinRTError) && reThis.test(function() { return this; });

	/**
	 * Detect if `Function#name` is supported (all but IE).
	 *
	 * @memberOf _.support
	 * @type boolean
	 */
	support.funcNames = typeof Function.name == 'string';

	module.exports = support;
	
	/* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))

/***/ },
/* 148 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/** Used for native method references */
	var objectProto = Object.prototype;

	/** Used to resolve the internal [[Class]] of values */
	var toString = objectProto.toString;

	/** Used to detect if a method is native */
	var reNative = RegExp('^' +
	  String(toString)
	    .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
	    .replace(/toString| for [^\]]+/g, '.*?') + '$'
	);

	/**
	 * Checks if `value` is a native function.
	 *
	 * @private
	 * @param {*} value The value to check.
	 * @returns {boolean} Returns `true` if the `value` is a native function, else `false`.
	 */
	function isNative(value) {
	  return typeof value == 'function' && reNative.test(value);
	}

	module.exports = isNative;


/***/ },
/* 149 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var objectTypes = __webpack_require__(139);

	/**
	 * Checks if `value` is the language type of Object.
	 * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
	 *
	 * @static
	 * @memberOf _
	 * @category Objects
	 * @param {*} value The value to check.
	 * @returns {boolean} Returns `true` if the `value` is an object, else `false`.
	 * @example
	 *
	 * _.isObject({});
	 * // => true
	 *
	 * _.isObject([1, 2, 3]);
	 * // => true
	 *
	 * _.isObject(1);
	 * // => false
	 */
	function isObject(value) {
	  // check if the value is the ECMAScript language type of Object
	  // http://es5.github.io/#x8
	  // and avoid a V8 bug
	  // http://code.google.com/p/v8/issues/detail?id=2291
	  return !!(value && objectTypes[typeof value]);
	}

	module.exports = isObject;


/***/ },
/* 150 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var objectTypes = __webpack_require__(139);

	/** Used for native method references */
	var objectProto = Object.prototype;

	/** Native method shortcuts */
	var hasOwnProperty = objectProto.hasOwnProperty;

	/**
	 * A fallback implementation of `Object.keys` which produces an array of the
	 * given object's own enumerable property names.
	 *
	 * @private
	 * @type Function
	 * @param {Object} object The object to inspect.
	 * @returns {Array} Returns an array of property names.
	 */
	var shimKeys = function(object) {
	  var index, iterable = object, result = [];
	  if (!iterable) return result;
	  if (!(objectTypes[typeof object])) return result;
	    for (index in iterable) {
	      if (hasOwnProperty.call(iterable, index)) {
	        result.push(index);
	      }
	    }
	  return result
	};

	module.exports = shimKeys;


/***/ },
/* 151 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var baseCreateCallback = __webpack_require__(163),
	    baseIsEqual = __webpack_require__(164),
	    isObject = __webpack_require__(165),
	    keys = __webpack_require__(166),
	    property = __webpack_require__(167);

	/**
	 * Produces a callback bound to an optional `thisArg`. If `func` is a property
	 * name the created callback will return the property value for a given element.
	 * If `func` is an object the created callback will return `true` for elements
	 * that contain the equivalent object properties, otherwise it will return `false`.
	 *
	 * @static
	 * @memberOf _
	 * @category Utilities
	 * @param {*} [func=identity] The value to convert to a callback.
	 * @param {*} [thisArg] The `this` binding of the created callback.
	 * @param {number} [argCount] The number of arguments the callback accepts.
	 * @returns {Function} Returns a callback function.
	 * @example
	 *
	 * var characters = [
	 *   { 'name': 'barney', 'age': 36 },
	 *   { 'name': 'fred',   'age': 40 }
	 * ];
	 *
	 * // wrap to create custom callback shorthands
	 * _.createCallback = _.wrap(_.createCallback, function(func, callback, thisArg) {
	 *   var match = /^(.+?)__([gl]t)(.+)$/.exec(callback);
	 *   return !match ? func(callback, thisArg) : function(object) {
	 *     return match[2] == 'gt' ? object[match[1]] > match[3] : object[match[1]] < match[3];
	 *   };
	 * });
	 *
	 * _.filter(characters, 'age__gt38');
	 * // => [{ 'name': 'fred', 'age': 40 }]
	 */
	function createCallback(func, thisArg, argCount) {
	  var type = typeof func;
	  if (func == null || type == 'function') {
	    return baseCreateCallback(func, thisArg, argCount);
	  }
	  // handle "_.pluck" style callback shorthands
	  if (type != 'object') {
	    return property(func);
	  }
	  var props = keys(func),
	      key = props[0],
	      a = func[key];

	  // handle "_.where" style callback shorthands
	  if (props.length == 1 && a === a && !isObject(a)) {
	    // fast path the common case of providing an object with a single
	    // property containing a primitive value
	    return function(object) {
	      var b = object[key];
	      return a === b && (a !== 0 || (1 / a == 1 / b));
	    };
	  }
	  return function(object) {
	    var length = props.length,
	        result = false;

	    while (length--) {
	      if (!(result = baseIsEqual(object[props[length]], func[props[length]], null, true))) {
	        break;
	      }
	    }
	    return result;
	  };
	}

	module.exports = createCallback;


/***/ },
/* 152 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var baseCreateCallback = __webpack_require__(168),
	    keys = __webpack_require__(169),
	    objectTypes = __webpack_require__(170);

	/**
	 * Iterates over own enumerable properties of an object, executing the callback
	 * for each property. The callback is bound to `thisArg` and invoked with three
	 * arguments; (value, key, object). Callbacks may exit iteration early by
	 * explicitly returning `false`.
	 *
	 * @static
	 * @memberOf _
	 * @type Function
	 * @category Objects
	 * @param {Object} object The object to iterate over.
	 * @param {Function} [callback=identity] The function called per iteration.
	 * @param {*} [thisArg] The `this` binding of `callback`.
	 * @returns {Object} Returns `object`.
	 * @example
	 *
	 * _.forOwn({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) {
	 *   console.log(key);
	 * });
	 * // => logs '0', '1', and 'length' (property order is not guaranteed across environments)
	 */
	var forOwn = function(collection, callback, thisArg) {
	  var index, iterable = collection, result = iterable;
	  if (!iterable) return result;
	  if (!objectTypes[typeof iterable]) return result;
	  callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3);
	    var ownIndex = -1,
	        ownProps = objectTypes[typeof iterable] && keys(iterable),
	        length = ownProps ? ownProps.length : 0;

	    while (++ownIndex < length) {
	      index = ownProps[ownIndex];
	      if (callback(iterable[index], index, collection) === false) return result;
	    }
	  return result
	};

	module.exports = forOwn;


/***/ },
/* 153 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/** Used for native method references */
	var objectProto = Object.prototype;

	/** Used to resolve the internal [[Class]] of values */
	var toString = objectProto.toString;

	/** Used to detect if a method is native */
	var reNative = RegExp('^' +
	  String(toString)
	    .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
	    .replace(/toString| for [^\]]+/g, '.*?') + '$'
	);

	/**
	 * Checks if `value` is a native function.
	 *
	 * @private
	 * @param {*} value The value to check.
	 * @returns {boolean} Returns `true` if the `value` is a native function, else `false`.
	 */
	function isNative(value) {
	  return typeof value == 'function' && reNative.test(value);
	}

	module.exports = isNative;


/***/ },
/* 154 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var objectTypes = __webpack_require__(171);

	/**
	 * Checks if `value` is the language type of Object.
	 * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
	 *
	 * @static
	 * @memberOf _
	 * @category Objects
	 * @param {*} value The value to check.
	 * @returns {boolean} Returns `true` if the `value` is an object, else `false`.
	 * @example
	 *
	 * _.isObject({});
	 * // => true
	 *
	 * _.isObject([1, 2, 3]);
	 * // => true
	 *
	 * _.isObject(1);
	 * // => false
	 */
	function isObject(value) {
	  // check if the value is the ECMAScript language type of Object
	  // http://es5.github.io/#x8
	  // and avoid a V8 bug
	  // http://code.google.com/p/v8/issues/detail?id=2291
	  return !!(value && objectTypes[typeof value]);
	}

	module.exports = isObject;


/***/ },
/* 155 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var objectTypes = __webpack_require__(172);

	/** Used for native method references */
	var objectProto = Object.prototype;

	/** Native method shortcuts */
	var hasOwnProperty = objectProto.hasOwnProperty;

	/**
	 * A fallback implementation of `Object.keys` which produces an array of the
	 * given object's own enumerable property names.
	 *
	 * @private
	 * @type Function
	 * @param {Object} object The object to inspect.
	 * @returns {Array} Returns an array of property names.
	 */
	var shimKeys = function(object) {
	  var index, iterable = object, result = [];
	  if (!iterable) return result;
	  if (!(objectTypes[typeof object])) return result;
	    for (index in iterable) {
	      if (hasOwnProperty.call(iterable, index)) {
	        result.push(index);
	      }
	    }
	  return result
	};

	module.exports = shimKeys;


/***/ },
/* 156 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/**
	 * Used to convert characters to HTML entities:
	 *
	 * Though the `>` character is escaped for symmetry, characters like `>` and `/`
	 * don't require escaping in HTML and have no special meaning unless they're part
	 * of a tag or an unquoted attribute value.
	 * http://mathiasbynens.be/notes/ambiguous-ampersands (under "semi-related fun fact")
	 */
	var htmlEscapes = {
	  '&': '&amp;',
	  '<': '&lt;',
	  '>': '&gt;',
	  '"': '&quot;',
	  "'": '&#39;'
	};

	module.exports = htmlEscapes;


/***/ },
/* 157 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/**
	 * Used to convert characters to HTML entities:
	 *
	 * Though the `>` character is escaped for symmetry, characters like `>` and `/`
	 * don't require escaping in HTML and have no special meaning unless they're part
	 * of a tag or an unquoted attribute value.
	 * http://mathiasbynens.be/notes/ambiguous-ampersands (under "semi-related fun fact")
	 */
	var htmlEscapes = {
	  '&': '&amp;',
	  '<': '&lt;',
	  '>': '&gt;',
	  '"': '&quot;',
	  "'": '&#39;'
	};

	module.exports = htmlEscapes;


/***/ },
/* 158 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var baseBind = __webpack_require__(173),
	    baseCreateWrapper = __webpack_require__(174),
	    isFunction = __webpack_require__(175),
	    slice = __webpack_require__(159);

	/**
	 * Used for `Array` method references.
	 *
	 * Normally `Array.prototype` would suffice, however, using an array literal
	 * avoids issues in Narwhal.
	 */
	var arrayRef = [];

	/** Native method shortcuts */
	var push = arrayRef.push,
	    unshift = arrayRef.unshift;

	/**
	 * Creates a function that, when called, either curries or invokes `func`
	 * with an optional `this` binding and partially applied arguments.
	 *
	 * @private
	 * @param {Function|string} func The function or method name to reference.
	 * @param {number} bitmask The bitmask of method flags to compose.
	 *  The bitmask may be composed of the following flags:
	 *  1 - `_.bind`
	 *  2 - `_.bindKey`
	 *  4 - `_.curry`
	 *  8 - `_.curry` (bound)
	 *  16 - `_.partial`
	 *  32 - `_.partialRight`
	 * @param {Array} [partialArgs] An array of arguments to prepend to those
	 *  provided to the new function.
	 * @param {Array} [partialRightArgs] An array of arguments to append to those
	 *  provided to the new function.
	 * @param {*} [thisArg] The `this` binding of `func`.
	 * @param {number} [arity] The arity of `func`.
	 * @returns {Function} Returns the new function.
	 */
	function createWrapper(func, bitmask, partialArgs, partialRightArgs, thisArg, arity) {
	  var isBind = bitmask & 1,
	      isBindKey = bitmask & 2,
	      isCurry = bitmask & 4,
	      isCurryBound = bitmask & 8,
	      isPartial = bitmask & 16,
	      isPartialRight = bitmask & 32;

	  if (!isBindKey && !isFunction(func)) {
	    throw new TypeError;
	  }
	  if (isPartial && !partialArgs.length) {
	    bitmask &= ~16;
	    isPartial = partialArgs = false;
	  }
	  if (isPartialRight && !partialRightArgs.length) {
	    bitmask &= ~32;
	    isPartialRight = partialRightArgs = false;
	  }
	  var bindData = func && func.__bindData__;
	  if (bindData && bindData !== true) {
	    // clone `bindData`
	    bindData = slice(bindData);
	    if (bindData[2]) {
	      bindData[2] = slice(bindData[2]);
	    }
	    if (bindData[3]) {
	      bindData[3] = slice(bindData[3]);
	    }
	    // set `thisBinding` is not previously bound
	    if (isBind && !(bindData[1] & 1)) {
	      bindData[4] = thisArg;
	    }
	    // set if previously bound but not currently (subsequent curried functions)
	    if (!isBind && bindData[1] & 1) {
	      bitmask |= 8;
	    }
	    // set curried arity if not yet set
	    if (isCurry && !(bindData[1] & 4)) {
	      bindData[5] = arity;
	    }
	    // append partial left arguments
	    if (isPartial) {
	      push.apply(bindData[2] || (bindData[2] = []), partialArgs);
	    }
	    // append partial right arguments
	    if (isPartialRight) {
	      unshift.apply(bindData[3] || (bindData[3] = []), partialRightArgs);
	    }
	    // merge flags
	    bindData[1] |= bitmask;
	    return createWrapper.apply(null, bindData);
	  }
	  // fast path for `_.bind`
	  var creater = (bitmask == 1 || bitmask === 17) ? baseBind : baseCreateWrapper;
	  return creater([func, bitmask, partialArgs, partialRightArgs, thisArg, arity]);
	}

	module.exports = createWrapper;


/***/ },
/* 159 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/**
	 * Slices the `collection` from the `start` index up to, but not including,
	 * the `end` index.
	 *
	 * Note: This function is used instead of `Array#slice` to support node lists
	 * in IE < 9 and to ensure dense arrays are returned.
	 *
	 * @private
	 * @param {Array|Object|string} collection The collection to slice.
	 * @param {number} start The start index.
	 * @param {number} end The end index.
	 * @returns {Array} Returns the new array.
	 */
	function slice(array, start, end) {
	  start || (start = 0);
	  if (typeof end == 'undefined') {
	    end = array ? array.length : 0;
	  }
	  var index = -1,
	      length = end - start || 0,
	      result = Array(length < 0 ? 0 : length);

	  while (++index < length) {
	    result[index] = array[start + index];
	  }
	  return result;
	}

	module.exports = slice;


/***/ },
/* 160 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/** Used for native method references */
	var objectProto = Object.prototype;

	/** Used to resolve the internal [[Class]] of values */
	var toString = objectProto.toString;

	/** Used to detect if a method is native */
	var reNative = RegExp('^' +
	  String(toString)
	    .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
	    .replace(/toString| for [^\]]+/g, '.*?') + '$'
	);

	/**
	 * Checks if `value` is a native function.
	 *
	 * @private
	 * @param {*} value The value to check.
	 * @returns {boolean} Returns `true` if the `value` is a native function, else `false`.
	 */
	function isNative(value) {
	  return typeof value == 'function' && reNative.test(value);
	}

	module.exports = isNative;


/***/ },
/* 161 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/**
	 * A no-operation function.
	 *
	 * @static
	 * @memberOf _
	 * @category Utilities
	 * @example
	 *
	 * var object = { 'name': 'fred' };
	 * _.noop(object) === undefined;
	 * // => true
	 */
	function noop() {
	  // no operation performed
	}

	module.exports = noop;


/***/ },
/* 162 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/** Used for native method references */
	var objectProto = Object.prototype;

	/** Used to resolve the internal [[Class]] of values */
	var toString = objectProto.toString;

	/** Used to detect if a method is native */
	var reNative = RegExp('^' +
	  String(toString)
	    .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
	    .replace(/toString| for [^\]]+/g, '.*?') + '$'
	);

	/**
	 * Checks if `value` is a native function.
	 *
	 * @private
	 * @param {*} value The value to check.
	 * @returns {boolean} Returns `true` if the `value` is a native function, else `false`.
	 */
	function isNative(value) {
	  return typeof value == 'function' && reNative.test(value);
	}

	module.exports = isNative;


/***/ },
/* 163 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var bind = __webpack_require__(176),
	    identity = __webpack_require__(177),
	    setBindData = __webpack_require__(178),
	    support = __webpack_require__(179);

	/** Used to detected named functions */
	var reFuncName = /^\s*function[ \n\r\t]+\w/;

	/** Used to detect functions containing a `this` reference */
	var reThis = /\bthis\b/;

	/** Native method shortcuts */
	var fnToString = Function.prototype.toString;

	/**
	 * The base implementation of `_.createCallback` without support for creating
	 * "_.pluck" or "_.where" style callbacks.
	 *
	 * @private
	 * @param {*} [func=identity] The value to convert to a callback.
	 * @param {*} [thisArg] The `this` binding of the created callback.
	 * @param {number} [argCount] The number of arguments the callback accepts.
	 * @returns {Function} Returns a callback function.
	 */
	function baseCreateCallback(func, thisArg, argCount) {
	  if (typeof func != 'function') {
	    return identity;
	  }
	  // exit early for no `thisArg` or already bound by `Function#bind`
	  if (typeof thisArg == 'undefined' || !('prototype' in func)) {
	    return func;
	  }
	  var bindData = func.__bindData__;
	  if (typeof bindData == 'undefined') {
	    if (support.funcNames) {
	      bindData = !func.name;
	    }
	    bindData = bindData || !support.funcDecomp;
	    if (!bindData) {
	      var source = fnToString.call(func);
	      if (!support.funcNames) {
	        bindData = !reFuncName.test(source);
	      }
	      if (!bindData) {
	        // checks if `func` references the `this` keyword and stores the result
	        bindData = reThis.test(source);
	        setBindData(func, bindData);
	      }
	    }
	  }
	  // exit early if there are no `this` references or `func` is bound
	  if (bindData === false || (bindData !== true && bindData[1] & 1)) {
	    return func;
	  }
	  switch (argCount) {
	    case 1: return function(value) {
	      return func.call(thisArg, value);
	    };
	    case 2: return function(a, b) {
	      return func.call(thisArg, a, b);
	    };
	    case 3: return function(value, index, collection) {
	      return func.call(thisArg, value, index, collection);
	    };
	    case 4: return function(accumulator, value, index, collection) {
	      return func.call(thisArg, accumulator, value, index, collection);
	    };
	  }
	  return bind(func, thisArg);
	}

	module.exports = baseCreateCallback;


/***/ },
/* 164 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var forIn = __webpack_require__(180),
	    getArray = __webpack_require__(181),
	    isFunction = __webpack_require__(182),
	    objectTypes = __webpack_require__(183),
	    releaseArray = __webpack_require__(184);

	/** `Object#toString` result shortcuts */
	var argsClass = '[object Arguments]',
	    arrayClass = '[object Array]',
	    boolClass = '[object Boolean]',
	    dateClass = '[object Date]',
	    numberClass = '[object Number]',
	    objectClass = '[object Object]',
	    regexpClass = '[object RegExp]',
	    stringClass = '[object String]';

	/** Used for native method references */
	var objectProto = Object.prototype;

	/** Used to resolve the internal [[Class]] of values */
	var toString = objectProto.toString;

	/** Native method shortcuts */
	var hasOwnProperty = objectProto.hasOwnProperty;

	/**
	 * The base implementation of `_.isEqual`, without support for `thisArg` binding,
	 * that allows partial "_.where" style comparisons.
	 *
	 * @private
	 * @param {*} a The value to compare.
	 * @param {*} b The other value to compare.
	 * @param {Function} [callback] The function to customize comparing values.
	 * @param {Function} [isWhere=false] A flag to indicate performing partial comparisons.
	 * @param {Array} [stackA=[]] Tracks traversed `a` objects.
	 * @param {Array} [stackB=[]] Tracks traversed `b` objects.
	 * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
	 */
	function baseIsEqual(a, b, callback, isWhere, stackA, stackB) {
	  // used to indicate that when comparing objects, `a` has at least the properties of `b`
	  if (callback) {
	    var result = callback(a, b);
	    if (typeof result != 'undefined') {
	      return !!result;
	    }
	  }
	  // exit early for identical values
	  if (a === b) {
	    // treat `+0` vs. `-0` as not equal
	    return a !== 0 || (1 / a == 1 / b);
	  }
	  var type = typeof a,
	      otherType = typeof b;

	  // exit early for unlike primitive values
	  if (a === a &&
	      !(a && objectTypes[type]) &&
	      !(b && objectTypes[otherType])) {
	    return false;
	  }
	  // exit early for `null` and `undefined` avoiding ES3's Function#call behavior
	  // http://es5.github.io/#x15.3.4.4
	  if (a == null || b == null) {
	    return a === b;
	  }
	  // compare [[Class]] names
	  var className = toString.call(a),
	      otherClass = toString.call(b);

	  if (className == argsClass) {
	    className = objectClass;
	  }
	  if (otherClass == argsClass) {
	    otherClass = objectClass;
	  }
	  if (className != otherClass) {
	    return false;
	  }
	  switch (className) {
	    case boolClass:
	    case dateClass:
	      // coerce dates and booleans to numbers, dates to milliseconds and booleans
	      // to `1` or `0` treating invalid dates coerced to `NaN` as not equal
	      return +a == +b;

	    case numberClass:
	      // treat `NaN` vs. `NaN` as equal
	      return (a != +a)
	        ? b != +b
	        // but treat `+0` vs. `-0` as not equal
	        : (a == 0 ? (1 / a == 1 / b) : a == +b);

	    case regexpClass:
	    case stringClass:
	      // coerce regexes to strings (http://es5.github.io/#x15.10.6.4)
	      // treat string primitives and their corresponding object instances as equal
	      return a == String(b);
	  }
	  var isArr = className == arrayClass;
	  if (!isArr) {
	    // unwrap any `lodash` wrapped values
	    var aWrapped = hasOwnProperty.call(a, '__wrapped__'),
	        bWrapped = hasOwnProperty.call(b, '__wrapped__');

	    if (aWrapped || bWrapped) {
	      return baseIsEqual(aWrapped ? a.__wrapped__ : a, bWrapped ? b.__wrapped__ : b, callback, isWhere, stackA, stackB);
	    }
	    // exit for functions and DOM nodes
	    if (className != objectClass) {
	      return false;
	    }
	    // in older versions of Opera, `arguments` objects have `Array` constructors
	    var ctorA = a.constructor,
	        ctorB = b.constructor;

	    // non `Object` object instances with different constructors are not equal
	    if (ctorA != ctorB &&
	          !(isFunction(ctorA) && ctorA instanceof ctorA && isFunction(ctorB) && ctorB instanceof ctorB) &&
	          ('constructor' in a && 'constructor' in b)
	        ) {
	      return false;
	    }
	  }
	  // assume cyclic structures are equal
	  // the algorithm for detecting cyclic structures is adapted from ES 5.1
	  // section 15.12.3, abstract operation `JO` (http://es5.github.io/#x15.12.3)
	  var initedStack = !stackA;
	  stackA || (stackA = getArray());
	  stackB || (stackB = getArray());

	  var length = stackA.length;
	  while (length--) {
	    if (stackA[length] == a) {
	      return stackB[length] == b;
	    }
	  }
	  var size = 0;
	  result = true;

	  // add `a` and `b` to the stack of traversed objects
	  stackA.push(a);
	  stackB.push(b);

	  // recursively compare objects and arrays (susceptible to call stack limits)
	  if (isArr) {
	    // compare lengths to determine if a deep comparison is necessary
	    length = a.length;
	    size = b.length;
	    result = size == length;

	    if (result || isWhere) {
	      // deep compare the contents, ignoring non-numeric properties
	      while (size--) {
	        var index = length,
	            value = b[size];

	        if (isWhere) {
	          while (index--) {
	            if ((result = baseIsEqual(a[index], value, callback, isWhere, stackA, stackB))) {
	              break;
	            }
	          }
	        } else if (!(result = baseIsEqual(a[size], value, callback, isWhere, stackA, stackB))) {
	          break;
	        }
	      }
	    }
	  }
	  else {
	    // deep compare objects using `forIn`, instead of `forOwn`, to avoid `Object.keys`
	    // which, in this case, is more costly
	    forIn(b, function(value, key, b) {
	      if (hasOwnProperty.call(b, key)) {
	        // count the number of properties.
	        size++;
	        // deep compare each property value.
	        return (result = hasOwnProperty.call(a, key) && baseIsEqual(a[key], value, callback, isWhere, stackA, stackB));
	      }
	    });

	    if (result && !isWhere) {
	      // ensure both objects have the same number of properties
	      forIn(a, function(value, key, a) {
	        if (hasOwnProperty.call(a, key)) {
	          // `size` will be `-1` if `a` has more properties than `b`
	          return (result = --size > -1);
	        }
	      });
	    }
	  }
	  stackA.pop();
	  stackB.pop();

	  if (initedStack) {
	    releaseArray(stackA);
	    releaseArray(stackB);
	  }
	  return result;
	}

	module.exports = baseIsEqual;


/***/ },
/* 165 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var objectTypes = __webpack_require__(185);

	/**
	 * Checks if `value` is the language type of Object.
	 * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
	 *
	 * @static
	 * @memberOf _
	 * @category Objects
	 * @param {*} value The value to check.
	 * @returns {boolean} Returns `true` if the `value` is an object, else `false`.
	 * @example
	 *
	 * _.isObject({});
	 * // => true
	 *
	 * _.isObject([1, 2, 3]);
	 * // => true
	 *
	 * _.isObject(1);
	 * // => false
	 */
	function isObject(value) {
	  // check if the value is the ECMAScript language type of Object
	  // http://es5.github.io/#x8
	  // and avoid a V8 bug
	  // http://code.google.com/p/v8/issues/detail?id=2291
	  return !!(value && objectTypes[typeof value]);
	}

	module.exports = isObject;


/***/ },
/* 166 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var isNative = __webpack_require__(186),
	    isObject = __webpack_require__(165),
	    shimKeys = __webpack_require__(187);

	/* Native method shortcuts for methods with the same name as other `lodash` methods */
	var nativeKeys = isNative(nativeKeys = Object.keys) && nativeKeys;

	/**
	 * Creates an array composed of the own enumerable property names of an object.
	 *
	 * @static
	 * @memberOf _
	 * @category Objects
	 * @param {Object} object The object to inspect.
	 * @returns {Array} Returns an array of property names.
	 * @example
	 *
	 * _.keys({ 'one': 1, 'two': 2, 'three': 3 });
	 * // => ['one', 'two', 'three'] (property order is not guaranteed across environments)
	 */
	var keys = !nativeKeys ? shimKeys : function(object) {
	  if (!isObject(object)) {
	    return [];
	  }
	  return nativeKeys(object);
	};

	module.exports = keys;


/***/ },
/* 167 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/**
	 * Creates a "_.pluck" style function, which returns the `key` value of a
	 * given object.
	 *
	 * @static
	 * @memberOf _
	 * @category Utilities
	 * @param {string} key The name of the property to retrieve.
	 * @returns {Function} Returns the new function.
	 * @example
	 *
	 * var characters = [
	 *   { 'name': 'fred',   'age': 40 },
	 *   { 'name': 'barney', 'age': 36 }
	 * ];
	 *
	 * var getName = _.property('name');
	 *
	 * _.map(characters, getName);
	 * // => ['barney', 'fred']
	 *
	 * _.sortBy(characters, getName);
	 * // => [{ 'name': 'barney', 'age': 36 }, { 'name': 'fred',   'age': 40 }]
	 */
	function property(key) {
	  return function(object) {
	    return object[key];
	  };
	}

	module.exports = property;


/***/ },
/* 168 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var bind = __webpack_require__(188),
	    identity = __webpack_require__(189),
	    setBindData = __webpack_require__(190),
	    support = __webpack_require__(191);

	/** Used to detected named functions */
	var reFuncName = /^\s*function[ \n\r\t]+\w/;

	/** Used to detect functions containing a `this` reference */
	var reThis = /\bthis\b/;

	/** Native method shortcuts */
	var fnToString = Function.prototype.toString;

	/**
	 * The base implementation of `_.createCallback` without support for creating
	 * "_.pluck" or "_.where" style callbacks.
	 *
	 * @private
	 * @param {*} [func=identity] The value to convert to a callback.
	 * @param {*} [thisArg] The `this` binding of the created callback.
	 * @param {number} [argCount] The number of arguments the callback accepts.
	 * @returns {Function} Returns a callback function.
	 */
	function baseCreateCallback(func, thisArg, argCount) {
	  if (typeof func != 'function') {
	    return identity;
	  }
	  // exit early for no `thisArg` or already bound by `Function#bind`
	  if (typeof thisArg == 'undefined' || !('prototype' in func)) {
	    return func;
	  }
	  var bindData = func.__bindData__;
	  if (typeof bindData == 'undefined') {
	    if (support.funcNames) {
	      bindData = !func.name;
	    }
	    bindData = bindData || !support.funcDecomp;
	    if (!bindData) {
	      var source = fnToString.call(func);
	      if (!support.funcNames) {
	        bindData = !reFuncName.test(source);
	      }
	      if (!bindData) {
	        // checks if `func` references the `this` keyword and stores the result
	        bindData = reThis.test(source);
	        setBindData(func, bindData);
	      }
	    }
	  }
	  // exit early if there are no `this` references or `func` is bound
	  if (bindData === false || (bindData !== true && bindData[1] & 1)) {
	    return func;
	  }
	  switch (argCount) {
	    case 1: return function(value) {
	      return func.call(thisArg, value);
	    };
	    case 2: return function(a, b) {
	      return func.call(thisArg, a, b);
	    };
	    case 3: return function(value, index, collection) {
	      return func.call(thisArg, value, index, collection);
	    };
	    case 4: return function(accumulator, value, index, collection) {
	      return func.call(thisArg, accumulator, value, index, collection);
	    };
	  }
	  return bind(func, thisArg);
	}

	module.exports = baseCreateCallback;


/***/ },
/* 169 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var isNative = __webpack_require__(192),
	    isObject = __webpack_require__(193),
	    shimKeys = __webpack_require__(194);

	/* Native method shortcuts for methods with the same name as other `lodash` methods */
	var nativeKeys = isNative(nativeKeys = Object.keys) && nativeKeys;

	/**
	 * Creates an array composed of the own enumerable property names of an object.
	 *
	 * @static
	 * @memberOf _
	 * @category Objects
	 * @param {Object} object The object to inspect.
	 * @returns {Array} Returns an array of property names.
	 * @example
	 *
	 * _.keys({ 'one': 1, 'two': 2, 'three': 3 });
	 * // => ['one', 'two', 'three'] (property order is not guaranteed across environments)
	 */
	var keys = !nativeKeys ? shimKeys : function(object) {
	  if (!isObject(object)) {
	    return [];
	  }
	  return nativeKeys(object);
	};

	module.exports = keys;


/***/ },
/* 170 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/** Used to determine if values are of the language type Object */
	var objectTypes = {
	  'boolean': false,
	  'function': true,
	  'object': true,
	  'number': false,
	  'string': false,
	  'undefined': false
	};

	module.exports = objectTypes;


/***/ },
/* 171 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/** Used to determine if values are of the language type Object */
	var objectTypes = {
	  'boolean': false,
	  'function': true,
	  'object': true,
	  'number': false,
	  'string': false,
	  'undefined': false
	};

	module.exports = objectTypes;


/***/ },
/* 172 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/** Used to determine if values are of the language type Object */
	var objectTypes = {
	  'boolean': false,
	  'function': true,
	  'object': true,
	  'number': false,
	  'string': false,
	  'undefined': false
	};

	module.exports = objectTypes;


/***/ },
/* 173 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var baseCreate = __webpack_require__(196),
	    isObject = __webpack_require__(198),
	    setBindData = __webpack_require__(146),
	    slice = __webpack_require__(159);

	/**
	 * Used for `Array` method references.
	 *
	 * Normally `Array.prototype` would suffice, however, using an array literal
	 * avoids issues in Narwhal.
	 */
	var arrayRef = [];

	/** Native method shortcuts */
	var push = arrayRef.push;

	/**
	 * The base implementation of `_.bind` that creates the bound function and
	 * sets its meta data.
	 *
	 * @private
	 * @param {Array} bindData The bind data array.
	 * @returns {Function} Returns the new bound function.
	 */
	function baseBind(bindData) {
	  var func = bindData[0],
	      partialArgs = bindData[2],
	      thisArg = bindData[4];

	  function bound() {
	    // `Function#bind` spec
	    // http://es5.github.io/#x15.3.4.5
	    if (partialArgs) {
	      // avoid `arguments` object deoptimizations by using `slice` instead
	      // of `Array.prototype.slice.call` and not assigning `arguments` to a
	      // variable as a ternary expression
	      var args = slice(partialArgs);
	      push.apply(args, arguments);
	    }
	    // mimic the constructor's `return` behavior
	    // http://es5.github.io/#x13.2.2
	    if (this instanceof bound) {
	      // ensure `new bound` is an instance of `func`
	      var thisBinding = baseCreate(func.prototype),
	          result = func.apply(thisBinding, args || arguments);
	      return isObject(result) ? result : thisBinding;
	    }
	    return func.apply(thisArg, args || arguments);
	  }
	  setBindData(bound, bindData);
	  return bound;
	}

	module.exports = baseBind;


/***/ },
/* 174 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var baseCreate = __webpack_require__(197),
	    isObject = __webpack_require__(195),
	    setBindData = __webpack_require__(146),
	    slice = __webpack_require__(159);

	/**
	 * Used for `Array` method references.
	 *
	 * Normally `Array.prototype` would suffice, however, using an array literal
	 * avoids issues in Narwhal.
	 */
	var arrayRef = [];

	/** Native method shortcuts */
	var push = arrayRef.push;

	/**
	 * The base implementation of `createWrapper` that creates the wrapper and
	 * sets its meta data.
	 *
	 * @private
	 * @param {Array} bindData The bind data array.
	 * @returns {Function} Returns the new function.
	 */
	function baseCreateWrapper(bindData) {
	  var func = bindData[0],
	      bitmask = bindData[1],
	      partialArgs = bindData[2],
	      partialRightArgs = bindData[3],
	      thisArg = bindData[4],
	      arity = bindData[5];

	  var isBind = bitmask & 1,
	      isBindKey = bitmask & 2,
	      isCurry = bitmask & 4,
	      isCurryBound = bitmask & 8,
	      key = func;

	  function bound() {
	    var thisBinding = isBind ? thisArg : this;
	    if (partialArgs) {
	      var args = slice(partialArgs);
	      push.apply(args, arguments);
	    }
	    if (partialRightArgs || isCurry) {
	      args || (args = slice(arguments));
	      if (partialRightArgs) {
	        push.apply(args, partialRightArgs);
	      }
	      if (isCurry && args.length < arity) {
	        bitmask |= 16 & ~32;
	        return baseCreateWrapper([func, (isCurryBound ? bitmask : bitmask & ~3), args, null, thisArg, arity]);
	      }
	    }
	    args || (args = arguments);
	    if (isBindKey) {
	      func = thisBinding[key];
	    }
	    if (this instanceof bound) {
	      thisBinding = baseCreate(func.prototype);
	      var result = func.apply(thisBinding, args);
	      return isObject(result) ? result : thisBinding;
	    }
	    return func.apply(thisBinding, args);
	  }
	  setBindData(bound, bindData);
	  return bound;
	}

	module.exports = baseCreateWrapper;


/***/ },
/* 175 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/**
	 * Checks if `value` is a function.
	 *
	 * @static
	 * @memberOf _
	 * @category Objects
	 * @param {*} value The value to check.
	 * @returns {boolean} Returns `true` if the `value` is a function, else `false`.
	 * @example
	 *
	 * _.isFunction(_);
	 * // => true
	 */
	function isFunction(value) {
	  return typeof value == 'function';
	}

	module.exports = isFunction;


/***/ },
/* 176 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var createWrapper = __webpack_require__(199),
	    slice = __webpack_require__(200);

	/**
	 * Creates a function that, when called, invokes `func` with the `this`
	 * binding of `thisArg` and prepends any additional `bind` arguments to those
	 * provided to the bound function.
	 *
	 * @static
	 * @memberOf _
	 * @category Functions
	 * @param {Function} func The function to bind.
	 * @param {*} [thisArg] The `this` binding of `func`.
	 * @param {...*} [arg] Arguments to be partially applied.
	 * @returns {Function} Returns the new bound function.
	 * @example
	 *
	 * var func = function(greeting) {
	 *   return greeting + ' ' + this.name;
	 * };
	 *
	 * func = _.bind(func, { 'name': 'fred' }, 'hi');
	 * func();
	 * // => 'hi fred'
	 */
	function bind(func, thisArg) {
	  return arguments.length > 2
	    ? createWrapper(func, 17, slice(arguments, 2), null, thisArg)
	    : createWrapper(func, 1, null, null, thisArg);
	}

	module.exports = bind;


/***/ },
/* 177 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/**
	 * This method returns the first argument provided to it.
	 *
	 * @static
	 * @memberOf _
	 * @category Utilities
	 * @param {*} value Any value.
	 * @returns {*} Returns `value`.
	 * @example
	 *
	 * var object = { 'name': 'fred' };
	 * _.identity(object) === object;
	 * // => true
	 */
	function identity(value) {
	  return value;
	}

	module.exports = identity;


/***/ },
/* 178 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var isNative = __webpack_require__(201),
	    noop = __webpack_require__(202);

	/** Used as the property descriptor for `__bindData__` */
	var descriptor = {
	  'configurable': false,
	  'enumerable': false,
	  'value': null,
	  'writable': false
	};

	/** Used to set meta data on functions */
	var defineProperty = (function() {
	  // IE 8 only accepts DOM elements
	  try {
	    var o = {},
	        func = isNative(func = Object.defineProperty) && func,
	        result = func(o, o, o) && func;
	  } catch(e) { }
	  return result;
	}());

	/**
	 * Sets `this` binding data on a given function.
	 *
	 * @private
	 * @param {Function} func The function to set data on.
	 * @param {Array} value The data array to set.
	 */
	var setBindData = !defineProperty ? noop : function(func, value) {
	  descriptor.value = value;
	  defineProperty(func, '__bindData__', descriptor);
	};

	module.exports = setBindData;


/***/ },
/* 179 */
/***/ function(module, exports, __webpack_require__) {

	/* WEBPACK VAR INJECTION */(function(global) {/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var isNative = __webpack_require__(203);

	/** Used to detect functions containing a `this` reference */
	var reThis = /\bthis\b/;

	/**
	 * An object used to flag environments features.
	 *
	 * @static
	 * @memberOf _
	 * @type Object
	 */
	var support = {};

	/**
	 * Detect if functions can be decompiled by `Function#toString`
	 * (all but PS3 and older Opera mobile browsers & avoided in Windows 8 apps).
	 *
	 * @memberOf _.support
	 * @type boolean
	 */
	support.funcDecomp = !isNative(global.WinRTError) && reThis.test(function() { return this; });

	/**
	 * Detect if `Function#name` is supported (all but IE).
	 *
	 * @memberOf _.support
	 * @type boolean
	 */
	support.funcNames = typeof Function.name == 'string';

	module.exports = support;
	
	/* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))

/***/ },
/* 180 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var baseCreateCallback = __webpack_require__(163),
	    objectTypes = __webpack_require__(183);

	/**
	 * Iterates over own and inherited enumerable properties of an object,
	 * executing the callback for each property. The callback is bound to `thisArg`
	 * and invoked with three arguments; (value, key, object). Callbacks may exit
	 * iteration early by explicitly returning `false`.
	 *
	 * @static
	 * @memberOf _
	 * @type Function
	 * @category Objects
	 * @param {Object} object The object to iterate over.
	 * @param {Function} [callback=identity] The function called per iteration.
	 * @param {*} [thisArg] The `this` binding of `callback`.
	 * @returns {Object} Returns `object`.
	 * @example
	 *
	 * function Shape() {
	 *   this.x = 0;
	 *   this.y = 0;
	 * }
	 *
	 * Shape.prototype.move = function(x, y) {
	 *   this.x += x;
	 *   this.y += y;
	 * };
	 *
	 * _.forIn(new Shape, function(value, key) {
	 *   console.log(key);
	 * });
	 * // => logs 'x', 'y', and 'move' (property order is not guaranteed across environments)
	 */
	var forIn = function(collection, callback, thisArg) {
	  var index, iterable = collection, result = iterable;
	  if (!iterable) return result;
	  if (!objectTypes[typeof iterable]) return result;
	  callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3);
	    for (index in iterable) {
	      if (callback(iterable[index], index, collection) === false) return result;
	    }
	  return result
	};

	module.exports = forIn;


/***/ },
/* 181 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var arrayPool = __webpack_require__(204);

	/**
	 * Gets an array from the array pool or creates a new one if the pool is empty.
	 *
	 * @private
	 * @returns {Array} The array from the pool.
	 */
	function getArray() {
	  return arrayPool.pop() || [];
	}

	module.exports = getArray;


/***/ },
/* 182 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/**
	 * Checks if `value` is a function.
	 *
	 * @static
	 * @memberOf _
	 * @category Objects
	 * @param {*} value The value to check.
	 * @returns {boolean} Returns `true` if the `value` is a function, else `false`.
	 * @example
	 *
	 * _.isFunction(_);
	 * // => true
	 */
	function isFunction(value) {
	  return typeof value == 'function';
	}

	module.exports = isFunction;


/***/ },
/* 183 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/** Used to determine if values are of the language type Object */
	var objectTypes = {
	  'boolean': false,
	  'function': true,
	  'object': true,
	  'number': false,
	  'string': false,
	  'undefined': false
	};

	module.exports = objectTypes;


/***/ },
/* 184 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var arrayPool = __webpack_require__(205),
	    maxPoolSize = __webpack_require__(206);

	/**
	 * Releases the given array back to the array pool.
	 *
	 * @private
	 * @param {Array} [array] The array to release.
	 */
	function releaseArray(array) {
	  array.length = 0;
	  if (arrayPool.length < maxPoolSize) {
	    arrayPool.push(array);
	  }
	}

	module.exports = releaseArray;


/***/ },
/* 185 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/** Used to determine if values are of the language type Object */
	var objectTypes = {
	  'boolean': false,
	  'function': true,
	  'object': true,
	  'number': false,
	  'string': false,
	  'undefined': false
	};

	module.exports = objectTypes;


/***/ },
/* 186 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/** Used for native method references */
	var objectProto = Object.prototype;

	/** Used to resolve the internal [[Class]] of values */
	var toString = objectProto.toString;

	/** Used to detect if a method is native */
	var reNative = RegExp('^' +
	  String(toString)
	    .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
	    .replace(/toString| for [^\]]+/g, '.*?') + '$'
	);

	/**
	 * Checks if `value` is a native function.
	 *
	 * @private
	 * @param {*} value The value to check.
	 * @returns {boolean} Returns `true` if the `value` is a native function, else `false`.
	 */
	function isNative(value) {
	  return typeof value == 'function' && reNative.test(value);
	}

	module.exports = isNative;


/***/ },
/* 187 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var objectTypes = __webpack_require__(207);

	/** Used for native method references */
	var objectProto = Object.prototype;

	/** Native method shortcuts */
	var hasOwnProperty = objectProto.hasOwnProperty;

	/**
	 * A fallback implementation of `Object.keys` which produces an array of the
	 * given object's own enumerable property names.
	 *
	 * @private
	 * @type Function
	 * @param {Object} object The object to inspect.
	 * @returns {Array} Returns an array of property names.
	 */
	var shimKeys = function(object) {
	  var index, iterable = object, result = [];
	  if (!iterable) return result;
	  if (!(objectTypes[typeof object])) return result;
	    for (index in iterable) {
	      if (hasOwnProperty.call(iterable, index)) {
	        result.push(index);
	      }
	    }
	  return result
	};

	module.exports = shimKeys;


/***/ },
/* 188 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var createWrapper = __webpack_require__(208),
	    slice = __webpack_require__(209);

	/**
	 * Creates a function that, when called, invokes `func` with the `this`
	 * binding of `thisArg` and prepends any additional `bind` arguments to those
	 * provided to the bound function.
	 *
	 * @static
	 * @memberOf _
	 * @category Functions
	 * @param {Function} func The function to bind.
	 * @param {*} [thisArg] The `this` binding of `func`.
	 * @param {...*} [arg] Arguments to be partially applied.
	 * @returns {Function} Returns the new bound function.
	 * @example
	 *
	 * var func = function(greeting) {
	 *   return greeting + ' ' + this.name;
	 * };
	 *
	 * func = _.bind(func, { 'name': 'fred' }, 'hi');
	 * func();
	 * // => 'hi fred'
	 */
	function bind(func, thisArg) {
	  return arguments.length > 2
	    ? createWrapper(func, 17, slice(arguments, 2), null, thisArg)
	    : createWrapper(func, 1, null, null, thisArg);
	}

	module.exports = bind;


/***/ },
/* 189 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/**
	 * This method returns the first argument provided to it.
	 *
	 * @static
	 * @memberOf _
	 * @category Utilities
	 * @param {*} value Any value.
	 * @returns {*} Returns `value`.
	 * @example
	 *
	 * var object = { 'name': 'fred' };
	 * _.identity(object) === object;
	 * // => true
	 */
	function identity(value) {
	  return value;
	}

	module.exports = identity;


/***/ },
/* 190 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var isNative = __webpack_require__(210),
	    noop = __webpack_require__(211);

	/** Used as the property descriptor for `__bindData__` */
	var descriptor = {
	  'configurable': false,
	  'enumerable': false,
	  'value': null,
	  'writable': false
	};

	/** Used to set meta data on functions */
	var defineProperty = (function() {
	  // IE 8 only accepts DOM elements
	  try {
	    var o = {},
	        func = isNative(func = Object.defineProperty) && func,
	        result = func(o, o, o) && func;
	  } catch(e) { }
	  return result;
	}());

	/**
	 * Sets `this` binding data on a given function.
	 *
	 * @private
	 * @param {Function} func The function to set data on.
	 * @param {Array} value The data array to set.
	 */
	var setBindData = !defineProperty ? noop : function(func, value) {
	  descriptor.value = value;
	  defineProperty(func, '__bindData__', descriptor);
	};

	module.exports = setBindData;


/***/ },
/* 191 */
/***/ function(module, exports, __webpack_require__) {

	/* WEBPACK VAR INJECTION */(function(global) {/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var isNative = __webpack_require__(212);

	/** Used to detect functions containing a `this` reference */
	var reThis = /\bthis\b/;

	/**
	 * An object used to flag environments features.
	 *
	 * @static
	 * @memberOf _
	 * @type Object
	 */
	var support = {};

	/**
	 * Detect if functions can be decompiled by `Function#toString`
	 * (all but PS3 and older Opera mobile browsers & avoided in Windows 8 apps).
	 *
	 * @memberOf _.support
	 * @type boolean
	 */
	support.funcDecomp = !isNative(global.WinRTError) && reThis.test(function() { return this; });

	/**
	 * Detect if `Function#name` is supported (all but IE).
	 *
	 * @memberOf _.support
	 * @type boolean
	 */
	support.funcNames = typeof Function.name == 'string';

	module.exports = support;
	
	/* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))

/***/ },
/* 192 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/** Used for native method references */
	var objectProto = Object.prototype;

	/** Used to resolve the internal [[Class]] of values */
	var toString = objectProto.toString;

	/** Used to detect if a method is native */
	var reNative = RegExp('^' +
	  String(toString)
	    .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
	    .replace(/toString| for [^\]]+/g, '.*?') + '$'
	);

	/**
	 * Checks if `value` is a native function.
	 *
	 * @private
	 * @param {*} value The value to check.
	 * @returns {boolean} Returns `true` if the `value` is a native function, else `false`.
	 */
	function isNative(value) {
	  return typeof value == 'function' && reNative.test(value);
	}

	module.exports = isNative;


/***/ },
/* 193 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var objectTypes = __webpack_require__(170);

	/**
	 * Checks if `value` is the language type of Object.
	 * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
	 *
	 * @static
	 * @memberOf _
	 * @category Objects
	 * @param {*} value The value to check.
	 * @returns {boolean} Returns `true` if the `value` is an object, else `false`.
	 * @example
	 *
	 * _.isObject({});
	 * // => true
	 *
	 * _.isObject([1, 2, 3]);
	 * // => true
	 *
	 * _.isObject(1);
	 * // => false
	 */
	function isObject(value) {
	  // check if the value is the ECMAScript language type of Object
	  // http://es5.github.io/#x8
	  // and avoid a V8 bug
	  // http://code.google.com/p/v8/issues/detail?id=2291
	  return !!(value && objectTypes[typeof value]);
	}

	module.exports = isObject;


/***/ },
/* 194 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var objectTypes = __webpack_require__(170);

	/** Used for native method references */
	var objectProto = Object.prototype;

	/** Native method shortcuts */
	var hasOwnProperty = objectProto.hasOwnProperty;

	/**
	 * A fallback implementation of `Object.keys` which produces an array of the
	 * given object's own enumerable property names.
	 *
	 * @private
	 * @type Function
	 * @param {Object} object The object to inspect.
	 * @returns {Array} Returns an array of property names.
	 */
	var shimKeys = function(object) {
	  var index, iterable = object, result = [];
	  if (!iterable) return result;
	  if (!(objectTypes[typeof object])) return result;
	    for (index in iterable) {
	      if (hasOwnProperty.call(iterable, index)) {
	        result.push(index);
	      }
	    }
	  return result
	};

	module.exports = shimKeys;


/***/ },
/* 195 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var objectTypes = __webpack_require__(139);

	/**
	 * Checks if `value` is the language type of Object.
	 * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
	 *
	 * @static
	 * @memberOf _
	 * @category Objects
	 * @param {*} value The value to check.
	 * @returns {boolean} Returns `true` if the `value` is an object, else `false`.
	 * @example
	 *
	 * _.isObject({});
	 * // => true
	 *
	 * _.isObject([1, 2, 3]);
	 * // => true
	 *
	 * _.isObject(1);
	 * // => false
	 */
	function isObject(value) {
	  // check if the value is the ECMAScript language type of Object
	  // http://es5.github.io/#x8
	  // and avoid a V8 bug
	  // http://code.google.com/p/v8/issues/detail?id=2291
	  return !!(value && objectTypes[typeof value]);
	}

	module.exports = isObject;


/***/ },
/* 196 */
/***/ function(module, exports, __webpack_require__) {

	/* WEBPACK VAR INJECTION */(function(global) {/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var isNative = __webpack_require__(215),
	    isObject = __webpack_require__(198),
	    noop = __webpack_require__(213);

	/* Native method shortcuts for methods with the same name as other `lodash` methods */
	var nativeCreate = isNative(nativeCreate = Object.create) && nativeCreate;

	/**
	 * The base implementation of `_.create` without support for assigning
	 * properties to the created object.
	 *
	 * @private
	 * @param {Object} prototype The object to inherit from.
	 * @returns {Object} Returns the new object.
	 */
	function baseCreate(prototype, properties) {
	  return isObject(prototype) ? nativeCreate(prototype) : {};
	}
	// fallback for browsers without `Object.create`
	if (!nativeCreate) {
	  baseCreate = (function() {
	    function Object() {}
	    return function(prototype) {
	      if (isObject(prototype)) {
	        Object.prototype = prototype;
	        var result = new Object;
	        Object.prototype = null;
	      }
	      return result || global.Object();
	    };
	  }());
	}

	module.exports = baseCreate;
	
	/* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))

/***/ },
/* 197 */
/***/ function(module, exports, __webpack_require__) {

	/* WEBPACK VAR INJECTION */(function(global) {/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var isNative = __webpack_require__(214),
	    isObject = __webpack_require__(195),
	    noop = __webpack_require__(216);

	/* Native method shortcuts for methods with the same name as other `lodash` methods */
	var nativeCreate = isNative(nativeCreate = Object.create) && nativeCreate;

	/**
	 * The base implementation of `_.create` without support for assigning
	 * properties to the created object.
	 *
	 * @private
	 * @param {Object} prototype The object to inherit from.
	 * @returns {Object} Returns the new object.
	 */
	function baseCreate(prototype, properties) {
	  return isObject(prototype) ? nativeCreate(prototype) : {};
	}
	// fallback for browsers without `Object.create`
	if (!nativeCreate) {
	  baseCreate = (function() {
	    function Object() {}
	    return function(prototype) {
	      if (isObject(prototype)) {
	        Object.prototype = prototype;
	        var result = new Object;
	        Object.prototype = null;
	      }
	      return result || global.Object();
	    };
	  }());
	}

	module.exports = baseCreate;
	
	/* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))

/***/ },
/* 198 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var objectTypes = __webpack_require__(139);

	/**
	 * Checks if `value` is the language type of Object.
	 * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
	 *
	 * @static
	 * @memberOf _
	 * @category Objects
	 * @param {*} value The value to check.
	 * @returns {boolean} Returns `true` if the `value` is an object, else `false`.
	 * @example
	 *
	 * _.isObject({});
	 * // => true
	 *
	 * _.isObject([1, 2, 3]);
	 * // => true
	 *
	 * _.isObject(1);
	 * // => false
	 */
	function isObject(value) {
	  // check if the value is the ECMAScript language type of Object
	  // http://es5.github.io/#x8
	  // and avoid a V8 bug
	  // http://code.google.com/p/v8/issues/detail?id=2291
	  return !!(value && objectTypes[typeof value]);
	}

	module.exports = isObject;


/***/ },
/* 199 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var baseBind = __webpack_require__(217),
	    baseCreateWrapper = __webpack_require__(218),
	    isFunction = __webpack_require__(219),
	    slice = __webpack_require__(200);

	/**
	 * Used for `Array` method references.
	 *
	 * Normally `Array.prototype` would suffice, however, using an array literal
	 * avoids issues in Narwhal.
	 */
	var arrayRef = [];

	/** Native method shortcuts */
	var push = arrayRef.push,
	    unshift = arrayRef.unshift;

	/**
	 * Creates a function that, when called, either curries or invokes `func`
	 * with an optional `this` binding and partially applied arguments.
	 *
	 * @private
	 * @param {Function|string} func The function or method name to reference.
	 * @param {number} bitmask The bitmask of method flags to compose.
	 *  The bitmask may be composed of the following flags:
	 *  1 - `_.bind`
	 *  2 - `_.bindKey`
	 *  4 - `_.curry`
	 *  8 - `_.curry` (bound)
	 *  16 - `_.partial`
	 *  32 - `_.partialRight`
	 * @param {Array} [partialArgs] An array of arguments to prepend to those
	 *  provided to the new function.
	 * @param {Array} [partialRightArgs] An array of arguments to append to those
	 *  provided to the new function.
	 * @param {*} [thisArg] The `this` binding of `func`.
	 * @param {number} [arity] The arity of `func`.
	 * @returns {Function} Returns the new function.
	 */
	function createWrapper(func, bitmask, partialArgs, partialRightArgs, thisArg, arity) {
	  var isBind = bitmask & 1,
	      isBindKey = bitmask & 2,
	      isCurry = bitmask & 4,
	      isCurryBound = bitmask & 8,
	      isPartial = bitmask & 16,
	      isPartialRight = bitmask & 32;

	  if (!isBindKey && !isFunction(func)) {
	    throw new TypeError;
	  }
	  if (isPartial && !partialArgs.length) {
	    bitmask &= ~16;
	    isPartial = partialArgs = false;
	  }
	  if (isPartialRight && !partialRightArgs.length) {
	    bitmask &= ~32;
	    isPartialRight = partialRightArgs = false;
	  }
	  var bindData = func && func.__bindData__;
	  if (bindData && bindData !== true) {
	    // clone `bindData`
	    bindData = slice(bindData);
	    if (bindData[2]) {
	      bindData[2] = slice(bindData[2]);
	    }
	    if (bindData[3]) {
	      bindData[3] = slice(bindData[3]);
	    }
	    // set `thisBinding` is not previously bound
	    if (isBind && !(bindData[1] & 1)) {
	      bindData[4] = thisArg;
	    }
	    // set if previously bound but not currently (subsequent curried functions)
	    if (!isBind && bindData[1] & 1) {
	      bitmask |= 8;
	    }
	    // set curried arity if not yet set
	    if (isCurry && !(bindData[1] & 4)) {
	      bindData[5] = arity;
	    }
	    // append partial left arguments
	    if (isPartial) {
	      push.apply(bindData[2] || (bindData[2] = []), partialArgs);
	    }
	    // append partial right arguments
	    if (isPartialRight) {
	      unshift.apply(bindData[3] || (bindData[3] = []), partialRightArgs);
	    }
	    // merge flags
	    bindData[1] |= bitmask;
	    return createWrapper.apply(null, bindData);
	  }
	  // fast path for `_.bind`
	  var creater = (bitmask == 1 || bitmask === 17) ? baseBind : baseCreateWrapper;
	  return creater([func, bitmask, partialArgs, partialRightArgs, thisArg, arity]);
	}

	module.exports = createWrapper;


/***/ },
/* 200 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/**
	 * Slices the `collection` from the `start` index up to, but not including,
	 * the `end` index.
	 *
	 * Note: This function is used instead of `Array#slice` to support node lists
	 * in IE < 9 and to ensure dense arrays are returned.
	 *
	 * @private
	 * @param {Array|Object|string} collection The collection to slice.
	 * @param {number} start The start index.
	 * @param {number} end The end index.
	 * @returns {Array} Returns the new array.
	 */
	function slice(array, start, end) {
	  start || (start = 0);
	  if (typeof end == 'undefined') {
	    end = array ? array.length : 0;
	  }
	  var index = -1,
	      length = end - start || 0,
	      result = Array(length < 0 ? 0 : length);

	  while (++index < length) {
	    result[index] = array[start + index];
	  }
	  return result;
	}

	module.exports = slice;


/***/ },
/* 201 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/** Used for native method references */
	var objectProto = Object.prototype;

	/** Used to resolve the internal [[Class]] of values */
	var toString = objectProto.toString;

	/** Used to detect if a method is native */
	var reNative = RegExp('^' +
	  String(toString)
	    .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
	    .replace(/toString| for [^\]]+/g, '.*?') + '$'
	);

	/**
	 * Checks if `value` is a native function.
	 *
	 * @private
	 * @param {*} value The value to check.
	 * @returns {boolean} Returns `true` if the `value` is a native function, else `false`.
	 */
	function isNative(value) {
	  return typeof value == 'function' && reNative.test(value);
	}

	module.exports = isNative;


/***/ },
/* 202 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/**
	 * A no-operation function.
	 *
	 * @static
	 * @memberOf _
	 * @category Utilities
	 * @example
	 *
	 * var object = { 'name': 'fred' };
	 * _.noop(object) === undefined;
	 * // => true
	 */
	function noop() {
	  // no operation performed
	}

	module.exports = noop;


/***/ },
/* 203 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/** Used for native method references */
	var objectProto = Object.prototype;

	/** Used to resolve the internal [[Class]] of values */
	var toString = objectProto.toString;

	/** Used to detect if a method is native */
	var reNative = RegExp('^' +
	  String(toString)
	    .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
	    .replace(/toString| for [^\]]+/g, '.*?') + '$'
	);

	/**
	 * Checks if `value` is a native function.
	 *
	 * @private
	 * @param {*} value The value to check.
	 * @returns {boolean} Returns `true` if the `value` is a native function, else `false`.
	 */
	function isNative(value) {
	  return typeof value == 'function' && reNative.test(value);
	}

	module.exports = isNative;


/***/ },
/* 204 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/** Used to pool arrays and objects used internally */
	var arrayPool = [];

	module.exports = arrayPool;


/***/ },
/* 205 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/** Used to pool arrays and objects used internally */
	var arrayPool = [];

	module.exports = arrayPool;


/***/ },
/* 206 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/** Used as the max size of the `arrayPool` and `objectPool` */
	var maxPoolSize = 40;

	module.exports = maxPoolSize;


/***/ },
/* 207 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/** Used to determine if values are of the language type Object */
	var objectTypes = {
	  'boolean': false,
	  'function': true,
	  'object': true,
	  'number': false,
	  'string': false,
	  'undefined': false
	};

	module.exports = objectTypes;


/***/ },
/* 208 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var baseBind = __webpack_require__(220),
	    baseCreateWrapper = __webpack_require__(221),
	    isFunction = __webpack_require__(222),
	    slice = __webpack_require__(209);

	/**
	 * Used for `Array` method references.
	 *
	 * Normally `Array.prototype` would suffice, however, using an array literal
	 * avoids issues in Narwhal.
	 */
	var arrayRef = [];

	/** Native method shortcuts */
	var push = arrayRef.push,
	    unshift = arrayRef.unshift;

	/**
	 * Creates a function that, when called, either curries or invokes `func`
	 * with an optional `this` binding and partially applied arguments.
	 *
	 * @private
	 * @param {Function|string} func The function or method name to reference.
	 * @param {number} bitmask The bitmask of method flags to compose.
	 *  The bitmask may be composed of the following flags:
	 *  1 - `_.bind`
	 *  2 - `_.bindKey`
	 *  4 - `_.curry`
	 *  8 - `_.curry` (bound)
	 *  16 - `_.partial`
	 *  32 - `_.partialRight`
	 * @param {Array} [partialArgs] An array of arguments to prepend to those
	 *  provided to the new function.
	 * @param {Array} [partialRightArgs] An array of arguments to append to those
	 *  provided to the new function.
	 * @param {*} [thisArg] The `this` binding of `func`.
	 * @param {number} [arity] The arity of `func`.
	 * @returns {Function} Returns the new function.
	 */
	function createWrapper(func, bitmask, partialArgs, partialRightArgs, thisArg, arity) {
	  var isBind = bitmask & 1,
	      isBindKey = bitmask & 2,
	      isCurry = bitmask & 4,
	      isCurryBound = bitmask & 8,
	      isPartial = bitmask & 16,
	      isPartialRight = bitmask & 32;

	  if (!isBindKey && !isFunction(func)) {
	    throw new TypeError;
	  }
	  if (isPartial && !partialArgs.length) {
	    bitmask &= ~16;
	    isPartial = partialArgs = false;
	  }
	  if (isPartialRight && !partialRightArgs.length) {
	    bitmask &= ~32;
	    isPartialRight = partialRightArgs = false;
	  }
	  var bindData = func && func.__bindData__;
	  if (bindData && bindData !== true) {
	    // clone `bindData`
	    bindData = slice(bindData);
	    if (bindData[2]) {
	      bindData[2] = slice(bindData[2]);
	    }
	    if (bindData[3]) {
	      bindData[3] = slice(bindData[3]);
	    }
	    // set `thisBinding` is not previously bound
	    if (isBind && !(bindData[1] & 1)) {
	      bindData[4] = thisArg;
	    }
	    // set if previously bound but not currently (subsequent curried functions)
	    if (!isBind && bindData[1] & 1) {
	      bitmask |= 8;
	    }
	    // set curried arity if not yet set
	    if (isCurry && !(bindData[1] & 4)) {
	      bindData[5] = arity;
	    }
	    // append partial left arguments
	    if (isPartial) {
	      push.apply(bindData[2] || (bindData[2] = []), partialArgs);
	    }
	    // append partial right arguments
	    if (isPartialRight) {
	      unshift.apply(bindData[3] || (bindData[3] = []), partialRightArgs);
	    }
	    // merge flags
	    bindData[1] |= bitmask;
	    return createWrapper.apply(null, bindData);
	  }
	  // fast path for `_.bind`
	  var creater = (bitmask == 1 || bitmask === 17) ? baseBind : baseCreateWrapper;
	  return creater([func, bitmask, partialArgs, partialRightArgs, thisArg, arity]);
	}

	module.exports = createWrapper;


/***/ },
/* 209 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/**
	 * Slices the `collection` from the `start` index up to, but not including,
	 * the `end` index.
	 *
	 * Note: This function is used instead of `Array#slice` to support node lists
	 * in IE < 9 and to ensure dense arrays are returned.
	 *
	 * @private
	 * @param {Array|Object|string} collection The collection to slice.
	 * @param {number} start The start index.
	 * @param {number} end The end index.
	 * @returns {Array} Returns the new array.
	 */
	function slice(array, start, end) {
	  start || (start = 0);
	  if (typeof end == 'undefined') {
	    end = array ? array.length : 0;
	  }
	  var index = -1,
	      length = end - start || 0,
	      result = Array(length < 0 ? 0 : length);

	  while (++index < length) {
	    result[index] = array[start + index];
	  }
	  return result;
	}

	module.exports = slice;


/***/ },
/* 210 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/** Used for native method references */
	var objectProto = Object.prototype;

	/** Used to resolve the internal [[Class]] of values */
	var toString = objectProto.toString;

	/** Used to detect if a method is native */
	var reNative = RegExp('^' +
	  String(toString)
	    .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
	    .replace(/toString| for [^\]]+/g, '.*?') + '$'
	);

	/**
	 * Checks if `value` is a native function.
	 *
	 * @private
	 * @param {*} value The value to check.
	 * @returns {boolean} Returns `true` if the `value` is a native function, else `false`.
	 */
	function isNative(value) {
	  return typeof value == 'function' && reNative.test(value);
	}

	module.exports = isNative;


/***/ },
/* 211 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/**
	 * A no-operation function.
	 *
	 * @static
	 * @memberOf _
	 * @category Utilities
	 * @example
	 *
	 * var object = { 'name': 'fred' };
	 * _.noop(object) === undefined;
	 * // => true
	 */
	function noop() {
	  // no operation performed
	}

	module.exports = noop;


/***/ },
/* 212 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/** Used for native method references */
	var objectProto = Object.prototype;

	/** Used to resolve the internal [[Class]] of values */
	var toString = objectProto.toString;

	/** Used to detect if a method is native */
	var reNative = RegExp('^' +
	  String(toString)
	    .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
	    .replace(/toString| for [^\]]+/g, '.*?') + '$'
	);

	/**
	 * Checks if `value` is a native function.
	 *
	 * @private
	 * @param {*} value The value to check.
	 * @returns {boolean} Returns `true` if the `value` is a native function, else `false`.
	 */
	function isNative(value) {
	  return typeof value == 'function' && reNative.test(value);
	}

	module.exports = isNative;


/***/ },
/* 213 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/**
	 * A no-operation function.
	 *
	 * @static
	 * @memberOf _
	 * @category Utilities
	 * @example
	 *
	 * var object = { 'name': 'fred' };
	 * _.noop(object) === undefined;
	 * // => true
	 */
	function noop() {
	  // no operation performed
	}

	module.exports = noop;


/***/ },
/* 214 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/** Used for native method references */
	var objectProto = Object.prototype;

	/** Used to resolve the internal [[Class]] of values */
	var toString = objectProto.toString;

	/** Used to detect if a method is native */
	var reNative = RegExp('^' +
	  String(toString)
	    .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
	    .replace(/toString| for [^\]]+/g, '.*?') + '$'
	);

	/**
	 * Checks if `value` is a native function.
	 *
	 * @private
	 * @param {*} value The value to check.
	 * @returns {boolean} Returns `true` if the `value` is a native function, else `false`.
	 */
	function isNative(value) {
	  return typeof value == 'function' && reNative.test(value);
	}

	module.exports = isNative;


/***/ },
/* 215 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/** Used for native method references */
	var objectProto = Object.prototype;

	/** Used to resolve the internal [[Class]] of values */
	var toString = objectProto.toString;

	/** Used to detect if a method is native */
	var reNative = RegExp('^' +
	  String(toString)
	    .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
	    .replace(/toString| for [^\]]+/g, '.*?') + '$'
	);

	/**
	 * Checks if `value` is a native function.
	 *
	 * @private
	 * @param {*} value The value to check.
	 * @returns {boolean} Returns `true` if the `value` is a native function, else `false`.
	 */
	function isNative(value) {
	  return typeof value == 'function' && reNative.test(value);
	}

	module.exports = isNative;


/***/ },
/* 216 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/**
	 * A no-operation function.
	 *
	 * @static
	 * @memberOf _
	 * @category Utilities
	 * @example
	 *
	 * var object = { 'name': 'fred' };
	 * _.noop(object) === undefined;
	 * // => true
	 */
	function noop() {
	  // no operation performed
	}

	module.exports = noop;


/***/ },
/* 217 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var baseCreate = __webpack_require__(223),
	    isObject = __webpack_require__(165),
	    setBindData = __webpack_require__(178),
	    slice = __webpack_require__(200);

	/**
	 * Used for `Array` method references.
	 *
	 * Normally `Array.prototype` would suffice, however, using an array literal
	 * avoids issues in Narwhal.
	 */
	var arrayRef = [];

	/** Native method shortcuts */
	var push = arrayRef.push;

	/**
	 * The base implementation of `_.bind` that creates the bound function and
	 * sets its meta data.
	 *
	 * @private
	 * @param {Array} bindData The bind data array.
	 * @returns {Function} Returns the new bound function.
	 */
	function baseBind(bindData) {
	  var func = bindData[0],
	      partialArgs = bindData[2],
	      thisArg = bindData[4];

	  function bound() {
	    // `Function#bind` spec
	    // http://es5.github.io/#x15.3.4.5
	    if (partialArgs) {
	      // avoid `arguments` object deoptimizations by using `slice` instead
	      // of `Array.prototype.slice.call` and not assigning `arguments` to a
	      // variable as a ternary expression
	      var args = slice(partialArgs);
	      push.apply(args, arguments);
	    }
	    // mimic the constructor's `return` behavior
	    // http://es5.github.io/#x13.2.2
	    if (this instanceof bound) {
	      // ensure `new bound` is an instance of `func`
	      var thisBinding = baseCreate(func.prototype),
	          result = func.apply(thisBinding, args || arguments);
	      return isObject(result) ? result : thisBinding;
	    }
	    return func.apply(thisArg, args || arguments);
	  }
	  setBindData(bound, bindData);
	  return bound;
	}

	module.exports = baseBind;


/***/ },
/* 218 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var baseCreate = __webpack_require__(225),
	    isObject = __webpack_require__(165),
	    setBindData = __webpack_require__(178),
	    slice = __webpack_require__(200);

	/**
	 * Used for `Array` method references.
	 *
	 * Normally `Array.prototype` would suffice, however, using an array literal
	 * avoids issues in Narwhal.
	 */
	var arrayRef = [];

	/** Native method shortcuts */
	var push = arrayRef.push;

	/**
	 * The base implementation of `createWrapper` that creates the wrapper and
	 * sets its meta data.
	 *
	 * @private
	 * @param {Array} bindData The bind data array.
	 * @returns {Function} Returns the new function.
	 */
	function baseCreateWrapper(bindData) {
	  var func = bindData[0],
	      bitmask = bindData[1],
	      partialArgs = bindData[2],
	      partialRightArgs = bindData[3],
	      thisArg = bindData[4],
	      arity = bindData[5];

	  var isBind = bitmask & 1,
	      isBindKey = bitmask & 2,
	      isCurry = bitmask & 4,
	      isCurryBound = bitmask & 8,
	      key = func;

	  function bound() {
	    var thisBinding = isBind ? thisArg : this;
	    if (partialArgs) {
	      var args = slice(partialArgs);
	      push.apply(args, arguments);
	    }
	    if (partialRightArgs || isCurry) {
	      args || (args = slice(arguments));
	      if (partialRightArgs) {
	        push.apply(args, partialRightArgs);
	      }
	      if (isCurry && args.length < arity) {
	        bitmask |= 16 & ~32;
	        return baseCreateWrapper([func, (isCurryBound ? bitmask : bitmask & ~3), args, null, thisArg, arity]);
	      }
	    }
	    args || (args = arguments);
	    if (isBindKey) {
	      func = thisBinding[key];
	    }
	    if (this instanceof bound) {
	      thisBinding = baseCreate(func.prototype);
	      var result = func.apply(thisBinding, args);
	      return isObject(result) ? result : thisBinding;
	    }
	    return func.apply(thisBinding, args);
	  }
	  setBindData(bound, bindData);
	  return bound;
	}

	module.exports = baseCreateWrapper;


/***/ },
/* 219 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/**
	 * Checks if `value` is a function.
	 *
	 * @static
	 * @memberOf _
	 * @category Objects
	 * @param {*} value The value to check.
	 * @returns {boolean} Returns `true` if the `value` is a function, else `false`.
	 * @example
	 *
	 * _.isFunction(_);
	 * // => true
	 */
	function isFunction(value) {
	  return typeof value == 'function';
	}

	module.exports = isFunction;


/***/ },
/* 220 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var baseCreate = __webpack_require__(224),
	    isObject = __webpack_require__(226),
	    setBindData = __webpack_require__(190),
	    slice = __webpack_require__(209);

	/**
	 * Used for `Array` method references.
	 *
	 * Normally `Array.prototype` would suffice, however, using an array literal
	 * avoids issues in Narwhal.
	 */
	var arrayRef = [];

	/** Native method shortcuts */
	var push = arrayRef.push;

	/**
	 * The base implementation of `_.bind` that creates the bound function and
	 * sets its meta data.
	 *
	 * @private
	 * @param {Array} bindData The bind data array.
	 * @returns {Function} Returns the new bound function.
	 */
	function baseBind(bindData) {
	  var func = bindData[0],
	      partialArgs = bindData[2],
	      thisArg = bindData[4];

	  function bound() {
	    // `Function#bind` spec
	    // http://es5.github.io/#x15.3.4.5
	    if (partialArgs) {
	      // avoid `arguments` object deoptimizations by using `slice` instead
	      // of `Array.prototype.slice.call` and not assigning `arguments` to a
	      // variable as a ternary expression
	      var args = slice(partialArgs);
	      push.apply(args, arguments);
	    }
	    // mimic the constructor's `return` behavior
	    // http://es5.github.io/#x13.2.2
	    if (this instanceof bound) {
	      // ensure `new bound` is an instance of `func`
	      var thisBinding = baseCreate(func.prototype),
	          result = func.apply(thisBinding, args || arguments);
	      return isObject(result) ? result : thisBinding;
	    }
	    return func.apply(thisArg, args || arguments);
	  }
	  setBindData(bound, bindData);
	  return bound;
	}

	module.exports = baseBind;


/***/ },
/* 221 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var baseCreate = __webpack_require__(227),
	    isObject = __webpack_require__(228),
	    setBindData = __webpack_require__(190),
	    slice = __webpack_require__(209);

	/**
	 * Used for `Array` method references.
	 *
	 * Normally `Array.prototype` would suffice, however, using an array literal
	 * avoids issues in Narwhal.
	 */
	var arrayRef = [];

	/** Native method shortcuts */
	var push = arrayRef.push;

	/**
	 * The base implementation of `createWrapper` that creates the wrapper and
	 * sets its meta data.
	 *
	 * @private
	 * @param {Array} bindData The bind data array.
	 * @returns {Function} Returns the new function.
	 */
	function baseCreateWrapper(bindData) {
	  var func = bindData[0],
	      bitmask = bindData[1],
	      partialArgs = bindData[2],
	      partialRightArgs = bindData[3],
	      thisArg = bindData[4],
	      arity = bindData[5];

	  var isBind = bitmask & 1,
	      isBindKey = bitmask & 2,
	      isCurry = bitmask & 4,
	      isCurryBound = bitmask & 8,
	      key = func;

	  function bound() {
	    var thisBinding = isBind ? thisArg : this;
	    if (partialArgs) {
	      var args = slice(partialArgs);
	      push.apply(args, arguments);
	    }
	    if (partialRightArgs || isCurry) {
	      args || (args = slice(arguments));
	      if (partialRightArgs) {
	        push.apply(args, partialRightArgs);
	      }
	      if (isCurry && args.length < arity) {
	        bitmask |= 16 & ~32;
	        return baseCreateWrapper([func, (isCurryBound ? bitmask : bitmask & ~3), args, null, thisArg, arity]);
	      }
	    }
	    args || (args = arguments);
	    if (isBindKey) {
	      func = thisBinding[key];
	    }
	    if (this instanceof bound) {
	      thisBinding = baseCreate(func.prototype);
	      var result = func.apply(thisBinding, args);
	      return isObject(result) ? result : thisBinding;
	    }
	    return func.apply(thisBinding, args);
	  }
	  setBindData(bound, bindData);
	  return bound;
	}

	module.exports = baseCreateWrapper;


/***/ },
/* 222 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/**
	 * Checks if `value` is a function.
	 *
	 * @static
	 * @memberOf _
	 * @category Objects
	 * @param {*} value The value to check.
	 * @returns {boolean} Returns `true` if the `value` is a function, else `false`.
	 * @example
	 *
	 * _.isFunction(_);
	 * // => true
	 */
	function isFunction(value) {
	  return typeof value == 'function';
	}

	module.exports = isFunction;


/***/ },
/* 223 */
/***/ function(module, exports, __webpack_require__) {

	/* WEBPACK VAR INJECTION */(function(global) {/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var isNative = __webpack_require__(231),
	    isObject = __webpack_require__(165),
	    noop = __webpack_require__(229);

	/* Native method shortcuts for methods with the same name as other `lodash` methods */
	var nativeCreate = isNative(nativeCreate = Object.create) && nativeCreate;

	/**
	 * The base implementation of `_.create` without support for assigning
	 * properties to the created object.
	 *
	 * @private
	 * @param {Object} prototype The object to inherit from.
	 * @returns {Object} Returns the new object.
	 */
	function baseCreate(prototype, properties) {
	  return isObject(prototype) ? nativeCreate(prototype) : {};
	}
	// fallback for browsers without `Object.create`
	if (!nativeCreate) {
	  baseCreate = (function() {
	    function Object() {}
	    return function(prototype) {
	      if (isObject(prototype)) {
	        Object.prototype = prototype;
	        var result = new Object;
	        Object.prototype = null;
	      }
	      return result || global.Object();
	    };
	  }());
	}

	module.exports = baseCreate;
	
	/* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))

/***/ },
/* 224 */
/***/ function(module, exports, __webpack_require__) {

	/* WEBPACK VAR INJECTION */(function(global) {/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var isNative = __webpack_require__(230),
	    isObject = __webpack_require__(226),
	    noop = __webpack_require__(232);

	/* Native method shortcuts for methods with the same name as other `lodash` methods */
	var nativeCreate = isNative(nativeCreate = Object.create) && nativeCreate;

	/**
	 * The base implementation of `_.create` without support for assigning
	 * properties to the created object.
	 *
	 * @private
	 * @param {Object} prototype The object to inherit from.
	 * @returns {Object} Returns the new object.
	 */
	function baseCreate(prototype, properties) {
	  return isObject(prototype) ? nativeCreate(prototype) : {};
	}
	// fallback for browsers without `Object.create`
	if (!nativeCreate) {
	  baseCreate = (function() {
	    function Object() {}
	    return function(prototype) {
	      if (isObject(prototype)) {
	        Object.prototype = prototype;
	        var result = new Object;
	        Object.prototype = null;
	      }
	      return result || global.Object();
	    };
	  }());
	}

	module.exports = baseCreate;
	
	/* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))

/***/ },
/* 225 */
/***/ function(module, exports, __webpack_require__) {

	/* WEBPACK VAR INJECTION */(function(global) {/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var isNative = __webpack_require__(233),
	    isObject = __webpack_require__(165),
	    noop = __webpack_require__(234);

	/* Native method shortcuts for methods with the same name as other `lodash` methods */
	var nativeCreate = isNative(nativeCreate = Object.create) && nativeCreate;

	/**
	 * The base implementation of `_.create` without support for assigning
	 * properties to the created object.
	 *
	 * @private
	 * @param {Object} prototype The object to inherit from.
	 * @returns {Object} Returns the new object.
	 */
	function baseCreate(prototype, properties) {
	  return isObject(prototype) ? nativeCreate(prototype) : {};
	}
	// fallback for browsers without `Object.create`
	if (!nativeCreate) {
	  baseCreate = (function() {
	    function Object() {}
	    return function(prototype) {
	      if (isObject(prototype)) {
	        Object.prototype = prototype;
	        var result = new Object;
	        Object.prototype = null;
	      }
	      return result || global.Object();
	    };
	  }());
	}

	module.exports = baseCreate;
	
	/* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))

/***/ },
/* 226 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var objectTypes = __webpack_require__(170);

	/**
	 * Checks if `value` is the language type of Object.
	 * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
	 *
	 * @static
	 * @memberOf _
	 * @category Objects
	 * @param {*} value The value to check.
	 * @returns {boolean} Returns `true` if the `value` is an object, else `false`.
	 * @example
	 *
	 * _.isObject({});
	 * // => true
	 *
	 * _.isObject([1, 2, 3]);
	 * // => true
	 *
	 * _.isObject(1);
	 * // => false
	 */
	function isObject(value) {
	  // check if the value is the ECMAScript language type of Object
	  // http://es5.github.io/#x8
	  // and avoid a V8 bug
	  // http://code.google.com/p/v8/issues/detail?id=2291
	  return !!(value && objectTypes[typeof value]);
	}

	module.exports = isObject;


/***/ },
/* 227 */
/***/ function(module, exports, __webpack_require__) {

	/* WEBPACK VAR INJECTION */(function(global) {/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var isNative = __webpack_require__(235),
	    isObject = __webpack_require__(228),
	    noop = __webpack_require__(236);

	/* Native method shortcuts for methods with the same name as other `lodash` methods */
	var nativeCreate = isNative(nativeCreate = Object.create) && nativeCreate;

	/**
	 * The base implementation of `_.create` without support for assigning
	 * properties to the created object.
	 *
	 * @private
	 * @param {Object} prototype The object to inherit from.
	 * @returns {Object} Returns the new object.
	 */
	function baseCreate(prototype, properties) {
	  return isObject(prototype) ? nativeCreate(prototype) : {};
	}
	// fallback for browsers without `Object.create`
	if (!nativeCreate) {
	  baseCreate = (function() {
	    function Object() {}
	    return function(prototype) {
	      if (isObject(prototype)) {
	        Object.prototype = prototype;
	        var result = new Object;
	        Object.prototype = null;
	      }
	      return result || global.Object();
	    };
	  }());
	}

	module.exports = baseCreate;
	
	/* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))

/***/ },
/* 228 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */
	var objectTypes = __webpack_require__(170);

	/**
	 * Checks if `value` is the language type of Object.
	 * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
	 *
	 * @static
	 * @memberOf _
	 * @category Objects
	 * @param {*} value The value to check.
	 * @returns {boolean} Returns `true` if the `value` is an object, else `false`.
	 * @example
	 *
	 * _.isObject({});
	 * // => true
	 *
	 * _.isObject([1, 2, 3]);
	 * // => true
	 *
	 * _.isObject(1);
	 * // => false
	 */
	function isObject(value) {
	  // check if the value is the ECMAScript language type of Object
	  // http://es5.github.io/#x8
	  // and avoid a V8 bug
	  // http://code.google.com/p/v8/issues/detail?id=2291
	  return !!(value && objectTypes[typeof value]);
	}

	module.exports = isObject;


/***/ },
/* 229 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/**
	 * A no-operation function.
	 *
	 * @static
	 * @memberOf _
	 * @category Utilities
	 * @example
	 *
	 * var object = { 'name': 'fred' };
	 * _.noop(object) === undefined;
	 * // => true
	 */
	function noop() {
	  // no operation performed
	}

	module.exports = noop;


/***/ },
/* 230 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/** Used for native method references */
	var objectProto = Object.prototype;

	/** Used to resolve the internal [[Class]] of values */
	var toString = objectProto.toString;

	/** Used to detect if a method is native */
	var reNative = RegExp('^' +
	  String(toString)
	    .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
	    .replace(/toString| for [^\]]+/g, '.*?') + '$'
	);

	/**
	 * Checks if `value` is a native function.
	 *
	 * @private
	 * @param {*} value The value to check.
	 * @returns {boolean} Returns `true` if the `value` is a native function, else `false`.
	 */
	function isNative(value) {
	  return typeof value == 'function' && reNative.test(value);
	}

	module.exports = isNative;


/***/ },
/* 231 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/** Used for native method references */
	var objectProto = Object.prototype;

	/** Used to resolve the internal [[Class]] of values */
	var toString = objectProto.toString;

	/** Used to detect if a method is native */
	var reNative = RegExp('^' +
	  String(toString)
	    .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
	    .replace(/toString| for [^\]]+/g, '.*?') + '$'
	);

	/**
	 * Checks if `value` is a native function.
	 *
	 * @private
	 * @param {*} value The value to check.
	 * @returns {boolean} Returns `true` if the `value` is a native function, else `false`.
	 */
	function isNative(value) {
	  return typeof value == 'function' && reNative.test(value);
	}

	module.exports = isNative;


/***/ },
/* 232 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/**
	 * A no-operation function.
	 *
	 * @static
	 * @memberOf _
	 * @category Utilities
	 * @example
	 *
	 * var object = { 'name': 'fred' };
	 * _.noop(object) === undefined;
	 * // => true
	 */
	function noop() {
	  // no operation performed
	}

	module.exports = noop;


/***/ },
/* 233 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/** Used for native method references */
	var objectProto = Object.prototype;

	/** Used to resolve the internal [[Class]] of values */
	var toString = objectProto.toString;

	/** Used to detect if a method is native */
	var reNative = RegExp('^' +
	  String(toString)
	    .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
	    .replace(/toString| for [^\]]+/g, '.*?') + '$'
	);

	/**
	 * Checks if `value` is a native function.
	 *
	 * @private
	 * @param {*} value The value to check.
	 * @returns {boolean} Returns `true` if the `value` is a native function, else `false`.
	 */
	function isNative(value) {
	  return typeof value == 'function' && reNative.test(value);
	}

	module.exports = isNative;


/***/ },
/* 234 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/**
	 * A no-operation function.
	 *
	 * @static
	 * @memberOf _
	 * @category Utilities
	 * @example
	 *
	 * var object = { 'name': 'fred' };
	 * _.noop(object) === undefined;
	 * // => true
	 */
	function noop() {
	  // no operation performed
	}

	module.exports = noop;


/***/ },
/* 235 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/** Used for native method references */
	var objectProto = Object.prototype;

	/** Used to resolve the internal [[Class]] of values */
	var toString = objectProto.toString;

	/** Used to detect if a method is native */
	var reNative = RegExp('^' +
	  String(toString)
	    .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
	    .replace(/toString| for [^\]]+/g, '.*?') + '$'
	);

	/**
	 * Checks if `value` is a native function.
	 *
	 * @private
	 * @param {*} value The value to check.
	 * @returns {boolean} Returns `true` if the `value` is a native function, else `false`.
	 */
	function isNative(value) {
	  return typeof value == 'function' && reNative.test(value);
	}

	module.exports = isNative;


/***/ },
/* 236 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
	 * Build: `lodash modularize modern exports="npm" -o ./npm/`
	 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
	 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
	 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	 * Available under MIT license <http://lodash.com/license>
	 */

	/**
	 * A no-operation function.
	 *
	 * @static
	 * @memberOf _
	 * @category Utilities
	 * @example
	 *
	 * var object = { 'name': 'fred' };
	 * _.noop(object) === undefined;
	 * // => true
	 */
	function noop() {
	  // no operation performed
	}

	module.exports = noop;


/***/ }
/******/ ])
});
