o: ActiveSupport::Cache::Entry :@compressedF:@expires_in0:@created_atf1361309866.799406: @value"ð‹{I" class:EFI"BundledAsset;FI"logical_path;FI"showboat/index.js;FI" pathname;FI"M/Users/mkitt/Sites/showboat/app/assets/javascripts/showboat/index.coffee;FI"content_type;FI"application/javascript;FI" mtime;FI"2013-02-19T14:37:39-07:00;FI" length;FiŠI" digest;F"%ebf482420dd1daf022ca54f5d5a67492I" source;FI"Š// Underscore.js 1.4.3 // http://underscorejs.org // (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. // Underscore may be freely distributed under the MIT license. (function() { // Baseline setup // -------------- // Establish the root object, `window` in the browser, or `global` on the server. var root = this; // Save the previous value of the `_` variable. var previousUnderscore = root._; // Establish the object that gets returned to break out of a loop iteration. var breaker = {}; // Save bytes in the minified (but not gzipped) version: var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; // Create quick reference variables for speed access to core prototypes. var push = ArrayProto.push, slice = ArrayProto.slice, concat = ArrayProto.concat, toString = ObjProto.toString, hasOwnProperty = ObjProto.hasOwnProperty; // All **ECMAScript 5** native function implementations that we hope to use // are declared here. var nativeForEach = ArrayProto.forEach, nativeMap = ArrayProto.map, nativeReduce = ArrayProto.reduce, nativeReduceRight = ArrayProto.reduceRight, nativeFilter = ArrayProto.filter, nativeEvery = ArrayProto.every, nativeSome = ArrayProto.some, nativeIndexOf = ArrayProto.indexOf, nativeLastIndexOf = ArrayProto.lastIndexOf, nativeIsArray = Array.isArray, nativeKeys = Object.keys, nativeBind = FuncProto.bind; // Create a safe reference to the Underscore object for use below. var _ = function(obj) { if (obj instanceof _) return obj; if (!(this instanceof _)) return new _(obj); this._wrapped = obj; }; // Export the Underscore object for **Node.js**, with // backwards-compatibility for the old `require()` API. If we're in // the browser, add `_` as a global object via a string identifier, // for Closure Compiler "advanced" mode. if (typeof exports !== 'undefined') { if (typeof module !== 'undefined' && module.exports) { exports = module.exports = _; } exports._ = _; } else { root._ = _; } // Current version. _.VERSION = '1.4.3'; // Collection Functions // -------------------- // The cornerstone, an `each` implementation, aka `forEach`. // Handles objects with the built-in `forEach`, arrays, and raw objects. // Delegates to **ECMAScript 5**'s native `forEach` if available. var each = _.each = _.forEach = function(obj, iterator, context) { if (obj == null) return; if (nativeForEach && obj.forEach === nativeForEach) { obj.forEach(iterator, context); } else if (obj.length === +obj.length) { for (var i = 0, l = obj.length; i < l; i++) { if (iterator.call(context, obj[i], i, obj) === breaker) return; } } else { for (var key in obj) { if (_.has(obj, key)) { if (iterator.call(context, obj[key], key, obj) === breaker) return; } } } }; // Return the results of applying the iterator to each element. // Delegates to **ECMAScript 5**'s native `map` if available. _.map = _.collect = function(obj, iterator, context) { var results = []; if (obj == null) return results; if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); each(obj, function(value, index, list) { results[results.length] = iterator.call(context, value, index, list); }); return results; }; var reduceError = 'Reduce of empty array with no initial value'; // **Reduce** builds up a single result from a list of values, aka `inject`, // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { var initial = arguments.length > 2; if (obj == null) obj = []; if (nativeReduce && obj.reduce === nativeReduce) { if (context) iterator = _.bind(iterator, context); return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); } each(obj, function(value, index, list) { if (!initial) { memo = value; initial = true; } else { memo = iterator.call(context, memo, value, index, list); } }); if (!initial) throw new TypeError(reduceError); return memo; }; // The right-associative version of reduce, also known as `foldr`. // Delegates to **ECMAScript 5**'s native `reduceRight` if available. _.reduceRight = _.foldr = function(obj, iterator, memo, context) { var initial = arguments.length > 2; if (obj == null) obj = []; if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { if (context) iterator = _.bind(iterator, context); return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); } var length = obj.length; if (length !== +length) { var keys = _.keys(obj); length = keys.length; } each(obj, function(value, index, list) { index = keys ? keys[--length] : --length; if (!initial) { memo = obj[index]; initial = true; } else { memo = iterator.call(context, memo, obj[index], index, list); } }); if (!initial) throw new TypeError(reduceError); return memo; }; // Return the first value which passes a truth test. Aliased as `detect`. _.find = _.detect = function(obj, iterator, context) { var result; any(obj, function(value, index, list) { if (iterator.call(context, value, index, list)) { result = value; return true; } }); return result; }; // Return all the elements that pass a truth test. // Delegates to **ECMAScript 5**'s native `filter` if available. // Aliased as `select`. _.filter = _.select = function(obj, iterator, context) { var results = []; if (obj == null) return results; if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context); each(obj, function(value, index, list) { if (iterator.call(context, value, index, list)) results[results.length] = value; }); return results; }; // Return all the elements for which a truth test fails. _.reject = function(obj, iterator, context) { return _.filter(obj, function(value, index, list) { return !iterator.call(context, value, index, list); }, context); }; // Determine whether all of the elements match a truth test. // Delegates to **ECMAScript 5**'s native `every` if available. // Aliased as `all`. _.every = _.all = function(obj, iterator, context) { iterator || (iterator = _.identity); var result = true; if (obj == null) return result; if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context); each(obj, function(value, index, list) { if (!(result = result && iterator.call(context, value, index, list))) return breaker; }); return !!result; }; // Determine if at least one element in the object matches a truth test. // Delegates to **ECMAScript 5**'s native `some` if available. // Aliased as `any`. var any = _.some = _.any = function(obj, iterator, context) { iterator || (iterator = _.identity); var result = false; if (obj == null) return result; if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); each(obj, function(value, index, list) { if (result || (result = iterator.call(context, value, index, list))) return breaker; }); return !!result; }; // Determine if the array or object contains a given value (using `===`). // Aliased as `include`. _.contains = _.include = function(obj, target) { if (obj == null) return false; if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; return any(obj, function(value) { return value === target; }); }; // Invoke a method (with arguments) on every item in a collection. _.invoke = function(obj, method) { var args = slice.call(arguments, 2); return _.map(obj, function(value) { return (_.isFunction(method) ? method : value[method]).apply(value, args); }); }; // Convenience version of a common use case of `map`: fetching a property. _.pluck = function(obj, key) { return _.map(obj, function(value){ return value[key]; }); }; // Convenience version of a common use case of `filter`: selecting only objects // with specific `key:value` pairs. _.where = function(obj, attrs) { if (_.isEmpty(attrs)) return []; return _.filter(obj, function(value) { for (var key in attrs) { if (attrs[key] !== value[key]) return false; } return true; }); }; // Return the maximum element or (element-based computation). // Can't optimize arrays of integers longer than 65,535 elements. // See: https://bugs.webkit.org/show_bug.cgi?id=80797 _.max = function(obj, iterator, context) { if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { return Math.max.apply(Math, obj); } if (!iterator && _.isEmpty(obj)) return -Infinity; var result = {computed : -Infinity, value: -Infinity}; each(obj, function(value, index, list) { var computed = iterator ? iterator.call(context, value, index, list) : value; computed >= result.computed && (result = {value : value, computed : computed}); }); return result.value; }; // Return the minimum element (or element-based computation). _.min = function(obj, iterator, context) { if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { return Math.min.apply(Math, obj); } if (!iterator && _.isEmpty(obj)) return Infinity; var result = {computed : Infinity, value: Infinity}; each(obj, function(value, index, list) { var computed = iterator ? iterator.call(context, value, index, list) : value; computed < result.computed && (result = {value : value, computed : computed}); }); return result.value; }; // Shuffle an array. _.shuffle = function(obj) { var rand; var index = 0; var shuffled = []; each(obj, function(value) { rand = _.random(index++); shuffled[index - 1] = shuffled[rand]; shuffled[rand] = value; }); return shuffled; }; // An internal function to generate lookup iterators. var lookupIterator = function(value) { return _.isFunction(value) ? value : function(obj){ return obj[value]; }; }; // Sort the object's values by a criterion produced by an iterator. _.sortBy = function(obj, value, context) { var iterator = lookupIterator(value); return _.pluck(_.map(obj, function(value, index, list) { return { value : value, index : index, criteria : iterator.call(context, value, index, list) }; }).sort(function(left, right) { var a = left.criteria; var b = right.criteria; if (a !== b) { if (a > b || a === void 0) return 1; if (a < b || b === void 0) return -1; } return left.index < right.index ? -1 : 1; }), 'value'); }; // An internal function used for aggregate "group by" operations. var group = function(obj, value, context, behavior) { var result = {}; var iterator = lookupIterator(value || _.identity); each(obj, function(value, index) { var key = iterator.call(context, value, index, obj); behavior(result, key, value); }); return result; }; // Groups the object's values by a criterion. Pass either a string attribute // to group by, or a function that returns the criterion. _.groupBy = function(obj, value, context) { return group(obj, value, context, function(result, key, value) { (_.has(result, key) ? result[key] : (result[key] = [])).push(value); }); }; // Counts instances of an object that group by a certain criterion. Pass // either a string attribute to count by, or a function that returns the // criterion. _.countBy = function(obj, value, context) { return group(obj, value, context, function(result, key) { if (!_.has(result, key)) result[key] = 0; result[key]++; }); }; // Use a comparator function to figure out the smallest index at which // an object should be inserted so as to maintain order. Uses binary search. _.sortedIndex = function(array, obj, iterator, context) { iterator = iterator == null ? _.identity : lookupIterator(iterator); var value = iterator.call(context, obj); var low = 0, high = array.length; while (low < high) { var mid = (low + high) >>> 1; iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid; } return low; }; // Safely convert anything iterable into a real, live array. _.toArray = function(obj) { if (!obj) return []; if (_.isArray(obj)) return slice.call(obj); if (obj.length === +obj.length) return _.map(obj, _.identity); return _.values(obj); }; // Return the number of elements in an object. _.size = function(obj) { if (obj == null) return 0; return (obj.length === +obj.length) ? obj.length : _.keys(obj).length; }; // Array Functions // --------------- // Get the first element of an array. Passing **n** will return the first N // values in the array. Aliased as `head` and `take`. The **guard** check // allows it to work with `_.map`. _.first = _.head = _.take = function(array, n, guard) { if (array == null) return void 0; return (n != null) && !guard ? slice.call(array, 0, n) : array[0]; }; // Returns everything but the last entry of the array. Especially useful on // the arguments object. Passing **n** will return all the values in // the array, excluding the last N. The **guard** check allows it to work with // `_.map`. _.initial = function(array, n, guard) { return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n)); }; // Get the last element of an array. Passing **n** will return the last N // values in the array. The **guard** check allows it to work with `_.map`. _.last = function(array, n, guard) { if (array == null) return void 0; if ((n != null) && !guard) { return slice.call(array, Math.max(array.length - n, 0)); } else { return array[array.length - 1]; } }; // Returns everything but the first entry of the array. Aliased as `tail` and `drop`. // Especially useful on the arguments object. Passing an **n** will return // the rest N values in the array. The **guard** // check allows it to work with `_.map`. _.rest = _.tail = _.drop = function(array, n, guard) { return slice.call(array, (n == null) || guard ? 1 : n); }; // Trim out all falsy values from an array. _.compact = function(array) { return _.filter(array, _.identity); }; // Internal implementation of a recursive `flatten` function. var flatten = function(input, shallow, output) { each(input, function(value) { if (_.isArray(value)) { shallow ? push.apply(output, value) : flatten(value, shallow, output); } else { output.push(value); } }); return output; }; // Return a completely flattened version of an array. _.flatten = function(array, shallow) { return flatten(array, shallow, []); }; // Return a version of the array that does not contain the specified value(s). _.without = function(array) { return _.difference(array, slice.call(arguments, 1)); }; // Produce a duplicate-free version of the array. If the array has already // been sorted, you have the option of using a faster algorithm. // Aliased as `unique`. _.uniq = _.unique = function(array, isSorted, iterator, context) { if (_.isFunction(isSorted)) { context = iterator; iterator = isSorted; isSorted = false; } var initial = iterator ? _.map(array, iterator, context) : array; var results = []; var seen = []; each(initial, function(value, index) { if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) { seen.push(value); results.push(array[index]); } }); return results; }; // Produce an array that contains the union: each distinct element from all of // the passed-in arrays. _.union = function() { return _.uniq(concat.apply(ArrayProto, arguments)); }; // Produce an array that contains every item shared between all the // passed-in arrays. _.intersection = function(array) { var rest = slice.call(arguments, 1); return _.filter(_.uniq(array), function(item) { return _.every(rest, function(other) { return _.indexOf(other, item) >= 0; }); }); }; // Take the difference between one array and a number of other arrays. // Only the elements present in just the first array will remain. _.difference = function(array) { var rest = concat.apply(ArrayProto, slice.call(arguments, 1)); return _.filter(array, function(value){ return !_.contains(rest, value); }); }; // Zip together multiple lists into a single array -- elements that share // an index go together. _.zip = function() { var args = slice.call(arguments); var length = _.max(_.pluck(args, 'length')); var results = new Array(length); for (var i = 0; i < length; i++) { results[i] = _.pluck(args, "" + i); } return results; }; // Converts lists into objects. Pass either a single array of `[key, value]` // pairs, or two parallel arrays of the same length -- one of keys, and one of // the corresponding values. _.object = function(list, values) { if (list == null) return {}; var result = {}; for (var i = 0, l = list.length; i < l; i++) { if (values) { result[list[i]] = values[i]; } else { result[list[i][0]] = list[i][1]; } } return result; }; // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), // we need this function. Return the position of the first occurrence of an // item in an array, or -1 if the item is not included in the array. // Delegates to **ECMAScript 5**'s native `indexOf` if available. // If the array is large and already in sort order, pass `true` // for **isSorted** to use binary search. _.indexOf = function(array, item, isSorted) { if (array == null) return -1; var i = 0, l = array.length; if (isSorted) { if (typeof isSorted == 'number') { i = (isSorted < 0 ? Math.max(0, l + isSorted) : isSorted); } else { i = _.sortedIndex(array, item); return array[i] === item ? i : -1; } } if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted); for (; i < l; i++) if (array[i] === item) return i; return -1; }; // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. _.lastIndexOf = function(array, item, from) { if (array == null) return -1; var hasIndex = from != null; if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) { return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item); } var i = (hasIndex ? from : array.length); while (i--) if (array[i] === item) return i; return -1; }; // Generate an integer Array containing an arithmetic progression. A port of // the native Python `range()` function. See // [the Python documentation](http://docs.python.org/library/functions.html#range). _.range = function(start, stop, step) { if (arguments.length <= 1) { stop = start || 0; start = 0; } step = arguments[2] || 1; var len = Math.max(Math.ceil((stop - start) / step), 0); var idx = 0; var range = new Array(len); while(idx < len) { range[idx++] = start; start += step; } return range; }; // Function (ahem) Functions // ------------------ // Reusable constructor function for prototype setting. var ctor = function(){}; // Create a function bound to a given object (assigning `this`, and arguments, // optionally). Binding with arguments is also known as `curry`. // Delegates to **ECMAScript 5**'s native `Function.bind` if available. // We check for `func.bind` first, to fail fast when `func` is undefined. _.bind = function(func, context) { var args, bound; if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); if (!_.isFunction(func)) throw new TypeError; args = slice.call(arguments, 2); return bound = function() { if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments))); ctor.prototype = func.prototype; var self = new ctor; ctor.prototype = null; var result = func.apply(self, args.concat(slice.call(arguments))); if (Object(result) === result) return result; return self; }; }; // Bind all of an object's methods to that object. Useful for ensuring that // all callbacks defined on an object belong to it. _.bindAll = function(obj) { var funcs = slice.call(arguments, 1); if (funcs.length == 0) funcs = _.functions(obj); each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); return obj; }; // Memoize an expensive function by storing its results. _.memoize = function(func, hasher) { var memo = {}; hasher || (hasher = _.identity); return function() { var key = hasher.apply(this, arguments); return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); }; }; // Delays a function for the given number of milliseconds, and then calls // it with the arguments supplied. _.delay = function(func, wait) { var args = slice.call(arguments, 2); return setTimeout(function(){ return func.apply(null, args); }, wait); }; // Defers a function, scheduling it to run after the current call stack has // cleared. _.defer = function(func) { return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); }; // Returns a function, that, when invoked, will only be triggered at most once // during a given window of time. _.throttle = function(func, wait) { var context, args, timeout, result; var previous = 0; var later = function() { previous = new Date; timeout = null; result = func.apply(context, args); }; return function() { var now = new Date; var remaining = wait - (now - previous); context = this; args = arguments; if (remaining <= 0) { clearTimeout(timeout); timeout = null; previous = now; result = func.apply(context, args); } else if (!timeout) { timeout = setTimeout(later, remaining); } return result; }; }; // Returns a function, that, as long as it continues to be invoked, will not // be triggered. The function will be called after it stops being called for // N milliseconds. If `immediate` is passed, trigger the function on the // leading edge, instead of the trailing. _.debounce = function(func, wait, immediate) { var timeout, result; return function() { var context = this, args = arguments; var later = function() { timeout = null; if (!immediate) result = func.apply(context, args); }; var callNow = immediate && !timeout; clearTimeout(timeout); timeout = setTimeout(later, wait); if (callNow) result = func.apply(context, args); return result; }; }; // Returns a function that will be executed at most one time, no matter how // often you call it. Useful for lazy initialization. _.once = function(func) { var ran = false, memo; return function() { if (ran) return memo; ran = true; memo = func.apply(this, arguments); func = null; return memo; }; }; // Returns the first function passed as an argument to the second, // allowing you to adjust arguments, run code before and after, and // conditionally execute the original function. _.wrap = function(func, wrapper) { return function() { var args = [func]; push.apply(args, arguments); return wrapper.apply(this, args); }; }; // Returns a function that is the composition of a list of functions, each // consuming the return value of the function that follows. _.compose = function() { var funcs = arguments; return function() { var args = arguments; for (var i = funcs.length - 1; i >= 0; i--) { args = [funcs[i].apply(this, args)]; } return args[0]; }; }; // Returns a function that will only be executed after being called N times. _.after = function(times, func) { if (times <= 0) return func(); return function() { if (--times < 1) { return func.apply(this, arguments); } }; }; // Object Functions // ---------------- // Retrieve the names of an object's properties. // Delegates to **ECMAScript 5**'s native `Object.keys` _.keys = nativeKeys || function(obj) { if (obj !== Object(obj)) throw new TypeError('Invalid object'); var keys = []; for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key; return keys; }; // Retrieve the values of an object's properties. _.values = function(obj) { var values = []; for (var key in obj) if (_.has(obj, key)) values.push(obj[key]); return values; }; // Convert an object into a list of `[key, value]` pairs. _.pairs = function(obj) { var pairs = []; for (var key in obj) if (_.has(obj, key)) pairs.push([key, obj[key]]); return pairs; }; // Invert the keys and values of an object. The values must be serializable. _.invert = function(obj) { var result = {}; for (var key in obj) if (_.has(obj, key)) result[obj[key]] = key; return result; }; // Return a sorted list of the function names available on the object. // Aliased as `methods` _.functions = _.methods = function(obj) { var names = []; for (var key in obj) { if (_.isFunction(obj[key])) names.push(key); } return names.sort(); }; // Extend a given object with all the properties in passed-in object(s). _.extend = function(obj) { each(slice.call(arguments, 1), function(source) { if (source) { for (var prop in source) { obj[prop] = source[prop]; } } }); return obj; }; // Return a copy of the object only containing the whitelisted properties. _.pick = function(obj) { var copy = {}; var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); each(keys, function(key) { if (key in obj) copy[key] = obj[key]; }); return copy; }; // Return a copy of the object without the blacklisted properties. _.omit = function(obj) { var copy = {}; var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); for (var key in obj) { if (!_.contains(keys, key)) copy[key] = obj[key]; } return copy; }; // Fill in a given object with default properties. _.defaults = function(obj) { each(slice.call(arguments, 1), function(source) { if (source) { for (var prop in source) { if (obj[prop] == null) obj[prop] = source[prop]; } } }); return obj; }; // Create a (shallow-cloned) duplicate of an object. _.clone = function(obj) { if (!_.isObject(obj)) return obj; return _.isArray(obj) ? obj.slice() : _.extend({}, obj); }; // Invokes interceptor with the obj, and then returns obj. // The primary purpose of this method is to "tap into" a method chain, in // order to perform operations on intermediate results within the chain. _.tap = function(obj, interceptor) { interceptor(obj); return obj; }; // Internal recursive comparison function for `isEqual`. var eq = function(a, b, aStack, bStack) { // Identical objects are equal. `0 === -0`, but they aren't identical. // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal. if (a === b) return a !== 0 || 1 / a == 1 / b; // A strict comparison is necessary because `null == undefined`. if (a == null || b == null) return a === b; // Unwrap any wrapped objects. if (a instanceof _) a = a._wrapped; if (b instanceof _) b = b._wrapped; // Compare `[[Class]]` names. var className = toString.call(a); if (className != toString.call(b)) return false; switch (className) { // Strings, numbers, dates, and booleans are compared by value. case '[object String]': // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is // equivalent to `new String("5")`. return a == String(b); case '[object Number]': // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for // other numeric values. return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b); case '[object Date]': case '[object Boolean]': // Coerce dates and booleans to numeric primitive values. Dates are compared by their // millisecond representations. Note that invalid dates with millisecond representations // of `NaN` are not equivalent. return +a == +b; // RegExps are compared by their source patterns and flags. case '[object RegExp]': return a.source == b.source && a.global == b.global && a.multiline == b.multiline && a.ignoreCase == b.ignoreCase; } if (typeof a != 'object' || typeof b != 'object') return false; // Assume equality for cyclic structures. The algorithm for detecting cyclic // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. var length = aStack.length; while (length--) { // Linear search. Performance is inversely proportional to the number of // unique nested structures. if (aStack[length] == a) return bStack[length] == b; } // Add the first object to the stack of traversed objects. aStack.push(a); bStack.push(b); var size = 0, result = true; // Recursively compare objects and arrays. if (className == '[object Array]') { // Compare array lengths to determine if a deep comparison is necessary. size = a.length; result = size == b.length; if (result) { // Deep compare the contents, ignoring non-numeric properties. while (size--) { if (!(result = eq(a[size], b[size], aStack, bStack))) break; } } } else { // Objects with different constructors are not equivalent, but `Object`s // from different frames are. var aCtor = a.constructor, bCtor = b.constructor; if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) && _.isFunction(bCtor) && (bCtor instanceof bCtor))) { return false; } // Deep compare objects. for (var key in a) { if (_.has(a, key)) { // Count the expected number of properties. size++; // Deep compare each member. if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break; } } // Ensure that both objects contain the same number of properties. if (result) { for (key in b) { if (_.has(b, key) && !(size--)) break; } result = !size; } } // Remove the first object from the stack of traversed objects. aStack.pop(); bStack.pop(); return result; }; // Perform a deep comparison to check if two objects are equal. _.isEqual = function(a, b) { return eq(a, b, [], []); }; // Is a given array, string, or object empty? // An "empty" object has no enumerable own-properties. _.isEmpty = function(obj) { if (obj == null) return true; if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; for (var key in obj) if (_.has(obj, key)) return false; return true; }; // Is a given value a DOM element? _.isElement = function(obj) { return !!(obj && obj.nodeType === 1); }; // Is a given value an array? // Delegates to ECMA5's native Array.isArray _.isArray = nativeIsArray || function(obj) { return toString.call(obj) == '[object Array]'; }; // Is a given variable an object? _.isObject = function(obj) { return obj === Object(obj); }; // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp. each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) { _['is' + name] = function(obj) { return toString.call(obj) == '[object ' + name + ']'; }; }); // Define a fallback version of the method in browsers (ahem, IE), where // there isn't any inspectable "Arguments" type. if (!_.isArguments(arguments)) { _.isArguments = function(obj) { return !!(obj && _.has(obj, 'callee')); }; } // Optimize `isFunction` if appropriate. if (typeof (/./) !== 'function') { _.isFunction = function(obj) { return typeof obj === 'function'; }; } // Is a given object a finite number? _.isFinite = function(obj) { return isFinite(obj) && !isNaN(parseFloat(obj)); }; // Is the given value `NaN`? (NaN is the only number which does not equal itself). _.isNaN = function(obj) { return _.isNumber(obj) && obj != +obj; }; // Is a given value a boolean? _.isBoolean = function(obj) { return obj === true || obj === false || toString.call(obj) == '[object Boolean]'; }; // Is a given value equal to null? _.isNull = function(obj) { return obj === null; }; // Is a given variable undefined? _.isUndefined = function(obj) { return obj === void 0; }; // Shortcut function for checking if an object has a given property directly // on itself (in other words, not on a prototype). _.has = function(obj, key) { return hasOwnProperty.call(obj, key); }; // Utility Functions // ----------------- // Run Underscore.js in *noConflict* mode, returning the `_` variable to its // previous owner. Returns a reference to the Underscore object. _.noConflict = function() { root._ = previousUnderscore; return this; }; // Keep the identity function around for default iterators. _.identity = function(value) { return value; }; // Run a function **n** times. _.times = function(n, iterator, context) { var accum = Array(n); for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i); return accum; }; // Return a random integer between min and max (inclusive). _.random = function(min, max) { if (max == null) { max = min; min = 0; } return min + (0 | Math.random() * (max - min + 1)); }; // List of HTML entities for escaping. var entityMap = { escape: { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', '/': '/' } }; entityMap.unescape = _.invert(entityMap.escape); // Regexes containing the keys and values listed immediately above. var entityRegexes = { escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'), unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g') }; // Functions for escaping and unescaping strings to/from HTML interpolation. _.each(['escape', 'unescape'], function(method) { _[method] = function(string) { if (string == null) return ''; return ('' + string).replace(entityRegexes[method], function(match) { return entityMap[method][match]; }); }; }); // If the value of the named property is a function then invoke it; // otherwise, return it. _.result = function(object, property) { if (object == null) return null; var value = object[property]; return _.isFunction(value) ? value.call(object) : value; }; // Add your own custom functions to the Underscore object. _.mixin = function(obj) { each(_.functions(obj), function(name){ var func = _[name] = obj[name]; _.prototype[name] = function() { var args = [this._wrapped]; push.apply(args, arguments); return result.call(this, func.apply(_, args)); }; }); }; // Generate a unique integer id (unique within the entire client session). // Useful for temporary DOM ids. var idCounter = 0; _.uniqueId = function(prefix) { var id = '' + ++idCounter; return prefix ? prefix + id : id; }; // By default, Underscore uses ERB-style template delimiters, change the // following template settings to use alternative delimiters. _.templateSettings = { evaluate : /<%([\s\S]+?)%>/g, interpolate : /<%=([\s\S]+?)%>/g, escape : /<%-([\s\S]+?)%>/g }; // When customizing `templateSettings`, if you don't want to define an // interpolation, evaluation or escaping regex, we need one that is // guaranteed not to match. var noMatch = /(.)^/; // Certain characters need to be escaped so that they can be put into a // string literal. var escapes = { "'": "'", '\\': '\\', '\r': 'r', '\n': 'n', '\t': 't', '\u2028': 'u2028', '\u2029': 'u2029' }; var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; // JavaScript micro-templating, similar to John Resig's implementation. // Underscore templating handles arbitrary delimiters, preserves whitespace, // and correctly escapes quotes within interpolated code. _.template = function(text, data, settings) { settings = _.defaults({}, settings, _.templateSettings); // Combine delimiters into one regular expression via alternation. var matcher = new RegExp([ (settings.escape || noMatch).source, (settings.interpolate || noMatch).source, (settings.evaluate || noMatch).source ].join('|') + '|$', 'g'); // Compile the template source, escaping string literals appropriately. var index = 0; var source = "__p+='"; text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { source += text.slice(index, offset) .replace(escaper, function(match) { return '\\' + escapes[match]; }); if (escape) { source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; } if (interpolate) { source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; } if (evaluate) { source += "';\n" + evaluate + "\n__p+='"; } index = offset + match.length; return match; }); source += "';\n"; // If a variable is not specified, place data values in local scope. if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; source = "var __t,__p='',__j=Array.prototype.join," + "print=function(){__p+=__j.call(arguments,'');};\n" + source + "return __p;\n"; try { var render = new Function(settings.variable || 'obj', '_', source); } catch (e) { e.source = source; throw e; } if (data) return render(data, _); var template = function(data) { return render.call(this, data, _); }; // Provide the compiled function source as a convenience for precompilation. template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; return template; }; // Add a "chain" function, which will delegate to the wrapper. _.chain = function(obj) { return _(obj).chain(); }; // OOP // --------------- // If Underscore is called as a function, it returns a wrapped object that // can be used OO-style. This wrapper holds altered versions of all the // underscore functions. Wrapped objects may be chained. // Helper function to continue chaining intermediate results. var result = function(obj) { return this._chain ? _(obj).chain() : obj; }; // Add all of the Underscore functions to the wrapper object. _.mixin(_); // Add all mutator Array functions to the wrapper. each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { var method = ArrayProto[name]; _.prototype[name] = function() { var obj = this._wrapped; method.apply(obj, arguments); if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0]; return result.call(this, obj); }; }); // Add all accessor Array functions to the wrapper. each(['concat', 'join', 'slice'], function(name) { var method = ArrayProto[name]; _.prototype[name] = function() { return result.call(this, method.apply(this._wrapped, arguments)); }; }); _.extend(_.prototype, { // Start chaining a wrapped Underscore object. chain: function() { this._chain = true; return this; }, // Extracts the result from a wrapped and chained object. value: function() { return this._wrapped; } }); }).call(this); /** Head JS The only script in your Copyright Tero Piirainen (tipiirai) License MIT / http://bit.ly/mit-license Version 0.96 http://headjs.com */ (function(a){function z(){d||(d=!0,s(e,function(a){p(a)}))}function y(c,d){var e=a.createElement("script");e.type="text/"+(c.type||"javascript"),e.src=c.src||c,e.async=!1,e.onreadystatechange=e.onload=function(){var a=e.readyState;!d.done&&(!a||/loaded|complete/.test(a))&&(d.done=!0,d())},(a.body||b).appendChild(e)}function x(a,b){if(a.state==o)return b&&b();if(a.state==n)return k.ready(a.name,b);if(a.state==m)return a.onpreload.push(function(){x(a,b)});a.state=n,y(a.url,function(){a.state=o,b&&b(),s(g[a.name],function(a){p(a)}),u()&&d&&s(g.ALL,function(a){p(a)})})}function w(a,b){a.state===undefined&&(a.state=m,a.onpreload=[],y({src:a.url,type:"cache"},function(){v(a)}))}function v(a){a.state=l,s(a.onpreload,function(a){a.call()})}function u(a){a=a||h;var b;for(var c in a){if(a.hasOwnProperty(c)&&a[c].state!=o)return!1;b=!0}return b}function t(a){return Object.prototype.toString.call(a)=="[object Function]"}function s(a,b){if(!!a){typeof a=="object"&&(a=[].slice.call(a));for(var c=0;c' + '' + ''; dom.wrapper.appendChild( controlsElement ); } // Presentation background element if( !dom.wrapper.querySelector( '.state-background' ) ) { var backgroundElement = document.createElement( 'div' ); backgroundElement.classList.add( 'state-background' ); dom.wrapper.appendChild( backgroundElement ); } // Overlay graphic which is displayed during the paused mode if( !dom.wrapper.querySelector( '.pause-overlay' ) ) { var pausedElement = document.createElement( 'div' ); pausedElement.classList.add( 'pause-overlay' ); dom.wrapper.appendChild( pausedElement ); } // Cache references to elements dom.progress = document.querySelector( '.reveal .progress' ); dom.progressbar = document.querySelector( '.reveal .progress span' ); if ( config.controls ) { dom.controls = document.querySelector( '.reveal .controls' ); // There can be multiple instances of controls throughout the page dom.controlsLeft = toArray( document.querySelectorAll( '.navigate-left' ) ); dom.controlsRight = toArray( document.querySelectorAll( '.navigate-right' ) ); dom.controlsUp = toArray( document.querySelectorAll( '.navigate-up' ) ); dom.controlsDown = toArray( document.querySelectorAll( '.navigate-down' ) ); dom.controlsPrev = toArray( document.querySelectorAll( '.navigate-prev' ) ); dom.controlsNext = toArray( document.querySelectorAll( '.navigate-next' ) ); } } /** * Hides the address bar if we're on a mobile device. */ function hideAddressBar() { if( navigator.userAgent.match( /(iphone|ipod)/i ) ) { // Give the page some scrollable overflow document.documentElement.style.overflow = 'scroll'; document.body.style.height = '120%'; // Events that should trigger the address bar to hide window.addEventListener( 'load', removeAddressBar, false ); window.addEventListener( 'orientationchange', removeAddressBar, false ); } } /** * Loads the dependencies of reveal.js. Dependencies are * defined via the configuration option 'dependencies' * and will be loaded prior to starting/binding reveal.js. * Some dependencies may have an 'async' flag, if so they * will load after reveal.js has been started up. */ function load() { var scripts = [], scriptsAsync = []; for( var i = 0, len = config.dependencies.length; i < len; i++ ) { var s = config.dependencies[i]; // Load if there's no condition or the condition is truthy if( !s.condition || s.condition() ) { if( s.async ) { scriptsAsync.push( s.src ); } else { scripts.push( s.src ); } // Extension may contain callback functions if( typeof s.callback === 'function' ) { head.ready( s.src.match( /([\w\d_\-]*)\.?js$|[^\\\/]*$/i )[0], s.callback ); } } } // Called once synchronous scritps finish loading function proceed() { if( scriptsAsync.length ) { // Load asynchronous scripts head.js.apply( null, scriptsAsync ); } start(); } if( scripts.length ) { head.ready( proceed ); // Load synchronous scripts head.js.apply( null, scripts ); } else { proceed(); } } /** * Starts up reveal.js by binding input events and navigating * to the current URL deeplink if there is one. */ function start() { // Make sure we've got all the DOM elements we need setupDOM(); // Subscribe to input addEventListeners(); // Updates the presentation to match the current configuration values configure(); // Force an initial layout, will thereafter be invoked as the window // is resized layout(); // Read the initial hash readURL(); // Start auto-sliding if it's enabled cueAutoSlide(); // Notify listeners that the presentation is ready but use a 1ms // timeout to ensure it's not fired synchronously after #initialize() setTimeout( function() { dispatchEvent( 'ready', { 'indexh': indexh, 'indexv': indexv, 'currentSlide': currentSlide } ); }, 1 ); } /** * Applies the configuration settings from the config object. */ function configure() { if( supports3DTransforms === false ) { config.transition = 'linear'; } if( config.controls && dom.controls ) { dom.controls.style.display = 'block'; } if( config.progress && dom.progress ) { dom.progress.style.display = 'block'; } if( config.transition !== 'default' ) { dom.wrapper.classList.add( config.transition ); } if( config.rtl ) { dom.wrapper.classList.add( 'rtl' ); } if( config.center ) { dom.wrapper.classList.add( 'center' ); } if( config.mouseWheel ) { document.addEventListener( 'DOMMouseScroll', onDocumentMouseScroll, false ); // FF document.addEventListener( 'mousewheel', onDocumentMouseScroll, false ); } // 3D links if( config.rollingLinks ) { linkify(); } // Load the theme in the config, if it's not already loaded if( config.theme && dom.theme ) { var themeURL = dom.theme.getAttribute( 'href' ); var themeFinder = /[^\/]*?(?=\.css)/; var themeName = themeURL.match(themeFinder)[0]; if( config.theme !== themeName ) { themeURL = themeURL.replace(themeFinder, config.theme); dom.theme.setAttribute( 'href', themeURL ); } } } /** * Binds all event listeners. */ function addEventListeners() { window.addEventListener( 'hashchange', onWindowHashChange, false ); window.addEventListener( 'resize', onWindowResize, false ); if( config.touch ) { document.addEventListener( 'touchstart', onDocumentTouchStart, false ); document.addEventListener( 'touchmove', onDocumentTouchMove, false ); document.addEventListener( 'touchend', onDocumentTouchEnd, false ); } if( config.keyboard ) { document.addEventListener( 'keydown', onDocumentKeyDown, false ); } if ( config.progress && dom.progress ) { dom.progress.addEventListener( 'click', preventAndForward( onProgressClick ), false ); } if ( config.controls && dom.controls ) { var actionEvent = 'ontouchstart' in window ? 'touchstart' : 'click'; dom.controlsLeft.forEach( function( el ) { el.addEventListener( actionEvent, preventAndForward( navigateLeft ), false ); } ); dom.controlsRight.forEach( function( el ) { el.addEventListener( actionEvent, preventAndForward( navigateRight ), false ); } ); dom.controlsUp.forEach( function( el ) { el.addEventListener( actionEvent, preventAndForward( navigateUp ), false ); } ); dom.controlsDown.forEach( function( el ) { el.addEventListener( actionEvent, preventAndForward( navigateDown ), false ); } ); dom.controlsPrev.forEach( function( el ) { el.addEventListener( actionEvent, preventAndForward( navigatePrev ), false ); } ); dom.controlsNext.forEach( function( el ) { el.addEventListener( actionEvent, preventAndForward( navigateNext ), false ); } ); } } /** * Unbinds all event listeners. */ function removeEventListeners() { document.removeEventListener( 'keydown', onDocumentKeyDown, false ); window.removeEventListener( 'hashchange', onWindowHashChange, false ); window.removeEventListener( 'resize', onWindowResize, false ); if( config.touch ) { document.removeEventListener( 'touchstart', onDocumentTouchStart, false ); document.removeEventListener( 'touchmove', onDocumentTouchMove, false ); document.removeEventListener( 'touchend', onDocumentTouchEnd, false ); } if ( config.progress && dom.progress ) { dom.progress.removeEventListener( 'click', preventAndForward( onProgressClick ), false ); } if ( config.controls && dom.controls ) { var actionEvent = 'ontouchstart' in window ? 'touchstart' : 'click'; dom.controlsLeft.forEach( function( el ) { el.removeEventListener( actionEvent, preventAndForward( navigateLeft ), false ); } ); dom.controlsRight.forEach( function( el ) { el.removeEventListener( actionEvent, preventAndForward( navigateRight ), false ); } ); dom.controlsUp.forEach( function( el ) { el.removeEventListener( actionEvent, preventAndForward( navigateUp ), false ); } ); dom.controlsDown.forEach( function( el ) { el.removeEventListener( actionEvent, preventAndForward( navigateDown ), false ); } ); dom.controlsPrev.forEach( function( el ) { el.removeEventListener( actionEvent, preventAndForward( navigatePrev ), false ); } ); dom.controlsNext.forEach( function( el ) { el.removeEventListener( actionEvent, preventAndForward( navigateNext ), false ); } ); } } /** * Extend object a with the properties of object b. * If there's a conflict, object b takes precedence. */ function extend( a, b ) { for( var i in b ) { a[ i ] = b[ i ]; } } /** * Converts the target object to an array. */ function toArray( o ) { return Array.prototype.slice.call( o ); } /** * Measures the distance in pixels between point a * and point b. * * @param {Object} a point with x/y properties * @param {Object} b point with x/y properties */ function distanceBetween( a, b ) { var dx = a.x - b.x, dy = a.y - b.y; return Math.sqrt( dx*dx + dy*dy ); } /** * Prevents an events defaults behavior calls the * specified delegate. * * @param {Function} delegate The method to call * after the wrapper has been executed */ function preventAndForward( delegate ) { return function( event ) { event.preventDefault(); delegate.call( null, event ); }; } /** * Causes the address bar to hide on mobile devices, * more vertical space ftw. */ function removeAddressBar() { setTimeout( function() { window.scrollTo( 0, 1 ); }, 0 ); } /** * Dispatches an event of the specified type from the * reveal DOM element. */ function dispatchEvent( type, properties ) { var event = document.createEvent( "HTMLEvents", 1, 2 ); event.initEvent( type, true, true ); extend( event, properties ); dom.wrapper.dispatchEvent( event ); } /** * Wrap all links in 3D goodness. */ function linkify() { if( supports3DTransforms && !( 'msPerspective' in document.body.style ) ) { var nodes = document.querySelectorAll( SLIDES_SELECTOR + ' a:not(.image)' ); for( var i = 0, len = nodes.length; i < len; i++ ) { var node = nodes[i]; if( node.textContent && !node.querySelector( '*' ) && ( !node.className || !node.classList.contains( node, 'roll' ) ) ) { var span = document.createElement('span'); span.setAttribute('data-title', node.text); span.innerHTML = node.innerHTML; node.classList.add( 'roll' ); node.innerHTML = ''; node.appendChild(span); } } } } /** * Applies JavaScript-controlled layout rules to the * presentation. */ function layout() { if( config.center ) { // Select all slides, vertical and horizontal var slides = toArray( document.querySelectorAll( SLIDES_SELECTOR ) ); // Determine the minimum top offset for slides var minTop = -dom.wrapper.offsetHeight / 2; for( var i = 0, len = slides.length; i < len; i++ ) { var slide = slides[ i ]; // Don't bother updating invisible slides if( slide.style.display === 'none' ) { continue; } // Vertical stacks are not centered since their section // children will be if( slide.classList.contains( 'stack' ) ) { slide.style.top = 0; } else { slide.style.top = Math.max( - ( slide.offsetHeight / 2 ) - 20, minTop ) + 'px'; } } } } /** * Stores the vertical index of a stack so that the same * vertical slide can be selected when navigating to and * from the stack. * * @param {HTMLElement} stack The vertical stack element * @param {int} v Index to memorize */ function setPreviousVerticalIndex( stack, v ) { if( stack ) { stack.setAttribute( 'data-previous-indexv', v || 0 ); } } /** * Retrieves the vertical index which was stored using * #setPreviousVerticalIndex() or 0 if no previous index * exists. * * @param {HTMLElement} stack The vertical stack element */ function getPreviousVerticalIndex( stack ) { if( stack && stack.classList.contains( 'stack' ) ) { return parseInt( stack.getAttribute( 'data-previous-indexv' ) || 0, 10 ); } return 0; } /** * Displays the overview of slides (quick nav) by * scaling down and arranging all slide elements. * * Experimental feature, might be dropped if perf * can't be improved. */ function activateOverview() { // Only proceed if enabled in config if( config.overview ) { var wasActive = dom.wrapper.classList.contains( 'overview' ); dom.wrapper.classList.add( 'overview' ); dom.wrapper.classList.remove( 'exit-overview' ); clearTimeout( activateOverviewTimeout ); clearTimeout( deactivateOverviewTimeout ); // Not the pretties solution, but need to let the overview // class apply first so that slides are measured accurately // before we can position them activateOverviewTimeout = setTimeout( function(){ var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ); for( var i = 0, len1 = horizontalSlides.length; i < len1; i++ ) { var hslide = horizontalSlides[i], htransform = 'translateZ(-2500px) translate(' + ( ( i - indexh ) * 105 ) + '%, 0%)'; hslide.setAttribute( 'data-index-h', i ); hslide.style.display = 'block'; hslide.style.WebkitTransform = htransform; hslide.style.MozTransform = htransform; hslide.style.msTransform = htransform; hslide.style.OTransform = htransform; hslide.style.transform = htransform; if( hslide.classList.contains( 'stack' ) ) { var verticalSlides = hslide.querySelectorAll( 'section' ); for( var j = 0, len2 = verticalSlides.length; j < len2; j++ ) { var verticalIndex = i === indexh ? indexv : getPreviousVerticalIndex( hslide ); var vslide = verticalSlides[j], vtransform = 'translate(0%, ' + ( ( j - verticalIndex ) * 105 ) + '%)'; vslide.setAttribute( 'data-index-h', i ); vslide.setAttribute( 'data-index-v', j ); vslide.style.display = 'block'; vslide.style.WebkitTransform = vtransform; vslide.style.MozTransform = vtransform; vslide.style.msTransform = vtransform; vslide.style.OTransform = vtransform; vslide.style.transform = vtransform; // Navigate to this slide on click vslide.addEventListener( 'click', onOverviewSlideClicked, true ); } } else { // Navigate to this slide on click hslide.addEventListener( 'click', onOverviewSlideClicked, true ); } } layout(); if( !wasActive ) { // Notify observers of the overview showing dispatchEvent( 'overviewshown', { 'indexh': indexh, 'indexv': indexv, 'currentSlide': currentSlide } ); } }, 10 ); } } /** * Exits the slide overview and enters the currently * active slide. */ function deactivateOverview() { // Only proceed if enabled in config if( config.overview ) { clearTimeout( activateOverviewTimeout ); clearTimeout( deactivateOverviewTimeout ); dom.wrapper.classList.remove( 'overview' ); // Temporarily add a class so that transitions can do different things // depending on whether they are exiting/entering overview, or just // moving from slide to slide dom.wrapper.classList.add( 'exit-overview' ); deactivateOverviewTimeout = setTimeout( function () { dom.wrapper.classList.remove( 'exit-overview' ); }, 10); // Select all slides var slides = toArray( document.querySelectorAll( SLIDES_SELECTOR ) ); for( var i = 0, len = slides.length; i < len; i++ ) { var element = slides[i]; element.style.display = ''; // Resets all transforms to use the external styles element.style.WebkitTransform = ''; element.style.MozTransform = ''; element.style.msTransform = ''; element.style.OTransform = ''; element.style.transform = ''; element.removeEventListener( 'click', onOverviewSlideClicked, true ); } slide( indexh, indexv ); // Notify observers of the overview hiding dispatchEvent( 'overviewhidden', { 'indexh': indexh, 'indexv': indexv, 'currentSlide': currentSlide } ); } } /** * Toggles the slide overview mode on and off. * * @param {Boolean} override Optional flag which overrides the * toggle logic and forcibly sets the desired state. True means * overview is open, false means it's closed. */ function toggleOverview( override ) { if( typeof override === 'boolean' ) { override ? activateOverview() : deactivateOverview(); } else { isOverviewActive() ? deactivateOverview() : activateOverview(); } } /** * Checks if the overview is currently active. * * @return {Boolean} true if the overview is active, * false otherwise */ function isOverviewActive() { return dom.wrapper.classList.contains( 'overview' ); } /** * Handling the fullscreen functionality via the fullscreen API * * @see http://fullscreen.spec.whatwg.org/ * @see https://developer.mozilla.org/en-US/docs/DOM/Using_fullscreen_mode */ function enterFullscreen() { var element = document.body; // Check which implementation is available var requestMethod = element.requestFullScreen || element.webkitRequestFullScreen || element.mozRequestFullScreen || element.msRequestFullScreen; if( requestMethod ) { requestMethod.apply( element ); } } /** * Enters the paused mode which fades everything on screen to * black. */ function pause() { dom.wrapper.classList.add( 'paused' ); } /** * Exits from the paused mode. */ function resume() { dom.wrapper.classList.remove( 'paused' ); } /** * Toggles the paused mode on and off. */ function togglePause() { if( isPaused() ) { resume(); } else { pause(); } } /** * Checks if we are currently in the paused mode. */ function isPaused() { return dom.wrapper.classList.contains( 'paused' ); } /** * Steps from the current point in the presentation to the * slide which matches the specified horizontal and vertical * indices. * * @param {int} h Horizontal index of the target slide * @param {int} v Vertical index of the target slide * @param {int} f Optional index of a fragment within the * target slide to activate */ function slide( h, v, f ) { // Remember where we were at before previousSlide = currentSlide; // Query all horizontal slides in the deck var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ); // If no vertical index is specified and the upcoming slide is a // stack, resume at its previous vertical index if( v === undefined ) { v = getPreviousVerticalIndex( horizontalSlides[ h ] ); } // If we were on a vertical stack, remember what vertical index // it was on so we can resume at the same position when returning if( previousSlide && previousSlide.parentNode && previousSlide.parentNode.classList.contains( 'stack' ) ) { setPreviousVerticalIndex( previousSlide.parentNode, indexv ); } // Remember the state before this slide var stateBefore = state.concat(); // Reset the state array state.length = 0; var indexhBefore = indexh, indexvBefore = indexv; // Activate and transition to the new slide indexh = updateSlides( HORIZONTAL_SLIDES_SELECTOR, h === undefined ? indexh : h ); indexv = updateSlides( VERTICAL_SLIDES_SELECTOR, v === undefined ? indexv : v ); layout(); // Apply the new state stateLoop: for( var i = 0, len = state.length; i < len; i++ ) { // Check if this state existed on the previous slide. If it // did, we will avoid adding it repeatedly for( var j = 0; j < stateBefore.length; j++ ) { if( stateBefore[j] === state[i] ) { stateBefore.splice( j, 1 ); continue stateLoop; } } document.documentElement.classList.add( state[i] ); // Dispatch custom event matching the state's name dispatchEvent( state[i] ); } // Clean up the remains of the previous state while( stateBefore.length ) { document.documentElement.classList.remove( stateBefore.pop() ); } // If the overview is active, re-activate it to update positions if( isOverviewActive() ) { activateOverview(); } // Update the URL hash after a delay since updating it mid-transition // is likely to cause visual lag writeURL( 1500 ); // Find the current horizontal slide and any possible vertical slides // within it var currentHorizontalSlide = horizontalSlides[ indexh ], currentVerticalSlides = currentHorizontalSlide.querySelectorAll( 'section' ); // Store references to the previous and current slides currentSlide = currentVerticalSlides[ indexv ] || currentHorizontalSlide; // Show fragment, if specified if( typeof f !== 'undefined' ) { var fragments = currentSlide.querySelectorAll( '.fragment' ); toArray( fragments ).forEach( function( fragment, indexf ) { if( indexf < f ) { fragment.classList.add( 'visible' ); } else { fragment.classList.remove( 'visible' ); } } ); } // Dispatch an event if the slide changed if( indexh !== indexhBefore || indexv !== indexvBefore ) { dispatchEvent( 'slidechanged', { 'indexh': indexh, 'indexv': indexv, 'previousSlide': previousSlide, 'currentSlide': currentSlide } ); } else { // Ensure that the previous slide is never the same as the current previousSlide = null; } // Solves an edge case where the previous slide maintains the // 'present' class when navigating between adjacent vertical // stacks if( previousSlide ) { previousSlide.classList.remove( 'present' ); // Reset all slides upon navigate to home // Issue: #285 if ( document.querySelector( HOME_SLIDE_SELECTOR ).classList.contains( 'present' ) ) { // Launch async task setTimeout( function () { var slides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.stack') ), i; for( i in slides ) { if( slides[i] ) { // Reset stack setPreviousVerticalIndex( slides[i], 0 ); } } }, 0 ); } } updateControls(); updateProgress(); } /** * Updates one dimension of slides by showing the slide * with the specified index. * * @param {String} selector A CSS selector that will fetch * the group of slides we are working with * @param {Number} index The index of the slide that should be * shown * * @return {Number} The index of the slide that is now shown, * might differ from the passed in index if it was out of * bounds. */ function updateSlides( selector, index ) { // Select all slides and convert the NodeList result to // an array var slides = toArray( document.querySelectorAll( selector ) ), slidesLength = slides.length; if( slidesLength ) { // Should the index loop? if( config.loop ) { index %= slidesLength; if( index < 0 ) { index = slidesLength + index; } } // Enforce max and minimum index bounds index = Math.max( Math.min( index, slidesLength - 1 ), 0 ); for( var i = 0; i < slidesLength; i++ ) { var element = slides[i]; // Optimization; hide all slides that are three or more steps // away from the present slide if( isOverviewActive() === false ) { // The distance loops so that it measures 1 between the first // and last slides var distance = Math.abs( ( index - i ) % ( slidesLength - 3 ) ) || 0; element.style.display = distance > 3 ? 'none' : 'block'; } slides[i].classList.remove( 'past' ); slides[i].classList.remove( 'present' ); slides[i].classList.remove( 'future' ); if( i < index ) { // Any element previous to index is given the 'past' class slides[i].classList.add( 'past' ); } else if( i > index ) { // Any element subsequent to index is given the 'future' class slides[i].classList.add( 'future' ); } // If this element contains vertical slides if( element.querySelector( 'section' ) ) { slides[i].classList.add( 'stack' ); } } // Mark the current slide as present slides[index].classList.add( 'present' ); // If this slide has a state associated with it, add it // onto the current state of the deck var slideState = slides[index].getAttribute( 'data-state' ); if( slideState ) { state = state.concat( slideState.split( ' ' ) ); } // If this slide has a data-autoslide attribtue associated use this as // autoSlide value otherwise use the global configured time var slideAutoSlide = slides[index].getAttribute( 'data-autoslide' ); if( slideAutoSlide ) { autoSlide = parseInt( slideAutoSlide, 10 ); } else { autoSlide = config.autoSlide; } } else { // Since there are no slides we can't be anywhere beyond the // zeroth index index = 0; } return index; } /** * Updates the progress bar to reflect the current slide. */ function updateProgress() { // Update progress if enabled if( config.progress && dom.progress ) { var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ); // The number of past and total slides var totalCount = document.querySelectorAll( SLIDES_SELECTOR + ':not(.stack)' ).length; var pastCount = 0; // Step through all slides and count the past ones mainLoop: for( var i = 0; i < horizontalSlides.length; i++ ) { var horizontalSlide = horizontalSlides[i]; var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) ); for( var j = 0; j < verticalSlides.length; j++ ) { // Stop as soon as we arrive at the present if( verticalSlides[j].classList.contains( 'present' ) ) { break mainLoop; } pastCount++; } // Stop as soon as we arrive at the present if( horizontalSlide.classList.contains( 'present' ) ) { break; } // Don't count the wrapping section for vertical slides if( horizontalSlide.classList.contains( 'stack' ) === false ) { pastCount++; } } dom.progressbar.style.width = ( pastCount / ( totalCount - 1 ) ) * window.innerWidth + 'px'; } } /** * Updates the state of all control/navigation arrows. */ function updateControls() { if ( config.controls && dom.controls ) { var routes = availableRoutes(); // Remove the 'enabled' class from all directions dom.controlsLeft.concat( dom.controlsRight ) .concat( dom.controlsUp ) .concat( dom.controlsDown ) .concat( dom.controlsPrev ) .concat( dom.controlsNext ).forEach( function( node ) { node.classList.remove( 'enabled' ); } ); // Add the 'enabled' class to the available routes if( routes.left ) dom.controlsLeft.forEach( function( el ) { el.classList.add( 'enabled' ); } ); if( routes.right ) dom.controlsRight.forEach( function( el ) { el.classList.add( 'enabled' ); } ); if( routes.up ) dom.controlsUp.forEach( function( el ) { el.classList.add( 'enabled' ); } ); if( routes.down ) dom.controlsDown.forEach( function( el ) { el.classList.add( 'enabled' ); } ); // Prev/next buttons if( routes.left || routes.up ) dom.controlsPrev.forEach( function( el ) { el.classList.add( 'enabled' ); } ); if( routes.right || routes.down ) dom.controlsNext.forEach( function( el ) { el.classList.add( 'enabled' ); } ); } } /** * Determine what available routes there are for navigation. * * @return {Object} containing four booleans: left/right/up/down */ function availableRoutes() { var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ), verticalSlides = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR ); return { left: indexh > 0, right: indexh < horizontalSlides.length - 1, up: indexv > 0, down: indexv < verticalSlides.length - 1 }; } /** * Reads the current URL (hash) and navigates accordingly. */ function readURL() { var hash = window.location.hash; // Attempt to parse the hash as either an index or name var bits = hash.slice( 2 ).split( '/' ), name = hash.replace( /#|\//gi, '' ); // If the first bit is invalid and there is a name we can // assume that this is a named link if( isNaN( parseInt( bits[0], 10 ) ) && name.length ) { // Find the slide with the specified name var element = document.querySelector( '#' + name ); if( element ) { // Find the position of the named slide and navigate to it var indices = Reveal.getIndices( element ); slide( indices.h, indices.v ); } // If the slide doesn't exist, navigate to the current slide else { slide( indexh, indexv ); } } else { // Read the index components of the hash var h = parseInt( bits[0], 10 ) || 0, v = parseInt( bits[1], 10 ) || 0; slide( h, v ); } } /** * Updates the page URL (hash) to reflect the current * state. * * @param {Number} delay The time in ms to wait before * writing the hash */ function writeURL( delay ) { if( config.history ) { // Make sure there's never more than one timeout running clearTimeout( writeURLTimeout ); // If a delay is specified, timeout this call if( typeof delay === 'number' ) { writeURLTimeout = setTimeout( writeURL, delay ); } else { var url = '/'; // If the current slide has an ID, use that as a named link if( currentSlide && typeof currentSlide.getAttribute( 'id' ) === 'string' ) { url = '/' + currentSlide.getAttribute( 'id' ); } // Otherwise use the /h/v index else { if( indexh > 0 || indexv > 0 ) url += indexh; if( indexv > 0 ) url += '/' + indexv; } window.location.hash = url; } } } /** * Retrieves the h/v location of the current, or specified, * slide. * * @param {HTMLElement} slide If specified, the returned * index will be for this slide rather than the currently * active one * * @return {Object} { h: , v: } */ function getIndices( slide ) { // By default, return the current indices var h = indexh, v = indexv; // If a slide is specified, return the indices of that slide if( slide ) { var isVertical = !!slide.parentNode.nodeName.match( /section/gi ); var slideh = isVertical ? slide.parentNode : slide; // Select all horizontal slides var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ); // Now that we know which the horizontal slide is, get its index h = Math.max( horizontalSlides.indexOf( slideh ), 0 ); // If this is a vertical slide, grab the vertical index if( isVertical ) { v = Math.max( toArray( slide.parentNode.querySelectorAll( 'section' ) ).indexOf( slide ), 0 ); } } return { h: h, v: v }; } /** * Navigate to the next slide fragment. * * @return {Boolean} true if there was a next fragment, * false otherwise */ function nextFragment() { // Vertical slides: if( document.querySelector( VERTICAL_SLIDES_SELECTOR + '.present' ) ) { var verticalFragments = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR + '.present .fragment:not(.visible)' ); if( verticalFragments.length ) { verticalFragments[0].classList.add( 'visible' ); // Notify subscribers of the change dispatchEvent( 'fragmentshown', { fragment: verticalFragments[0] } ); return true; } } // Horizontal slides: else { var horizontalFragments = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.present .fragment:not(.visible)' ); if( horizontalFragments.length ) { horizontalFragments[0].classList.add( 'visible' ); // Notify subscribers of the change dispatchEvent( 'fragmentshown', { fragment: horizontalFragments[0] } ); return true; } } return false; } /** * Navigate to the previous slide fragment. * * @return {Boolean} true if there was a previous fragment, * false otherwise */ function previousFragment() { // Vertical slides: if( document.querySelector( VERTICAL_SLIDES_SELECTOR + '.present' ) ) { var verticalFragments = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR + '.present .fragment.visible' ); if( verticalFragments.length ) { verticalFragments[ verticalFragments.length - 1 ].classList.remove( 'visible' ); // Notify subscribers of the change dispatchEvent( 'fragmenthidden', { fragment: verticalFragments[ verticalFragments.length - 1 ] } ); return true; } } // Horizontal slides: else { var horizontalFragments = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.present .fragment.visible' ); if( horizontalFragments.length ) { horizontalFragments[ horizontalFragments.length - 1 ].classList.remove( 'visible' ); // Notify subscribers of the change dispatchEvent( 'fragmenthidden', { fragment: horizontalFragments[ horizontalFragments.length - 1 ] } ); return true; } } return false; } /** * Cues a new automated slide if enabled in the config. */ function cueAutoSlide() { clearTimeout( autoSlideTimeout ); // Cue the next auto-slide if enabled if( autoSlide ) { autoSlideTimeout = setTimeout( navigateNext, autoSlide ); } } function navigateLeft() { // Prioritize hiding fragments if( availableRoutes().left && isOverviewActive() || previousFragment() === false ) { slide( indexh - 1 ); } } function navigateRight() { // Prioritize revealing fragments if( availableRoutes().right && isOverviewActive() || nextFragment() === false ) { slide( indexh + 1 ); } } function navigateUp() { // Prioritize hiding fragments if( availableRoutes().up && isOverviewActive() || previousFragment() === false ) { slide( indexh, indexv - 1 ); } } function navigateDown() { // Prioritize revealing fragments if( availableRoutes().down && isOverviewActive() || nextFragment() === false ) { slide( indexh, indexv + 1 ); } } /** * Navigates backwards, prioritized in the following order: * 1) Previous fragment * 2) Previous vertical slide * 3) Previous horizontal slide */ function navigatePrev() { // Prioritize revealing fragments if( previousFragment() === false ) { if( availableRoutes().up ) { navigateUp(); } else { // Fetch the previous horizontal slide, if there is one var previousSlide = document.querySelector( HORIZONTAL_SLIDES_SELECTOR + '.past:nth-child(' + indexh + ')' ); if( previousSlide ) { indexv = ( previousSlide.querySelectorAll( 'section' ).length + 1 ) || undefined; indexh --; slide(); } } } } /** * Same as #navigatePrev() but navigates forwards. */ function navigateNext() { // Prioritize revealing fragments if( nextFragment() === false ) { availableRoutes().down ? navigateDown() : navigateRight(); } // If auto-sliding is enabled we need to cue up // another timeout cueAutoSlide(); } // --------------------------------------------------------------------// // ----------------------------- EVENTS -------------------------------// // --------------------------------------------------------------------// /** * Handler for the document level 'keydown' event. * * @param {Object} event */ function onDocumentKeyDown( event ) { // Check if there's a focused element that could be using // the keyboard var activeElement = document.activeElement; var hasFocus = !!( document.activeElement && ( document.activeElement.type || document.activeElement.href || document.activeElement.contentEditable !== 'inherit' ) ); // Disregard the event if there's a focused element or a // keyboard modifier key is present if ( hasFocus || event.shiftKey || event.altKey || event.ctrlKey || event.metaKey ) return; var triggered = true; switch( event.keyCode ) { // p, page up case 80: case 33: navigatePrev(); break; // n, page down case 78: case 34: navigateNext(); break; // h, left case 72: case 37: navigateLeft(); break; // l, right case 76: case 39: navigateRight(); break; // k, up case 75: case 38: navigateUp(); break; // j, down case 74: case 40: navigateDown(); break; // home case 36: slide( 0 ); break; // end case 35: slide( Number.MAX_VALUE ); break; // space case 32: isOverviewActive() ? deactivateOverview() : navigateNext(); break; // return case 13: isOverviewActive() ? deactivateOverview() : triggered = false; break; // b, period, Logitech presenter tools "black screen" button case 66: case 190: case 191: togglePause(); break; // f case 70: enterFullscreen(); break; default: triggered = false; } // If the input resulted in a triggered action we should prevent // the browsers default behavior if( triggered ) { event.preventDefault(); } else if ( event.keyCode === 27 && supports3DTransforms ) { toggleOverview(); event.preventDefault(); } // If auto-sliding is enabled we need to cue up // another timeout cueAutoSlide(); } /** * Handler for the document level 'touchstart' event, * enables support for swipe and pinch gestures. */ function onDocumentTouchStart( event ) { touch.startX = event.touches[0].clientX; touch.startY = event.touches[0].clientY; touch.startCount = event.touches.length; // If there's two touches we need to memorize the distance // between those two points to detect pinching if( event.touches.length === 2 && config.overview ) { touch.startSpan = distanceBetween( { x: event.touches[1].clientX, y: event.touches[1].clientY }, { x: touch.startX, y: touch.startY } ); } } /** * Handler for the document level 'touchmove' event. */ function onDocumentTouchMove( event ) { // Each touch should only trigger one action if( !touch.handled ) { var currentX = event.touches[0].clientX; var currentY = event.touches[0].clientY; // If the touch started off with two points and still has // two active touches; test for the pinch gesture if( event.touches.length === 2 && touch.startCount === 2 && config.overview ) { // The current distance in pixels between the two touch points var currentSpan = distanceBetween( { x: event.touches[1].clientX, y: event.touches[1].clientY }, { x: touch.startX, y: touch.startY } ); // If the span is larger than the desire amount we've got // ourselves a pinch if( Math.abs( touch.startSpan - currentSpan ) > touch.threshold ) { touch.handled = true; if( currentSpan < touch.startSpan ) { activateOverview(); } else { deactivateOverview(); } } event.preventDefault(); } // There was only one touch point, look for a swipe else if( event.touches.length === 1 && touch.startCount !== 2 ) { var deltaX = currentX - touch.startX, deltaY = currentY - touch.startY; if( deltaX > touch.threshold && Math.abs( deltaX ) > Math.abs( deltaY ) ) { touch.handled = true; navigateLeft(); } else if( deltaX < -touch.threshold && Math.abs( deltaX ) > Math.abs( deltaY ) ) { touch.handled = true; navigateRight(); } else if( deltaY > touch.threshold ) { touch.handled = true; navigateUp(); } else if( deltaY < -touch.threshold ) { touch.handled = true; navigateDown(); } event.preventDefault(); } } // There's a bug with swiping on some Android devices unless // the default action is always prevented else if( navigator.userAgent.match( /android/gi ) ) { event.preventDefault(); } } /** * Handler for the document level 'touchend' event. */ function onDocumentTouchEnd( event ) { touch.handled = false; } /** * Handles mouse wheel scrolling, throttled to avoid skipping * multiple slides. */ function onDocumentMouseScroll( event ) { clearTimeout( mouseWheelTimeout ); mouseWheelTimeout = setTimeout( function() { var delta = event.detail || -event.wheelDelta; if( delta > 0 ) { navigateNext(); } else { navigatePrev(); } }, 100 ); } /** * Clicking on the progress bar results in a navigation to the * closest approximate horizontal slide using this equation: * * ( clickX / presentationWidth ) * numberOfSlides */ function onProgressClick( event ) { var slidesTotal = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).length; var slideIndex = Math.floor( ( event.clientX / dom.wrapper.offsetWidth ) * slidesTotal ); slide( slideIndex ); } /** * Handler for the window level 'hashchange' event. */ function onWindowHashChange( event ) { readURL(); } /** * Handler for the window level 'resize' event. */ function onWindowResize( event ) { layout(); } /** * Invoked when a slide is and we're in the overview. */ function onOverviewSlideClicked( event ) { // TODO There's a bug here where the event listeners are not // removed after deactivating the overview. if( isOverviewActive() ) { event.preventDefault(); deactivateOverview(); var element = event.target; while( element && !element.nodeName.match( /section/gi ) ) { element = element.parentNode; } if( element.nodeName.match( /section/gi ) ) { var h = parseInt( element.getAttribute( 'data-index-h' ), 10 ), v = parseInt( element.getAttribute( 'data-index-v' ), 10 ); slide( h, v ); } } } // --------------------------------------------------------------------// // ------------------------------- API --------------------------------// // --------------------------------------------------------------------// return { initialize: initialize, // Navigation methods slide: slide, left: navigateLeft, right: navigateRight, up: navigateUp, down: navigateDown, prev: navigatePrev, next: navigateNext, prevFragment: previousFragment, nextFragment: nextFragment, // Deprecated aliases navigateTo: slide, navigateLeft: navigateLeft, navigateRight: navigateRight, navigateUp: navigateUp, navigateDown: navigateDown, navigatePrev: navigatePrev, navigateNext: navigateNext, // Forces an update in slide layout layout: layout, // Toggles the overview mode on/off toggleOverview: toggleOverview, // Toggles the "black screen" mode on/off togglePause: togglePause, // Adds or removes all internal event listeners (such as keyboard) addEventListeners: addEventListeners, removeEventListeners: removeEventListeners, // Returns the indices of the current, or specified, slide getIndices: getIndices, // Returns the previous slide element, may be null getPreviousSlide: function() { return previousSlide; }, // Returns the current slide element getCurrentSlide: function() { return currentSlide; }, // Helper method, retrieves query string as a key/value hash getQueryHash: function() { var query = {}; location.search.replace( /[A-Z0-9]+?=(\w*)/gi, function(a) { query[ a.split( '=' ).shift() ] = a.split( '=' ).pop(); } ); return query; }, // Forward event binding to the reveal DOM element addEventListener: function( type, listener, useCapture ) { if( 'addEventListener' in window ) { ( dom.wrapper || document.querySelector( '.reveal' ) ).addEventListener( type, listener, useCapture ); } }, removeEventListener: function( type, listener, useCapture ) { if( 'addEventListener' in window ) { ( dom.wrapper || document.querySelector( '.reveal' ) ).removeEventListener( type, listener, useCapture ); } } }; })(); (function() { window.HelpModal = (function() { HelpModal.prototype.shortcuts = [ { key: 'F', desc: 'Enter full screen mode' }, { key: 'S', desc: 'Open the notes window' }, { key: 'Esc', desc: 'Zoom to the overview screen' }, { key: 'Return', desc: 'Select slide from overview screen' }, { key: 'Space', desc: 'Next slide or select slide from overview screen' }, { key: '.', desc: 'Pause the presentation', key_alt: 'b' }, { key: '→', desc: 'Navigate right', key_alt: 'l' }, { key: '←', desc: 'Navigate left', key_alt: 'h' }, { key: '↑', desc: 'Navigate up', key_alt: 'k' }, { key: '↓', desc: 'Navigate down', key_alt: 'j' }, { key: 'p', desc: 'Previous slide', key_alt: 'Page Up' }, { key: 'n', desc: 'Next slide', key_alt: 'Page Down' }, { key: 'home', desc: 'Jump to first slide' }, { key: 'end', desc: 'Jump to last slide' }, { key: '?', desc: 'Toggles the help modal' } ]; function HelpModal(reveal) { var _this = this; this.reveal = reveal; this.help = null; this.is_active = false; document.addEventListener('keydown', function() { return _this.keyed.apply(_this, arguments); }); } HelpModal.prototype.keyed = function(e) { if (e.shiftKey && e.keyCode === 191) { if (this.is_active) { return this.remove(); } else { return this.add(); } } }; HelpModal.prototype.add = function() { this.is_active = true; if (!this.help) { this.render(); } return document.body.appendChild(this.help); }; HelpModal.prototype.remove = function() { this.is_active = false; return document.body.removeChild(this.help); }; HelpModal.prototype.render = function() { var div; div = document.createElement('div'); div.className = 'help-modal'; div.innerHTML = this.markup(); return this.help = document.body.appendChild(div); }; HelpModal.prototype.markup = function() { var html, shortcut; html = "

Keyboard Shortcuts

\n" + ((function() { var _i, _len, _ref, _results; _ref = this.shortcuts; _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { shortcut = _ref[_i]; _results.push(this.row(shortcut)); } return _results; }).call(this)); return html.replace(/\,/g, ''); }; HelpModal.prototype.row = function(shortcut) { return "

\n " + shortcut.desc + "\n " + shortcut.key + "\n " + (shortcut.key_alt ? ' or ' + shortcut.key_alt + '' : '') + "\n

"; }; return HelpModal; })(); }).call(this); (function() { window.PageTitle = (function() { function PageTitle(reveal, prefix) { var _ref, _this = this; this.reveal = reveal; this.prefix = prefix; this.title = document.querySelector('title'); if ((_ref = this.prefix) == null) { this.prefix = this.title.innerHTML; } this.total = document.querySelectorAll(".reveal .slides > section:not(.stack)").length; this.reveal.addEventListener('slidechanged', function() { return _this.render.apply(_this, arguments); }); this.render({ indexh: this.reveal.getIndices().h }); } PageTitle.prototype.render = function(e) { return this.title.innerHTML = "" + this.prefix + ": " + e.indexh + " of " + this.total; }; return PageTitle; })(); }).call(this); (function() { var __slice = [].slice; window.ResponsiveFrame = (function() { function ResponsiveFrame(reveal) { var _this = this; this.reveal = reveal; this.frames = document.querySelectorAll('.media-template > iframe'); if (!this.frames.length) { return; } _.defer(function() { return _this.initialize(); }); } ResponsiveFrame.prototype.initialize = function() { this.sample_media = document.querySelector('.media-template > img') || this.frames[0]; this.sample_parent = this.sample_media.parentNode; this.setRatios(); this.addListeners(); return this.resize(); }; ResponsiveFrame.prototype.resize = function(e) { var frame, hv, percent, ratio, wv, _i, _len, _ref, _results; percent = this.sample_media.offsetWidth / this.sample_parent.offsetWidth; if (_.isNaN(percent)) { return; } _ref = this.frames; _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { frame = _ref[_i]; ratio = frame.getAttribute('data-ratio'); wv = Math.round(frame.parentNode.offsetWidth * percent); hv = Math.round(wv * ratio); frame.setAttribute('width', wv); _results.push(frame.setAttribute('height', hv)); } return _results; }; ResponsiveFrame.prototype.addListeners = function() { var _this = this; window.addEventListener('resize', function() { return _this.resize.apply(_this, __slice.call(arguments).concat([false])); }); return this.reveal.addEventListener('slidechanged', function() { return _this.resize.apply(_this, arguments); }); }; ResponsiveFrame.prototype.setRatios = function() { var frame, hv, ratio, wv, _i, _len, _ref, _results; _ref = this.frames; _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { frame = _ref[_i]; wv = frame.getAttribute('width') || frame.offsetWidth; hv = frame.getAttribute('height') || frame.offsetHeight; ratio = hv / wv; _results.push(frame.setAttribute('data-ratio', ratio)); } return _results; }; return ResponsiveFrame; })(); }).call(this); (function() { window.SlideHeading = (function() { function SlideHeading(reveal) { var _this = this; this.reveal = reveal; this.header = document.querySelector('#main_heading'); this.heading = this.header.querySelector('h1'); this.reveal.addEventListener('slidechanged', function() { return _this.render.apply(_this, arguments); }); this.render({ currentSlide: _.last(document.querySelectorAll('.present')) }); } SlideHeading.prototype.render = function(e) { var markup, method, slide; slide = e.currentSlide; markup = this.title(slide); markup += this.link(slide); method = _.isEmpty(markup) ? 'remove' : 'add'; this.heading.innerHTML = markup; return this.header.classList[method]('in'); }; SlideHeading.prototype.title = function(slide) { return slide.getAttribute('data-heading') || ''; }; SlideHeading.prototype.link = function(slide) { var link, src; link = slide.getAttribute('data-link'); if (!link) { return ''; } src = slide.getAttribute('data-src') || "#"; return "" + link + ""; }; return SlideHeading; })(); }).call(this); (function() { }).call(this); ;FI"required_assets_digest;F"%8b98b6458a69ff7a7bd34ce6843737cdI" _version;F"%6776f581a4329e299531e1d52aa59832