From e002d09d2b6b2317fec6caa8836b20e6709c5da3 Mon Sep 17 00:00:00 2001 From: Jinwei Zhao Date: Mon, 4 Apr 2016 14:00:39 +0800 Subject: jinwei.me --- deprecated/ZJW/javascripts/webflow.js | 4445 +++++++++++++++++++++++++++++++++ 1 file changed, 4445 insertions(+) create mode 100644 deprecated/ZJW/javascripts/webflow.js (limited to 'deprecated/ZJW/javascripts/webflow.js') diff --git a/deprecated/ZJW/javascripts/webflow.js b/deprecated/ZJW/javascripts/webflow.js new file mode 100644 index 0000000..b048709 --- /dev/null +++ b/deprecated/ZJW/javascripts/webflow.js @@ -0,0 +1,4445 @@ +/*! + * ---------------------------------------------------------------------- + * Webflow: Front-end site library + * @license MIT + * Other scripts may access this api using an async handler: + * var Webflow = Webflow || []; + * Webflow.push(readyFunction); + * ---------------------------------------------------------------------- + */ +var Webflow = { + w: Webflow +}; +Webflow.init = function() { + 'use strict'; + + var $ = window.$; + var api = {}; + var modules = {}; + var primary = []; + var secondary = this.w || []; + var $win = $(window); + var _ = api._ = underscore(); + var domready = false; + var tram = window.tram; + var Modernizr = window.Modernizr; + var noop = function() {}; + tram.config.hideBackface = false; + tram.config.keepInherited = true; + + /** + * Webflow.define() - Define a webflow.js module + * @param {string} name + * @param {function} factory + */ + api.define = function(name, factory) { + var module = modules[name] = factory($, _); + if (!module) return; + // If running in Webflow app, subscribe to design/preview events + if (api.env()) { + $.isFunction(module.design) && window.addEventListener('__wf_design', module.design); + $.isFunction(module.preview) && window.addEventListener('__wf_preview', module.preview); + } + // Subscribe to module front-end events + $.isFunction(module.destroy) && $win.on('__wf_destroy', module.destroy); + // Look for a ready method on module + if (module.ready && $.isFunction(module.ready)) { + // If domready has already happened, call ready method + if (domready) module.ready(); + // Otherwise push ready method into primary queue + else primary.push(module.ready); + } + }; + + /** + * Webflow.require() - Load a Webflow.js module + * @param {string} name + * @return {object} + */ + api.require = function(name) { + return modules[name]; + }; + + /** + * Webflow.push() - Add a ready handler into secondary queue + * @param {function} ready Callback to invoke on domready + */ + api.push = function(ready) { + // If domready has already happened, invoke handler + if (domready) { + $.isFunction(ready) && ready(); + return; + } + // Otherwise push into secondary queue + secondary.push(ready); + }; + + /** + * Webflow.env() - Get the state of the Webflow app + * @param {string} mode [optional] + * @return {boolean} + */ + api.env = function(mode) { + var designFlag = window.__wf_design; + var inApp = typeof designFlag != 'undefined'; + if (!mode) return inApp; + if (mode == 'design') return inApp && designFlag; + if (mode == 'preview') return inApp && !designFlag; + if (mode == 'slug') return inApp && window.__wf_slug; + }; + + // Feature detects + browser sniffs ಠ_ಠ + var userAgent = navigator.userAgent.toLowerCase(); + var appVersion = navigator.appVersion.toLowerCase(); + api.env.touch = ('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch; + var chrome = api.env.chrome = (window.chrome || /chrome/.test(userAgent)) && parseInt(appVersion.match(/chrome\/(\d+)\./)[1], 10); + var ios = api.env.ios = Modernizr && Modernizr.ios; + api.env.safari = /safari/.test(userAgent) && !chrome && !ios; + + /** + * Webflow.resize, Webflow.scroll - throttled event proxies + */ + var resizeEvents = 'resize.webflow orientationchange.webflow load.webflow'; + var scrollEvents = 'scroll.webflow ' + resizeEvents; + api.resize = eventProxy($win, resizeEvents); + api.scroll = eventProxy($win, scrollEvents); + api.redraw = eventProxy(); + + // Create a proxy instance for throttled events + function eventProxy(target, types) { + + // Set up throttled method (using custom frame-based _.throttle) + var handlers = []; + var proxy = {}; + proxy.up = _.throttle(function(evt) { + _.each(handlers, function(h) { + h(evt); + }); + }); + + // Bind events to target + if (target && types) target.on(types, proxy.up); + + /** + * Add an event handler + * @param {function} handler + */ + proxy.on = function(handler) { + if (typeof handler != 'function') return; + if (_.contains(handlers, handler)) return; + handlers.push(handler); + }; + + /** + * Remove an event handler + * @param {function} handler + */ + proxy.off = function(handler) { + handlers = _.filter(handlers, function(h) { + return h !== handler; + }); + }; + return proxy; + } + + // Provide optional IX events to components + api.ixEvents = function() { + var ix = api.require('ix'); + return (ix && ix.events) || { + reset: noop, + intro: noop, + outro: noop + }; + }; + + // Webflow.location() - Wrap window.location in api + api.location = function(url) { + window.location = url; + }; + + // Webflow.app - Designer-specific methods + api.app = api.env() ? {} : null; + if (api.app) { + + // Trigger redraw for specific elements + var Event = window.Event; + var redraw = new Event('__wf_redraw'); + api.app.redrawElement = function(i, el) { + el.dispatchEvent(redraw); + }; + + // Webflow.location - Re-route location change to trigger an event + api.location = function(url) { + window.dispatchEvent(new CustomEvent('__wf_location', { + detail: url + })); + }; + } + + // Webflow.ready() - Call primary and secondary handlers + api.ready = function() { + domready = true; + $.each(primary.concat(secondary), function(index, value) { + $.isFunction(value) && value(); + }); + // Trigger resize + api.resize.up(); + }; + + // Webflow.destroy() - Trigger a cleanup event for all modules + api.destroy = function() { + $win.triggerHandler('__wf_destroy'); + }; + + // Listen for domready + $(api.ready); + + /*! + * Webflow._ (aka) Underscore.js 1.6.0 (custom build) + * _.each + * _.map + * _.find + * _.filter + * _.any + * _.contains + * _.delay + * _.defer + * _.throttle (webflow) + * _.debounce + * _.keys + * _.has + * _.now + * + * http://underscorejs.org + * (c) 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Underscore may be freely distributed under the MIT license. + */ + function underscore() { + var _ = {}; + + // Current version. + _.VERSION = '1.6.0-Webflow'; + + // 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; + + // 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) { + /* jshint shadow:true */ + if (obj == null) return obj; + if (nativeForEach && obj.forEach === nativeForEach) { + obj.forEach(iterator, context); + } else if (obj.length === +obj.length) { + for (var i = 0, length = obj.length; i < length; i++) { + if (iterator.call(context, obj[i], i, obj) === breaker) return; + } + } else { + var keys = _.keys(obj); + for (var i = 0, length = keys.length; i < length; i++) { + if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) return; + } + } + return obj; + }; + + // 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.push(iterator.call(context, value, index, list)); + }); + return results; + }; + + // Return the first value which passes a truth test. Aliased as `detect`. + _.find = _.detect = function(obj, predicate, context) { + var result; + any(obj, function(value, index, list) { + if (predicate.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, predicate, context) { + var results = []; + if (obj == null) return results; + if (nativeFilter && obj.filter === nativeFilter) return obj.filter(predicate, context); + each(obj, function(value, index, list) { + if (predicate.call(context, value, index, list)) results.push(value); + }); + return results; + }; + + // 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, predicate, context) { + predicate || (predicate = _.identity); + var result = false; + if (obj == null) return result; + if (nativeSome && obj.some === nativeSome) return obj.some(predicate, context); + each(obj, function(value, index, list) { + if (result || (result = predicate.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; + }); + }; + + // Function (ahem) Functions + // -------------------- + + // 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 once every + // browser animation frame - using tram's requestAnimationFrame polyfill. + _.throttle = function(func) { + var wait, args, context; + return function() { + if (wait) return; + wait = true; + args = arguments; + context = this; + tram.frame(function() { + wait = false; + func.apply(context, args); + }); + }; + }; + + // 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, args, context, timestamp, result; + + var later = function() { + var last = _.now() - timestamp; + if (last < wait) { + timeout = setTimeout(later, wait - last); + } else { + timeout = null; + if (!immediate) { + result = func.apply(context, args); + context = args = null; + } + } + }; + + return function() { + context = this; + args = arguments; + timestamp = _.now(); + var callNow = immediate && !timeout; + if (!timeout) { + timeout = setTimeout(later, wait); + } + if (callNow) { + result = func.apply(context, args); + context = args = null; + } + + return result; + }; + }; + + // Object Functions + // ---------------- + + // Retrieve the names of an object's properties. + // Delegates to **ECMAScript 5**'s native `Object.keys` + _.keys = function(obj) { + if (!_.isObject(obj)) return []; + if (nativeKeys) return nativeKeys(obj); + var keys = []; + for (var key in obj) + if (_.has(obj, key)) keys.push(key); + return keys; + }; + + // 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); + }; + + // Is a given variable an object? + _.isObject = function(obj) { + return obj === Object(obj); + }; + + // Utility Functions + // ----------------- + + // A (possibly faster) way to get the current timestamp as an integer. + _.now = Date.now || function() { + return new Date().getTime(); + }; + + // Export underscore + return _; + } + + // Export api + Webflow = api; +}; +/*! + * ---------------------------------------------------------------------- + * Webflow: 3rd party plugins + */ +/* jshint ignore:start */ +/*! + * tram.js v0.8.1-global + * Cross-browser CSS3 transitions in JavaScript + * https://github.com/bkwld/tram + * MIT License + */ +window.tram = function(a) { + function b(a, b) { + var c = new L.Bare; + return c.init(a, b) + } + + function c(a) { + return a.replace(/[A-Z]/g, function(a) { + return "-" + a.toLowerCase() + }) + } + + function d(a) { + var b = parseInt(a.slice(1), 16), + c = b >> 16 & 255, + d = b >> 8 & 255, + e = 255 & b; + return [c, d, e] + } + + function e(a, b, c) { + return "#" + (1 << 24 | a << 16 | b << 8 | c).toString(16).slice(1) + } + + function f() {} + + function g(a, b) { + _("Type warning: Expected: [" + a + "] Got: [" + typeof b + "] " + b) + } + + function h(a, b, c) { + _("Units do not match [" + a + "]: " + b + ", " + c) + } + + function i(a, b, c) { + if (void 0 !== b && (c = b), void 0 === a) return c; + var d = c; + return Z.test(a) || !$.test(a) ? d = parseInt(a, 10) : $.test(a) && (d = 1e3 * parseFloat(a)), 0 > d && (d = 0), d === d ? d : c + } + + function j(a) { + for (var b = -1, c = a ? a.length : 0, d = []; ++b < c;) { + var e = a[b]; + e && d.push(e) + } + return d + } + var k = function(a, b, c) { + function d(a) { + return "object" == typeof a + } + + function e(a) { + return "function" == typeof a + } + + function f() {} + + function g(h, i) { + function j() { + var a = new k; + return e(a.init) && a.init.apply(a, arguments), a + } + + function k() {} + i === c && (i = h, h = Object), j.Bare = k; + var l, m = f[a] = h[a], + n = k[a] = j[a] = new f; + return n.constructor = j, j.mixin = function(b) { + return k[a] = j[a] = g(j, b)[a], j + }, j.open = function(a) { + if (l = {}, e(a) ? l = a.call(j, n, m, j, h) : d(a) && (l = a), d(l)) + for (var c in l) b.call(l, c) && (n[c] = l[c]); + return e(n.init) || (n.init = h), j + }, j.open(i) + } + return g + }("prototype", {}.hasOwnProperty), + l = { + ease: ["ease", + function(a, b, c, d) { + var e = (a /= d) * a, + f = e * a; + return b + c * (-2.75 * f * e + 11 * e * e + -15.5 * f + 8 * e + .25 * a) + } + ], + "ease-in": ["ease-in", + function(a, b, c, d) { + var e = (a /= d) * a, + f = e * a; + return b + c * (-1 * f * e + 3 * e * e + -3 * f + 2 * e) + } + ], + "ease-out": ["ease-out", + function(a, b, c, d) { + var e = (a /= d) * a, + f = e * a; + return b + c * (.3 * f * e + -1.6 * e * e + 2.2 * f + -1.8 * e + 1.9 * a) + } + ], + "ease-in-out": ["ease-in-out", + function(a, b, c, d) { + var e = (a /= d) * a, + f = e * a; + return b + c * (2 * f * e + -5 * e * e + 2 * f + 2 * e) + } + ], + linear: ["linear", + function(a, b, c, d) { + return c * a / d + b + } + ], + "ease-in-quad": ["cubic-bezier(0.550, 0.085, 0.680, 0.530)", + function(a, b, c, d) { + return c * (a /= d) * a + b + } + ], + "ease-out-quad": ["cubic-bezier(0.250, 0.460, 0.450, 0.940)", + function(a, b, c, d) { + return -c * (a /= d) * (a - 2) + b + } + ], + "ease-in-out-quad": ["cubic-bezier(0.455, 0.030, 0.515, 0.955)", + function(a, b, c, d) { + return (a /= d / 2) < 1 ? c / 2 * a * a + b : -c / 2 * (--a * (a - 2) - 1) + b + } + ], + "ease-in-cubic": ["cubic-bezier(0.550, 0.055, 0.675, 0.190)", + function(a, b, c, d) { + return c * (a /= d) * a * a + b + } + ], + "ease-out-cubic": ["cubic-bezier(0.215, 0.610, 0.355, 1)", + function(a, b, c, d) { + return c * ((a = a / d - 1) * a * a + 1) + b + } + ], + "ease-in-out-cubic": ["cubic-bezier(0.645, 0.045, 0.355, 1)", + function(a, b, c, d) { + return (a /= d / 2) < 1 ? c / 2 * a * a * a + b : c / 2 * ((a -= 2) * a * a + 2) + b + } + ], + "ease-in-quart": ["cubic-bezier(0.895, 0.030, 0.685, 0.220)", + function(a, b, c, d) { + return c * (a /= d) * a * a * a + b + } + ], + "ease-out-quart": ["cubic-bezier(0.165, 0.840, 0.440, 1)", + function(a, b, c, d) { + return -c * ((a = a / d - 1) * a * a * a - 1) + b + } + ], + "ease-in-out-quart": ["cubic-bezier(0.770, 0, 0.175, 1)", + function(a, b, c, d) { + return (a /= d / 2) < 1 ? c / 2 * a * a * a * a + b : -c / 2 * ((a -= 2) * a * a * a - 2) + b + } + ], + "ease-in-quint": ["cubic-bezier(0.755, 0.050, 0.855, 0.060)", + function(a, b, c, d) { + return c * (a /= d) * a * a * a * a + b + } + ], + "ease-out-quint": ["cubic-bezier(0.230, 1, 0.320, 1)", + function(a, b, c, d) { + return c * ((a = a / d - 1) * a * a * a * a + 1) + b + } + ], + "ease-in-out-quint": ["cubic-bezier(0.860, 0, 0.070, 1)", + function(a, b, c, d) { + return (a /= d / 2) < 1 ? c / 2 * a * a * a * a * a + b : c / 2 * ((a -= 2) * a * a * a * a + 2) + b + } + ], + "ease-in-sine": ["cubic-bezier(0.470, 0, 0.745, 0.715)", + function(a, b, c, d) { + return -c * Math.cos(a / d * (Math.PI / 2)) + c + b + } + ], + "ease-out-sine": ["cubic-bezier(0.390, 0.575, 0.565, 1)", + function(a, b, c, d) { + return c * Math.sin(a / d * (Math.PI / 2)) + b + } + ], + "ease-in-out-sine": ["cubic-bezier(0.445, 0.050, 0.550, 0.950)", + function(a, b, c, d) { + return -c / 2 * (Math.cos(Math.PI * a / d) - 1) + b + } + ], + "ease-in-expo": ["cubic-bezier(0.950, 0.050, 0.795, 0.035)", + function(a, b, c, d) { + return 0 === a ? b : c * Math.pow(2, 10 * (a / d - 1)) + b + } + ], + "ease-out-expo": ["cubic-bezier(0.190, 1, 0.220, 1)", + function(a, b, c, d) { + return a === d ? b + c : c * (-Math.pow(2, -10 * a / d) + 1) + b + } + ], + "ease-in-out-expo": ["cubic-bezier(1, 0, 0, 1)", + function(a, b, c, d) { + return 0 === a ? b : a === d ? b + c : (a /= d / 2) < 1 ? c / 2 * Math.pow(2, 10 * (a - 1)) + b : c / 2 * (-Math.pow(2, -10 * --a) + 2) + b + } + ], + "ease-in-circ": ["cubic-bezier(0.600, 0.040, 0.980, 0.335)", + function(a, b, c, d) { + return -c * (Math.sqrt(1 - (a /= d) * a) - 1) + b + } + ], + "ease-out-circ": ["cubic-bezier(0.075, 0.820, 0.165, 1)", + function(a, b, c, d) { + return c * Math.sqrt(1 - (a = a / d - 1) * a) + b + } + ], + "ease-in-out-circ": ["cubic-bezier(0.785, 0.135, 0.150, 0.860)", + function(a, b, c, d) { + return (a /= d / 2) < 1 ? -c / 2 * (Math.sqrt(1 - a * a) - 1) + b : c / 2 * (Math.sqrt(1 - (a -= 2) * a) + 1) + b + } + ], + "ease-in-back": ["cubic-bezier(0.600, -0.280, 0.735, 0.045)", + function(a, b, c, d, e) { + return void 0 === e && (e = 1.70158), c * (a /= d) * a * ((e + 1) * a - e) + b + } + ], + "ease-out-back": ["cubic-bezier(0.175, 0.885, 0.320, 1.275)", + function(a, b, c, d, e) { + return void 0 === e && (e = 1.70158), c * ((a = a / d - 1) * a * ((e + 1) * a + e) + 1) + b + } + ], + "ease-in-out-back": ["cubic-bezier(0.680, -0.550, 0.265, 1.550)", + function(a, b, c, d, e) { + return void 0 === e && (e = 1.70158), (a /= d / 2) < 1 ? c / 2 * a * a * (((e *= 1.525) + 1) * a - e) + b : c / 2 * ((a -= 2) * a * (((e *= 1.525) + 1) * a + e) + 2) + b + } + ] + }, + m = { + "ease-in-back": "cubic-bezier(0.600, 0, 0.735, 0.045)", + "ease-out-back": "cubic-bezier(0.175, 0.885, 0.320, 1)", + "ease-in-out-back": "cubic-bezier(0.680, 0, 0.265, 1)" + }, + n = document, + o = window, + p = "bkwld-tram", + q = /[\-\.0-9]/g, + r = /[A-Z]/, + s = "number", + t = /^(rgb|#)/, + u = /(em|cm|mm|in|pt|pc|px)$/, + v = /(em|cm|mm|in|pt|pc|px|%)$/, + w = /(deg|rad|turn)$/, + x = "unitless", + y = /(all|none) 0s ease 0s/, + z = /^(width|height)$/, + A = " ", + B = n.createElement("a"), + C = ["Webkit", "Moz", "O", "ms"], + D = ["-webkit-", "-moz-", "-o-", "-ms-"], + E = function(a) { + if (a in B.style) return { + dom: a, + css: a + }; + var b, c, d = "", + e = a.split("-"); + for (b = 0; b < e.length; b++) d += e[b].charAt(0).toUpperCase() + e[b].slice(1); + for (b = 0; b < C.length; b++) + if (c = C[b] + d, c in B.style) return { + dom: c, + css: D[b] + a + } + }, + F = b.support = { + bind: Function.prototype.bind, + transform: E("transform"), + transition: E("transition"), + backface: E("backface-visibility"), + timing: E("transition-timing-function") + }; + if (F.transition) { + var G = F.timing.dom; + if (B.style[G] = l["ease-in-back"][0], !B.style[G]) + for (var H in m) l[H][0] = m[H] + } + var I = b.frame = function() { + var a = o.requestAnimationFrame || o.webkitRequestAnimationFrame || o.mozRequestAnimationFrame || o.oRequestAnimationFrame || o.msRequestAnimationFrame; + return a && F.bind ? a.bind(o) : function(a) { + o.setTimeout(a, 16) + } + }(), + J = b.now = function() { + var a = o.performance, + b = a && (a.now || a.webkitNow || a.msNow || a.mozNow); + return b && F.bind ? b.bind(a) : Date.now || function() { + return +new Date + } + }(), + K = k(function(b) { + function d(a, b) { + var c = j(("" + a).split(A)), + d = c[0]; + b = b || {}; + var e = X[d]; + if (!e) return _("Unsupported property: " + d); + if (!b.weak || !this.props[d]) { + var f = e[0], + g = this.props[d]; + return g || (g = this.props[d] = new f.Bare), g.init(this.$el, c, e, b), g + } + } + + function e(a, b, c) { + if (a) { + var e = typeof a; + if (b || (this.timer && this.timer.destroy(), this.queue = [], this.active = !1), "number" == e && b) return this.timer = new R({ + duration: a, + context: this, + complete: h + }), void(this.active = !0); + if ("string" == e && b) { + switch (a) { + case "hide": + n.call(this); + break; + case "stop": + k.call(this); + break; + case "redraw": + o.call(this); + break; + default: + d.call(this, a, c && c[1]) + } + return h.call(this) + } + if ("function" == e) return void a.call(this, this); + if ("object" == e) { + var f = 0; + t.call(this, a, function(a, b) { + a.span > f && (f = a.span), a.stop(), a.animate(b) + }, function(a) { + "wait" in a && (f = i(a.wait, 0)) + }), s.call(this), f > 0 && (this.timer = new R({ + duration: f, + context: this + }), this.active = !0, b && (this.timer.complete = h)); + var g = this, + j = !1, + l = {}; + I(function() { + t.call(g, a, function(a) { + a.active && (j = !0, l[a.name] = a.nextStyle) + }), j && g.$el.css(l) + }) + } + } + } + + function f(a) { + a = i(a, 0), this.active ? this.queue.push({ + options: a + }) : (this.timer = new R({ + duration: a, + context: this, + complete: h + }), this.active = !0) + } + + function g(a) { + return this.active ? (this.queue.push({ + options: a, + args: arguments + }), void(this.timer.complete = h)) : _("No active transition timer. Use start() or wait() before then().") + } + + function h() { + if (this.timer && this.timer.destroy(), this.active = !1, this.queue.length) { + var a = this.queue.shift(); + e.call(this, a.options, !0, a.args) + } + } + + function k(a) { + this.timer && this.timer.destroy(), this.queue = [], this.active = !1; + var b; + "string" == typeof a ? (b = {}, b[a] = 1) : b = "object" == typeof a && null != a ? a : this.props, t.call(this, b, u), s.call(this) + } + + function l(a) { + k.call(this, a), t.call(this, a, v, w) + } + + function m(a) { + "string" != typeof a && (a = "block"), this.el.style.display = a + } + + function n() { + k.call(this), this.el.style.display = "none" + } + + function o() { + this.el.offsetHeight + } + + function q() { + k.call(this), a.removeData(this.el, p), this.$el = this.el = null + } + + function s() { + var a, b, c = []; + this.upstream && c.push(this.upstream); + for (a in this.props) b = this.props[a], b.active && c.push(b.string); + c = c.join(","), this.style !== c && (this.style = c, this.el.style[F.transition.dom] = c) + } + + function t(a, b, e) { + var f, g, h, i, j = b !== u, + k = {}; + for (f in a) h = a[f], f in Y ? (k.transform || (k.transform = {}), k.transform[f] = h) : (r.test(f) && (f = c(f)), f in X ? k[f] = h : (i || (i = {}), i[f] = h)); + for (f in k) { + if (h = k[f], g = this.props[f], !g) { + if (!j) continue; + g = d.call(this, f) + } + b.call(this, g, h) + } + e && i && e.call(this, i) + } + + function u(a) { + a.stop() + } + + function v(a, b) { + a.set(b) + } + + function w(a) { + this.$el.css(a) + } + + function x(a, c) { + b[a] = function() { + return this.children ? z.call(this, c, arguments) : (this.el && c.apply(this, arguments), this) + } + } + + function z(a, b) { + var c, d = this.children.length; + for (c = 0; d > c; c++) a.apply(this.children[c], b); + return this + } + b.init = function(b) { + if (this.$el = a(b), this.el = this.$el[0], this.props = {}, this.queue = [], this.style = "", this.active = !1, T.keepInherited && !T.fallback) { + var c = V(this.el, "transition"); + c && !y.test(c) && (this.upstream = c) + } + F.backface && T.hideBackface && U(this.el, F.backface.css, "hidden") + }, x("add", d), x("start", e), x("wait", f), x("then", g), x("next", h), x("stop", k), x("set", l), x("show", m), x("hide", n), x("redraw", o), x("destroy", q) + }), + L = k(K, function(b) { + function c(b, c) { + var d = a.data(b, p) || a.data(b, p, new K.Bare); + return d.el || d.init(b), c ? d.start(c) : d + } + b.init = function(b, d) { + var e = a(b); + if (!e.length) return this; + if (1 === e.length) return c(e[0], d); + var f = []; + return e.each(function(a, b) { + f.push(c(b, d)) + }), this.children = f, this + } + }), + M = k(function(a) { + function b() { + var a = this.get(); + this.update("auto"); + var b = this.get(); + return this.update(a), b + } + + function c(a, b, c) { + return void 0 !== b && (c = b), a in l ? a : c + } + + function d(a) { + var b = /rgba?\((\d+),\s*(\d+),\s*(\d+)/.exec(a); + return (b ? e(b[1], b[2], b[3]) : a).replace(/#(\w)(\w)(\w)$/, "#$1$1$2$2$3$3") + } + var f = { + duration: 500, + ease: "ease", + delay: 0 + }; + a.init = function(a, b, d, e) { + this.$el = a, this.el = a[0]; + var g = b[0]; + d[2] && (g = d[2]), W[g] && (g = W[g]), this.name = g, this.type = d[1], this.duration = i(b[1], this.duration, f.duration), this.ease = c(b[2], this.ease, f.ease), this.delay = i(b[3], this.delay, f.delay), this.span = this.duration + this.delay, this.active = !1, this.nextStyle = null, this.auto = z.test(this.name), this.unit = e.unit || this.unit || T.defaultUnit, this.angle = e.angle || this.angle || T.defaultAngle, T.fallback || e.fallback ? this.animate = this.fallback : (this.animate = this.transition, this.string = this.name + A + this.duration + "ms" + ("ease" != this.ease ? A + l[this.ease][0] : "") + (this.delay ? A + this.delay + "ms" : "")) + }, a.set = function(a) { + a = this.convert(a, this.type), this.update(a), this.redraw() + }, a.transition = function(a) { + this.active = !0, a = this.convert(a, this.type), this.auto && ("auto" == this.el.style[this.name] && (this.update(this.get()), this.redraw()), "auto" == a && (a = b.call(this))), this.nextStyle = a + }, a.fallback = function(a) { + var c = this.el.style[this.name] || this.convert(this.get(), this.type); + a = this.convert(a, this.type), this.auto && ("auto" == c && (c = this.convert(this.get(), this.type)), "auto" == a && (a = b.call(this))), this.tween = new Q({ + from: c, + to: a, + duration: this.duration, + delay: this.delay, + ease: this.ease, + update: this.update, + context: this + }) + }, a.get = function() { + return V(this.el, this.name) + }, a.update = function(a) { + U(this.el, this.name, a) + }, a.stop = function() { + (this.active || this.nextStyle) && (this.active = !1, this.nextStyle = null, U(this.el, this.name, this.get())); + var a = this.tween; + a && a.context && a.destroy() + }, a.convert = function(a, b) { + if ("auto" == a && this.auto) return a; + var c, e = "number" == typeof a, + f = "string" == typeof a; + switch (b) { + case s: + if (e) return a; + if (f && "" === a.replace(q, "")) return +a; + c = "number(unitless)"; + break; + case t: + if (f) { + if ("" === a && this.original) return this.original; + if (b.test(a)) return "#" == a.charAt(0) && 7 == a.length ? a : d(a) + } + c = "hex or rgb string"; + break; + case u: + if (e) return a + this.unit; + if (f && b.test(a)) return a; + c = "number(px) or string(unit)"; + break; + case v: + if (e) return a + this.unit; + if (f && b.test(a)) return a; + c = "number(px) or string(unit or %)"; + break; + case w: + if (e) return a + this.angle; + if (f && b.test(a)) return a; + c = "number(deg) or string(angle)"; + break; + case x: + if (e) return a; + if (f && v.test(a)) return a; + c = "number(unitless) or string(unit or %)" + } + return g(c, a), a + }, a.redraw = function() { + this.el.offsetHeight + } + }), + N = k(M, function(a, b) { + a.init = function() { + b.init.apply(this, arguments), this.original || (this.original = this.convert(this.get(), t)) + } + }), + O = k(M, function(a, b) { + a.init = function() { + b.init.apply(this, arguments), this.animate = this.fallback + }, a.get = function() { + return this.$el[this.name]() + }, a.update = function(a) { + this.$el[this.name](a) + } + }), + P = k(M, function(a, b) { + function c(a, b) { + var c, d, e, f, g; + for (c in a) f = Y[c], e = f[0], d = f[1] || c, g = this.convert(a[c], e), b.call(this, d, g, e) + } + a.init = function() { + b.init.apply(this, arguments), this.current || (this.current = {}, Y.perspective && T.perspective && (this.current.perspective = T.perspective, U(this.el, this.name, this.style(this.current)), this.redraw())) + }, a.set = function(a) { + c.call(this, a, function(a, b) { + this.current[a] = b + }), U(this.el, this.name, this.style(this.current)), this.redraw() + }, a.transition = function(a) { + var b = this.values(a); + this.tween = new S({ + current: this.current, + values: b, + duration: this.duration, + delay: this.delay, + ease: this.ease + }); + var c, d = {}; + for (c in this.current) d[c] = c in b ? b[c] : this.current[c]; + this.active = !0, this.nextStyle = this.style(d) + }, a.fallback = function(a) { + var b = this.values(a); + this.tween = new S({ + current: this.current, + values: b, + duration: this.duration, + delay: this.delay, + ease: this.ease, + update: this.update, + context: this + }) + }, a.update = function() { + U(this.el, this.name, this.style(this.current)) + }, a.style = function(a) { + var b, c = ""; + for (b in a) c += b + "(" + a[b] + ") "; + return c + }, a.values = function(a) { + var b, d = {}; + return c.call(this, a, function(a, c, e) { + d[a] = c, void 0 === this.current[a] && (b = 0, ~a.indexOf("scale") && (b = 1), this.current[a] = this.convert(b, e)) + }), d + } + }), + Q = k(function(b) { + function c(a) { + 1 === n.push(a) && I(g) + } + + function g() { + var a, b, c, d = n.length; + if (d) + for (I(g), b = J(), a = d; a--;) c = n[a], c && c.render(b) + } + + function i(b) { + var c, d = a.inArray(b, n); + d >= 0 && (c = n.slice(d + 1), n.length = d, c.length && (n = n.concat(c))) + } + + function j(a) { + return Math.round(a * o) / o + } + + function k(a, b, c) { + return e(a[0] + c * (b[0] - a[0]), a[1] + c * (b[1] - a[1]), a[2] + c * (b[2] - a[2])) + } + var m = { + ease: l.ease[1], + from: 0, + to: 1 + }; + b.init = function(a) { + this.duration = a.duration || 0, this.delay = a.delay || 0; + var b = a.ease || m.ease; + l[b] && (b = l[b][1]), "function" != typeof b && (b = m.ease), this.ease = b, this.update = a.update || f, this.complete = a.complete || f, this.context = a.context || this, this.name = a.name; + var c = a.from, + d = a.to; + void 0 === c && (c = m.from), void 0 === d && (d = m.to), this.unit = a.unit || "", "number" == typeof c && "number" == typeof d ? (this.begin = c, this.change = d - c) : this.format(d, c), this.value = this.begin + this.unit, this.start = J(), a.autoplay !== !1 && this.play() + }, b.play = function() { + this.active || (this.start || (this.start = J()), this.active = !0, c(this)) + }, b.stop = function() { + this.active && (this.active = !1, i(this)) + }, b.render = function(a) { + var b, c = a - this.start; + if (this.delay) { + if (c <= this.delay) return; + c -= this.delay + } + if (c < this.duration) { + var d = this.ease(c, 0, 1, this.duration); + return b = this.startRGB ? k(this.startRGB, this.endRGB, d) : j(this.begin + d * this.change), this.value = b + this.unit, void this.update.call(this.context, this.value) + } + b = this.endHex || this.begin + this.change, this.value = b + this.unit, this.update.call(this.context, this.value), this.complete.call(this.context), this.destroy() + }, b.format = function(a, b) { + if (b += "", a += "", "#" == a.charAt(0)) return this.startRGB = d(b), this.endRGB = d(a), this.endHex = a, this.begin = 0, void(this.change = 1); + if (!this.unit) { + var c = b.replace(q, ""), + e = a.replace(q, ""); + c !== e && h("tween", b, a), this.unit = c + } + b = parseFloat(b), a = parseFloat(a), this.begin = this.value = b, this.change = a - b + }, b.destroy = function() { + this.stop(), this.context = null, this.ease = this.update = this.complete = f + }; + var n = [], + o = 1e3 + }), + R = k(Q, function(a) { + a.init = function(a) { + this.duration = a.duration || 0, this.complete = a.complete || f, this.context = a.context, this.play() + }, a.render = function(a) { + var b = a - this.start; + b < this.duration || (this.complete.call(this.context), this.destroy()) + } + }), + S = k(Q, function(a, b) { + a.init = function(a) { + this.context = a.context, this.update = a.update, this.tweens = [], this.current = a.current; + var b, c; + for (b in a.values) c = a.values[b], this.current[b] !== c && this.tweens.push(new Q({ + name: b, + from: this.current[b], + to: c, + duration: a.duration, + delay: a.delay, + ease: a.ease, + autoplay: !1 + })); + this.play() + }, a.render = function(a) { + var b, c, d = this.tweens.length, + e = !1; + for (b = d; b--;) c = this.tweens[b], c.context && (c.render(a), this.current[c.name] = c.value, e = !0); + return e ? void(this.update && this.update.call(this.context)) : this.destroy() + }, a.destroy = function() { + if (b.destroy.call(this), this.tweens) { + var a, c = this.tweens.length; + for (a = c; a--;) this.tweens[a].destroy(); + this.tweens = null, this.current = null + } + } + }), + T = b.config = { + defaultUnit: "px", + defaultAngle: "deg", + keepInherited: !1, + hideBackface: !1, + perspective: "", + fallback: !F.transition, + agentTests: [] + }; + b.fallback = function(a) { + if (!F.transition) return T.fallback = !0; + T.agentTests.push("(" + a + ")"); + var b = new RegExp(T.agentTests.join("|"), "i"); + T.fallback = b.test(navigator.userAgent) + }, b.fallback("6.0.[2-5] Safari"), b.tween = function(a) { + return new Q(a) + }, b.delay = function(a, b, c) { + return new R({ + complete: b, + duration: a, + context: c + }) + }, a.fn.tram = function(a) { + return b.call(null, this, a) + }; + var U = a.style, + V = a.css, + W = { + transform: F.transform && F.transform.css + }, + X = { + color: [N, t], + background: [N, t, "background-color"], + "outline-color": [N, t], + "border-color": [N, t], + "border-top-color": [N, t], + "border-right-color": [N, t], + "border-bottom-color": [N, t], + "border-left-color": [N, t], + "border-width": [M, u], + "border-top-width": [M, u], + "border-right-width": [M, u], + "border-bottom-width": [M, u], + "border-left-width": [M, u], + "border-spacing": [M, u], + "letter-spacing": [M, u], + margin: [M, u], + "margin-top": [M, u], + "margin-right": [M, u], + "margin-bottom": [M, u], + "margin-left": [M, u], + padding: [M, u], + "padding-top": [M, u], + "padding-right": [M, u], + "padding-bottom": [M, u], + "padding-left": [M, u], + "outline-width": [M, u], + opacity: [M, s], + top: [M, v], + right: [M, v], + bottom: [M, v], + left: [M, v], + "font-size": [M, v], + "text-indent": [M, v], + "word-spacing": [M, v], + width: [M, v], + "min-width": [M, v], + "max-width": [M, v], + height: [M, v], + "min-height": [M, v], + "max-height": [M, v], + "line-height": [M, x], + "scroll-top": [O, s, "scrollTop"], + "scroll-left": [O, s, "scrollLeft"] + }, + Y = {}; + F.transform && (X.transform = [P], Y = { + x: [v, "translateX"], + y: [v, "translateY"], + rotate: [w], + rotateX: [w], + rotateY: [w], + scale: [s], + scaleX: [s], + scaleY: [s], + skew: [w], + skewX: [w], + skewY: [w] + }), F.transform && F.backface && (Y.z = [v, "translateZ"], Y.rotateZ = [w], Y.scaleZ = [s], Y.perspective = [u]); + var Z = /ms/, + $ = /s|\./, + _ = function() { + var a = "warn", + b = window.console; + return b && b[a] ? function(c) { + b[a](c) + } : f + }(); + return a.tram = b +}(window.jQuery); +/*! + * jQuery-ajaxTransport-XDomainRequest - v1.0.1 - 2013-10-17 + * https://github.com/MoonScript/jQuery-ajaxTransport-XDomainRequest + * Copyright (c) 2013 Jason Moon (@JSONMOON) + * Licensed MIT (/blob/master/LICENSE.txt) + */ +(function($) { + if (!$.support.cors && $.ajaxTransport && window.XDomainRequest) { + var n = /^https?:\/\//i; + var o = /^get|post$/i; + var p = new RegExp('^' + location.protocol, 'i'); + var q = /text\/html/i; + var r = /\/json/i; + var s = /\/xml/i; + $.ajaxTransport('* text html xml json', function(i, j, k) { + if (i.crossDomain && i.async && o.test(i.type) && n.test(i.url) && p.test(i.url)) { + var l = null; + var m = (j.dataType || '').toLowerCase(); + return { + send: function(f, g) { + l = new XDomainRequest(); + if (/^\d+$/.test(j.timeout)) { + l.timeout = j.timeout + } + l.ontimeout = function() { + g(500, 'timeout') + }; + l.onload = function() { + var a = 'Content-Length: ' + l.responseText.length + '\r\nContent-Type: ' + l.contentType; + var b = { + code: 200, + message: 'success' + }; + var c = { + text: l.responseText + }; + try { + if (m === 'html' || q.test(l.contentType)) { + c.html = l.responseText + } else if (m === 'json' || (m !== 'text' && r.test(l.contentType))) { + try { + c.json = $.parseJSON(l.responseText) + } catch (e) { + b.code = 500; + b.message = 'parseerror' + } + } else if (m === 'xml' || (m !== 'text' && s.test(l.contentType))) { + var d = new ActiveXObject('Microsoft.XMLDOM'); + d.async = false; + try { + d.loadXML(l.responseText) + } catch (e) { + d = undefined + } + if (!d || !d.documentElement || d.getElementsByTagName('parsererror').length) { + b.code = 500; + b.message = 'parseerror'; + throw 'Invalid XML: ' + l.responseText; + } + c.xml = d + } + } catch (parseMessage) { + throw parseMessage; + } finally { + g(b.code, b.message, c, a) + } + }; + l.onprogress = function() {}; + l.onerror = function() { + g(500, 'error', { + text: l.responseText + }) + }; + var h = ''; + if (j.data) { + h = ($.type(j.data) === 'string') ? j.data : $.param(j.data) + } + l.open(i.type, i.url); + l.send(h) + }, + abort: function() { + if (l) { + l.abort() + } + } + } + } + }) + } +})(jQuery); +/* jshint ignore:end */ +/** + * ---------------------------------------------------------------------- + * Init lib after plugins + */ +Webflow.init(); +/** + * ---------------------------------------------------------------------- + * Webflow: Interactions + */ +Webflow.define('ix', function($, _) { + 'use strict'; + + var api = {}; + var designer; + var $win = $(window); + var namespace = '.w-ix'; + var tram = window.tram; + var env = Webflow.env; + var ios = env.ios; + var inApp = env(); + var emptyFix = env.chrome && env.chrome < 35; + var transNone = 'none 0s ease 0s'; + var introEvent = 'w-ix-intro' + namespace; + var outroEvent = 'w-ix-outro' + namespace; + var fallbackProps = /width|height/; + var eventQueue = []; + var $subs = $(); + var config = {}; + var anchors = []; + var loads = []; + var readys = []; + var unique = 0; + var store; + + // Component types and proxy selectors + var components = { + tabs: '.w-tab-link, .w-tab-pane', + dropdown: '.w-dropdown', + slider: '.w-slide', + navbar: '.w-nav' + }; + + // ----------------------------------- + // Module methods + + api.init = function(list) { + setTimeout(function() { + init(list); + }, 1); + }; + + api.preview = function() { + designer = false; + setTimeout(function() { + init(window.__wf_ix); + }, 1); + }; + + api.design = function() { + designer = true; + $subs.each(teardown); + Webflow.scroll.off(scroll); + asyncEvents(); + anchors = []; + loads = []; + readys = []; + }; + + api.run = run; + api.events = {}; + api.style = inApp ? styleApp : stylePub; + + // ----------------------------------- + // Private methods + + function init(list) { + if (!list) return; + store = {}; + + // Map all interactions to a hash using slug as key. + config = {}; + _.each(list, function(item) { + config[item.slug] = item.value; + }); + + // Build each element's interaction keying from data attribute + var els = $('[data-ix]'); + if (!els.length) return; + els.each(teardown); + els.each(build); + + // Listen for scroll events if any anchors exist + if (anchors.length) { + Webflow.scroll.on(scroll); + setTimeout(scroll, 1); + } + + // Handle loads or readys if they exist + if (loads.length) $win.on('load', runLoads); + if (readys.length) setTimeout(runReadys, 1); + + // Init module events + initEvents(); + } + + function build(i, el) { + var $el = $(el); + var id = $el.attr('data-ix'); + var ix = config[id]; + if (!ix) return; + var triggers = ix.triggers; + if (!triggers) return; + var state = store[id] || (store[id] = {}); + + // Set initial styles, unless we detect an iOS device + any non-iOS triggers + var setStyles = !(ios && _.any(triggers, isNonIOS)); + if (setStyles) api.style($el, ix.style); + + _.each(triggers, function(trigger) { + var type = trigger.type; + var stepsB = trigger.stepsB && trigger.stepsB.length; + + function runA() { + run(trigger, $el, { + group: 'A' + }); + } + + function runB() { + run(trigger, $el, { + group: 'B' + }); + } + + if (type == 'load') { + (trigger.preload && !inApp) ? loads.push(runA) : readys.push(runA); + return; + } + + if (type == 'click') { + var stateKey = 'click:' + unique++; + if (trigger.descend) stateKey += ':descend'; + if (trigger.siblings) stateKey += ':siblings'; + if (trigger.selector) stateKey += ':' + trigger.selector; + + $el.on('click' + namespace, function(evt) { + if ($el.attr('href') === '#') evt.preventDefault(); + + run(trigger, $el, { + group: state[stateKey] ? 'B' : 'A' + }); + if (stepsB) state[stateKey] = !state[stateKey]; + }); + $subs = $subs.add($el); + return; + } + + if (type == 'hover') { + $el.on('mouseenter' + namespace, runA); + $el.on('mouseleave' + namespace, runB); + $subs = $subs.add($el); + return; + } + + // Check for a component proxy selector + var proxy = components[type]; + if (proxy) { + var $proxy = $el.closest(proxy); + $proxy.on(introEvent, runA).on(outroEvent, runB); + $subs = $subs.add($proxy); + return; + } + + // Ignore the following triggers on iOS devices + if (ios) return; + + if (type == 'scroll') { + anchors.push({ + el: $el, + trigger: trigger, + state: { + active: false + }, + offsetTop: convert(trigger.offsetTop), + offsetBot: convert(trigger.offsetBot) + }); + return; + } + }); + } + + function isNonIOS(trigger) { + return trigger.type == 'scroll'; + } + + function convert(offset) { + if (!offset) return 0; + offset = offset + ''; + var result = parseInt(offset, 10); + if (result !== result) return 0; + if (offset.indexOf('%') > 0) { + result = result / 100; + if (result >= 1) result = 0.999; + } + return result; + } + + function teardown(i, el) { + $(el).off(namespace); + } + + function scroll() { + var viewTop = $win.scrollTop(); + var viewHeight = $win.height(); + + // Check each anchor for a valid scroll trigger + var count = anchors.length; + for (var i = 0; i < count; i++) { + var anchor = anchors[i]; + var $el = anchor.el; + var trigger = anchor.trigger; + var stepsB = trigger.stepsB && trigger.stepsB.length; + var state = anchor.state; + var top = $el.offset().top; + var height = $el.outerHeight(); + var offsetTop = anchor.offsetTop; + var offsetBot = anchor.offsetBot; + if (offsetTop < 1 && offsetTop > 0) offsetTop *= viewHeight; + if (offsetBot < 1 && offsetBot > 0) offsetBot *= viewHeight; + var active = (top + height - offsetTop >= viewTop && top + offsetBot <= viewTop + viewHeight); + if (active === state.active) continue; + if (active === false && !stepsB) continue; + state.active = active; + run(trigger, $el, { + group: active ? 'A' : 'B' + }); + } + } + + function runLoads() { + var count = loads.length; + for (var i = 0; i < count; i++) { + loads[i](); + } + } + + function runReadys() { + var count = readys.length; + for (var i = 0; i < count; i++) { + readys[i](); + } + } + + function run(trigger, $el, opts, replay) { + opts = opts || {}; + var done = opts.done; + + // Do not run in designer unless forced + if (designer && !opts.force) return; + + // Operate on a set of grouped steps + var group = opts.group || 'A'; + var loop = trigger['loop' + group]; + var steps = trigger['steps' + group]; + if (!steps || !steps.length) return; + if (steps.length < 2) loop = false; + + // One-time init before any loops + if (!replay) { + + // Find selector within element descendants, siblings, or query whole document + var selector = trigger.selector; + if (selector) { + $el = ( + trigger.descend ? $el.find(selector) : + trigger.siblings ? $el.siblings(selector) : + $(selector) + ); + if (inApp) $el.attr('data-ix-affect', 1); + } + + // Apply empty fix for certain Chrome versions + if (emptyFix) $el.addClass('w-ix-emptyfix'); + } + + var _tram = tram($el); + + // Add steps + var meta = {}; + for (var i = 0; i < steps.length; i++) { + addStep(_tram, steps[i], meta); + } + + function fin() { + // Run trigger again if looped + if (loop) return run(trigger, $el, opts, true); + + // Reset any 'auto' values + if (meta.width == 'auto') _tram.set({ + width: 'auto' + }); + if (meta.height == 'auto') _tram.set({ + height: 'auto' + }); + + // Run callback + done && done(); + } + + // Add final step to queue if tram has started + meta.start ? _tram.then(fin) : fin(); + } + + function addStep(_tram, step, meta) { + var addMethod = 'add'; + var startMethod = 'start'; + + // Once the transition has started, we will always use then() to add to the queue. + if (meta.start) addMethod = startMethod = 'then'; + + // Parse transitions string on the current step + var transitions = step.transition; + if (transitions) { + transitions = transitions.split(','); + for (var i = 0; i < transitions.length; i++) { + var transition = transitions[i]; + var options = fallbackProps.test(transition) ? { + fallback: true + } : null; + _tram[addMethod](transition, options); + } + } + + // Build a clean object to pass to the tram method + var clean = tramify(step) || {}; + + // Store last width and height values + if (clean.width != null) meta.width = clean.width; + if (clean.height != null) meta.height = clean.height; + + // When transitions are not present, set values immediately and continue queue. + if (transitions == null) { + + // If we have started, wrap set() in then() and reset queue + if (meta.start) { + _tram.then(function() { + var queue = this.queue; + this.set(clean); + if (clean.display) { + _tram.redraw(); + Webflow.redraw.up(); + } + this.queue = queue; + this.next(); + }); + } else { + _tram.set(clean); + + // Always redraw after setting display + if (clean.display) { + _tram.redraw(); + Webflow.redraw.up(); + } + } + + // Use the wait() method to kick off queue in absence of transitions. + var wait = clean.wait; + if (wait != null) { + _tram.wait(wait); + meta.start = true; + } + + // Otherwise, when transitions are present + } else { + + // If display is present, handle it separately + if (clean.display) { + var display = clean.display; + delete clean.display; + + // If we've already started, we need to wrap it in a then() + if (meta.start) { + _tram.then(function() { + var queue = this.queue; + this.set({ + display: display + }).redraw(); + Webflow.redraw.up(); + this.queue = queue; + this.next(); + }); + } else { + _tram.set({ + display: display + }).redraw(); + Webflow.redraw.up(); + } + } + + // Otherwise, start a transition using the current start method. + _tram[startMethod](clean); + meta.start = true; + } + } + + // (In app) Set styles immediately and manage upstream transition + function styleApp(el, data) { + var _tram = tram(el); + + // Get computed transition value + el.css('transition', ''); + var computed = el.css('transition'); + + // If computed is disabled, clear upstream + if (computed === transNone) computed = _tram.upstream = null; + + // Disable upstream temporarily + _tram.upstream = transNone; + + // Set values immediately + _tram.set(tramify(data)); + + // Only restore upstream in preview mode + _tram.upstream = computed; + } + + // (Published) Set styles immediately on specified jquery element + function stylePub(el, data) { + tram(el).set(tramify(data)); + } + + // Build a clean object for tram + function tramify(obj) { + var result = {}; + var found = false; + for (var x in obj) { + if (x === 'transition') continue; + result[x] = obj[x]; + found = true; + } + // If empty, return null for tram.set/stop compliance + return found ? result : null; + } + + // Events used by other webflow modules + var events = { + reset: function(i, el) { + el.__wf_intro = null; + }, + intro: function(i, el) { + if (el.__wf_intro) return; + el.__wf_intro = true; + $(el).triggerHandler(introEvent); + }, + outro: function(i, el) { + if (!el.__wf_intro) return; + el.__wf_intro = null; + $(el).triggerHandler(outroEvent); + } + }; + + // Trigger events in queue + point to sync methods + function initEvents() { + var count = eventQueue.length; + for (var i = 0; i < count; i++) { + var memo = eventQueue[i]; + memo[0](0, memo[1]); + } + eventQueue = []; + $.extend(api.events, events); + } + + // Replace events with async methods prior to init + function asyncEvents() { + _.each(events, function(func, name) { + api.events[name] = function(i, el) { + eventQueue.push([func, el]); + }; + }); + } + + asyncEvents(); + + // Export module + return api; +}); +/** + * ---------------------------------------------------------------------- + * Webflow: Touch events + */ +Webflow.define('touch', function($, _) { + 'use strict'; + + var api = {}; + var fallback = !document.addEventListener; + var getSelection = window.getSelection; + + // Fallback to click events in old IE + if (fallback) { + $.event.special.tap = { + bindType: 'click', + delegateType: 'click' + }; + } + + api.init = function(el) { + if (fallback) return null; + el = typeof el === 'string' ? $(el).get(0) : el; + return el ? new Touch(el) : null; + }; + + function Touch(el) { + var active = false; + var dirty = false; + var useTouch = false; + var thresholdX = Math.min(Math.round(window.innerWidth * 0.04), 40); + var startX, startY, lastX; + var _move = _.throttle(move); + + el.addEventListener('touchstart', start, false); + el.addEventListener('touchmove', _move, false); + el.addEventListener('touchend', end, false); + el.addEventListener('touchcancel', cancel, false); + el.addEventListener('mousedown', start, false); + el.addEventListener('mousemove', _move, false); + el.addEventListener('mouseup', end, false); + el.addEventListener('mouseout', cancel, false); + + function start(evt) { + // We don’t handle multi-touch events yet. + var touches = evt.touches; + if (touches && touches.length > 1) { + return; + } + + active = true; + dirty = false; + + if (touches) { + useTouch = true; + startX = touches[0].clientX; + startY = touches[0].clientY; + } else { + startX = evt.clientX; + startY = evt.clientY; + } + + lastX = startX; + } + + function move(evt) { + if (!active) return; + + if (useTouch && evt.type === 'mousemove') { + evt.preventDefault(); + evt.stopPropagation(); + return; + } + + var touches = evt.touches; + var x = touches ? touches[0].clientX : evt.clientX; + var y = touches ? touches[0].clientY : evt.clientY; + + var velocityX = x - lastX; + lastX = x; + + // Allow swipes while pointer is down, but prevent them during text selection + if (Math.abs(velocityX) > thresholdX && getSelection && getSelection() + '' === '') { + triggerEvent('swipe', evt, { + direction: velocityX > 0 ? 'right' : 'left' + }); + cancel(); + } + + // If pointer moves more than 10px flag to cancel tap + if (Math.abs(x - startX) > 10 || Math.abs(y - startY) > 10) { + dirty = true; + } + } + + function end(evt) { + if (!active) return; + active = false; + + if (useTouch && evt.type === 'mouseup') { + evt.preventDefault(); + evt.stopPropagation(); + useTouch = false; + return; + } + + if (!dirty) triggerEvent('tap', evt); + } + + function cancel(evt) { + active = false; + } + + function destroy() { + el.removeEventListener('touchstart', start, false); + el.removeEventListener('touchmove', _move, false); + el.removeEventListener('touchend', end, false); + el.removeEventListener('touchcancel', cancel, false); + el.removeEventListener('mousedown', start, false); + el.removeEventListener('mousemove', _move, false); + el.removeEventListener('mouseup', end, false); + el.removeEventListener('mouseout', cancel, false); + el = null; + } + + // Public instance methods + this.destroy = destroy; + } + + // Wrap native event to supoprt preventdefault + stopPropagation + function triggerEvent(type, evt, data) { + var newEvent = $.Event(type, { + originalEvent: evt + }); + $(evt.target).trigger(newEvent, data); + } + + // Listen for touch events on all nodes by default. + api.instance = api.init(document); + + // Export module + return api; +}); +/** + * ---------------------------------------------------------------------- + * Webflow: Forms + */ +Webflow.define('forms', function($, _) { + 'use strict'; + + var api = {}; + var $doc = $(document); + var $forms; + var loc = window.location; + var retro = window.XDomainRequest && !window.atob; + var namespace = '.w-form'; + var siteId; + var emailField = /e(\-)?mail/i; + var emailValue = /^\S+@\S+$/; + var alert = window.alert; + var listening; + + // MailChimp domains: list-manage.com + mirrors + var chimpRegex = /list-manage[1-9]?.com/i; + + api.ready = function() { + // Init forms + init(); + + // Wire document events once + if (!listening) addListeners(); + }; + + api.preview = api.design = function() { + init(); + }; + + function init() { + siteId = $('html').attr('data-wf-site'); + + $forms = $(namespace + ' form'); + if (!$forms.length) return; + $forms.each(build); + } + + function build(i, el) { + // Store form state using namespace + var $el = $(el); + var data = $.data(el, namespace); + if (!data) data = $.data(el, namespace, { + form: $el + }); // data.form + + reset(data); + var wrap = $el.closest('div.w-form'); + data.done = wrap.find('> .w-form-done'); + data.fail = wrap.find('> .w-form-fail'); + + var action = data.action = $el.attr('action'); + data.handler = null; + data.redirect = $el.attr('data-redirect'); + + // MailChimp form + if (chimpRegex.test(action)) { + data.handler = submitMailChimp; + return; + } + + // Custom form action + if (action) return; + + // Webflow form + if (siteId) { + data.handler = submitWebflow; + return; + } + + // Alert for disconnected Webflow forms + disconnected(); + } + + function addListeners() { + listening = true; + + // Handle form submission for Webflow forms + $doc.on('submit', namespace + ' form', function(evt) { + var data = $.data(this, namespace); + if (data.handler) { + data.evt = evt; + data.handler(data); + } + }); + } + + // Reset data common to all submit handlers + function reset(data) { + var btn = data.btn = data.form.find(':input[type="submit"]'); + data.wait = data.btn.attr('data-wait') || null; + data.success = false; + btn.prop('disabled', false); + data.label && btn.val(data.label); + } + + // Disable submit button + function disableBtn(data) { + var btn = data.btn; + var wait = data.wait; + btn.prop('disabled', true); + // Show wait text and store previous label + if (wait) { + data.label = btn.val(); + btn.val(wait); + } + } + + // Find form fields, validate, and set value pairs + function findFields(form, result) { + var status = null; + result = result || {}; + + // The ":input" selector is a jQuery shortcut to select all inputs, selects, textareas + form.find(':input:not([type="submit"])').each(function(i, el) { + var field = $(el); + var type = field.attr('type'); + var name = field.attr('data-name') || field.attr('name') || ('Field ' + (i + 1)); + var value = field.val(); + + if (type == 'checkbox') { + value = field.is(':checked'); + } + if (type == 'radio') { + // Radio group value already processed + if (result[name] === null || typeof result[name] == 'string') { + return; + } + + value = form.find('input[name="' + field.attr('name') + '"]:checked').val() || null; + } + + if (typeof value == 'string') value = $.trim(value); + result[name] = value; + status = status || getStatus(field, name, value); + }); + + return status; + } + + function getStatus(field, name, value) { + var status = null; + if (!field.attr('required')) return null; + if (!value) status = 'Please fill out the required field: ' + name; + else if (emailField.test(name) || emailField.test(field.attr('type'))) { + if (!emailValue.test(value)) status = 'Please enter a valid email address for: ' + name; + } + return status; + } + + // Submit form to Webflow + function submitWebflow(data) { + reset(data); + + var form = data.form; + var payload = { + name: form.attr('data-name') || form.attr('name') || 'Untitled Form', + source: loc.href, + test: Webflow.env(), + fields: {} + }; + + preventDefault(data); + + // Find & populate all fields + var status = findFields(form, payload.fields); + if (status) return alert(status); + + // Disable submit button + disableBtn(data); + + // Read site ID + // NOTE: If this site is exported, the HTML tag must retain the data-wf-site attribute for forms to work + if (!siteId) { + afterSubmit(data); + return; + } + var url = 'https://webflow.com/api/v1/form/' + siteId; + + // Work around same-protocol IE XDR limitation + if (retro && url.indexOf('https://webflow.com') >= 0) { + url = url.replace('https://webflow.com/', 'http://data.webflow.com/'); + } + + $.ajax({ + url: url, + type: 'POST', + data: payload, + dataType: 'json', + crossDomain: true + }).done(function() { + data.success = true; + afterSubmit(data); + }).fail(function() { + afterSubmit(data); + }); + } + + // Submit form to MailChimp + function submitMailChimp(data) { + reset(data); + + var form = data.form; + var payload = {}; + + // Skip Ajax submission if http/s mismatch, fallback to POST instead + if (/^https/.test(loc.href) && !/^https/.test(data.action)) { + form.attr('method', 'post'); + return; + } + + preventDefault(data); + + // Find & populate all fields + var status = findFields(form, payload); + if (status) return alert(status); + + // Disable submit button + disableBtn(data); + + // Use special format for MailChimp params + var fullName; + _.each(payload, function(value, key) { + if (emailField.test(key)) payload.EMAIL = value; + if (/^((full[ _-]?)?name)$/i.test(key)) fullName = value; + if (/^(first[ _-]?name)$/i.test(key)) payload.FNAME = value; + if (/^(last[ _-]?name)$/i.test(key)) payload.LNAME = value; + }); + + if (fullName && !payload.FNAME) { + fullName = fullName.split(' '); + payload.FNAME = fullName[0]; + payload.LNAME = payload.LNAME || fullName[1]; + } + + // Use the (undocumented) MailChimp jsonp api + var url = data.action.replace('/post?', '/post-json?') + '&c=?'; + // Add special param to prevent bot signups + var userId = url.indexOf('u=') + 2; + userId = url.substring(userId, url.indexOf('&', userId)); + var listId = url.indexOf('id=') + 3; + listId = url.substring(listId, url.indexOf('&', listId)); + payload['b_' + userId + '_' + listId] = ''; + + $.ajax({ + url: url, + data: payload, + dataType: 'jsonp' + }).done(function(resp) { + data.success = (resp.result == 'success' || /already/.test(resp.msg)); + if (!data.success) console.info('MailChimp error: ' + resp.msg); + afterSubmit(data); + }).fail(function() { + afterSubmit(data); + }); + } + + // Common callback which runs after all Ajax submissions + function afterSubmit(data) { + var form = data.form; + var wrap = form.closest('div.w-form'); + var redirect = data.redirect; + var success = data.success; + + // Redirect to a success url if defined + if (success && redirect) { + Webflow.location(redirect); + return; + } + + // Show or hide status divs + data.done.toggle(success); + data.fail.toggle(!success); + + // Hide form on success + form.toggle(!success); + + // Reset data and enable submit button + reset(data); + } + + function preventDefault(data) { + data.evt && data.evt.preventDefault(); + data.evt = null; + } + + var disconnected = _.debounce(function() { + alert('Oops! This page has a form that is powered by Webflow, but important code was removed that is required to make the form work. Please contact support@webflow.com to fix this issue.'); + }, 100); + + // Export module + return api; +}); +/** + * ---------------------------------------------------------------------- + * Webflow: Smooth scroll + */ +Webflow.define('scroll', function($) { + 'use strict'; + + var $doc = $(document); + var win = window; + var loc = win.location; + var history = win.history; + var validHash = /^[a-zA-Z][\w:.-]*$/; + + function ready() { + // If hash is already present on page load, scroll to it right away + if (loc.hash) { + findEl(loc.hash.substring(1)); + } + + // When clicking on a link, check if it links to another part of the page + $doc.on('click', 'a', function(e) { + if (Webflow.env('design')) { + return; + } + + // Ignore links being used by jQuery mobile + if (window.$.mobile && $(e.currentTarget).hasClass('ui-link')) return; + + var hash = this.hash ? this.hash.substring(1) : null; + if (hash) { + findEl(hash, e); + } + }); + } + + function findEl(hash, e) { + if (!validHash.test(hash)) return; + + var el = $('#' + hash); + if (!el.length) { + return; + } + + if (e) { + e.preventDefault(); + e.stopPropagation(); + } + + // Push new history state + if (loc.hash !== hash && history && history.pushState) { + var oldHash = history.state && history.state.hash; + if (oldHash !== hash) { + history.pushState({ + hash: hash + }, '', '#' + hash); + } + } + + // If a fixed header exists, offset for the height + var header = $('header, body > .header, body > .w-nav'); + var offset = header.css('position') === 'fixed' ? header.outerHeight() : 0; + + win.setTimeout(function() { + scroll(el, offset); + }, e ? 0 : 300); + } + + function scroll(el, offset) { + var start = $(win).scrollTop(); + var end = el.offset().top - offset; + + // If specified, scroll so that the element ends up in the middle of the viewport + if (el.data('scroll') == 'mid') { + var available = $(win).height() - offset; + var elHeight = el.outerHeight(); + if (elHeight < available) { + end -= Math.round((available - elHeight) / 2); + } + } + + var mult = 1; + + // Check for custom time multiplier on the body and the element + $('body').add(el).each(function(i) { + var time = parseFloat($(this).attr('data-scroll-time'), 10); + if (!isNaN(time) && (time === 0 || time > 0)) { + mult = time; + } + }); + + // Shim for IE8 and below + if (!Date.now) { + Date.now = function() { + return new Date().getTime(); + }; + } + + var clock = Date.now(); + var animate = win.requestAnimationFrame || win.mozRequestAnimationFrame || win.webkitRequestAnimationFrame || function(fn) { + win.setTimeout(fn, 15); + }; + var duration = (472.143 * Math.log(Math.abs(start - end) + 125) - 2000) * mult; + + var step = function() { + var elapsed = Date.now() - clock; + win.scroll(0, getY(start, end, elapsed, duration)); + + if (elapsed <= duration) { + animate(step); + } + }; + + step(); + } + + function getY(start, end, elapsed, duration) { + if (elapsed > duration) { + return end; + } + + return start + (end - start) * ease(elapsed / duration); + } + + function ease(t) { + return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1; + } + + // Export module + return { + ready: ready + }; +}); +/** + * ---------------------------------------------------------------------- + * Webflow: Auto-select links to current page or section + */ +Webflow.define('links', function($, _) { + 'use strict'; + + var api = {}; + var $win = $(window); + var designer; + var inApp = Webflow.env(); + var location = window.location; + var linkCurrent = 'w--current'; + var validHash = /^#[a-zA-Z][\w:.-]*$/; + var indexPage = /index\.(html|php)$/; + var dirList = /\/$/; + var anchors; + var slug; + + // ----------------------------------- + // Module methods + + api.ready = api.design = api.preview = init; + + // ----------------------------------- + // Private methods + + function init() { + designer = inApp && Webflow.env('design'); + slug = Webflow.env('slug') || location.pathname || ''; + + // Reset scroll listener, init anchors + Webflow.scroll.off(scroll); + anchors = []; + + // Test all links for a selectable href + var links = document.links; + for (var i = 0; i < links.length; ++i) { + select(links[i]); + } + + // Listen for scroll if any anchors exist + if (anchors.length) { + Webflow.scroll.on(scroll); + scroll(); + } + } + + function select(link) { + var href = link.getAttribute('href'); + + // Ignore any hrefs with a colon to safely avoid all uri schemes + if (href.indexOf(':') >= 0) return; + + var $link = $(link); + + // Check for valid hash links w/ sections and use scroll anchor + if (href.indexOf('#') === 0 && validHash.test(href)) { + // Ignore #edit anchors + if (href === '#edit') return; + var $section = $(href); + $section.length && anchors.push({ + link: $link, + sec: $section, + active: false + }); + return; + } + + // Determine whether the link should be selected + var match = (link.href === location.href) || (href === slug) || (indexPage.test(href) && dirList.test(slug)); + setClass($link, linkCurrent, match); + } + + function scroll() { + var viewTop = $win.scrollTop(); + var viewHeight = $win.height(); + + // Check each anchor for a section in view + _.each(anchors, function(anchor) { + var $link = anchor.link; + var $section = anchor.sec; + var top = $section.offset().top; + var height = $section.outerHeight(); + var offset = viewHeight * 0.5; + var active = ($section.is(':visible') && + top + height - offset >= viewTop && + top + offset <= viewTop + viewHeight); + if (anchor.active === active) return; + anchor.active = active; + setClass($link, linkCurrent, active); + if (designer) $link[0].__wf_current = active; + }); + } + + function setClass($elem, className, add) { + var exists = $elem.hasClass(className); + if (add && exists) return; + if (!add && !exists) return; + add ? $elem.addClass(className) : $elem.removeClass(className); + } + + // Export module + return api; +}); +/** + * ---------------------------------------------------------------------- + * Webflow: Slider component + */ +Webflow.define('slider', function($, _) { + 'use strict'; + + var api = {}; + var tram = window.tram; + var $doc = $(document); + var $sliders; + var designer; + var inApp = Webflow.env(); + var namespace = '.w-slider'; + var dot = '
'; + var ix = Webflow.ixEvents(); + var fallback; + var redraw; + + // ----------------------------------- + // Module methods + + api.ready = function() { + init(); + }; + + api.design = function() { + designer = true; + init(); + }; + + api.preview = function() { + designer = false; + init(); + }; + + api.redraw = function() { + redraw = true; + init(); + }; + + api.destroy = removeListeners; + + // ----------------------------------- + // Private methods + + function init() { + // Find all sliders on the page + $sliders = $doc.find(namespace); + if (!$sliders.length) return; + $sliders.filter(':visible').each(build); + redraw = null; + if (fallback) return; + + // Wire events + removeListeners(); + addListeners(); + } + + function removeListeners() { + Webflow.resize.off(renderAll); + Webflow.redraw.off(api.redraw); + } + + function addListeners() { + Webflow.resize.on(renderAll); + Webflow.redraw.on(api.redraw); + } + + function renderAll() { + $sliders.filter(':visible').each(render); + } + + function build(i, el) { + var $el = $(el); + + // Store slider state in data + var data = $.data(el, namespace); + if (!data) data = $.data(el, namespace, { + index: 0, + depth: 1, + el: $el, + config: {} + }); + data.mask = $el.children('.w-slider-mask'); + data.left = $el.children('.w-slider-arrow-left'); + data.right = $el.children('.w-slider-arrow-right'); + data.nav = $el.children('.w-slider-nav'); + data.slides = data.mask.children('.w-slide'); + data.slides.each(ix.reset); + if (redraw) data.maskWidth = 0; + + // Disable in old browsers + if (!tram.support.transform) { + data.left.hide(); + data.right.hide(); + data.nav.hide(); + fallback = true; + return; + } + + // Remove old events + data.el.off(namespace); + data.left.off(namespace); + data.right.off(namespace); + data.nav.off(namespace); + + // Set config from data attributes + configure(data); + + // Add events based on mode + if (designer) { + data.el.on('setting' + namespace, handler(data)); + stopTimer(data); + data.hasTimer = false; + } else { + data.el.on('swipe' + namespace, handler(data)); + data.left.on('tap' + namespace, previous(data)); + data.right.on('tap' + namespace, next(data)); + + // Start timer if autoplay is true, only once + if (data.config.autoplay && !data.hasTimer) { + data.hasTimer = true; + data.timerCount = 1; + startTimer(data); + } + } + + // Listen to nav events + data.nav.on('tap' + namespace, '> div', handler(data)); + + // Remove gaps from formatted html (for inline-blocks) + if (!inApp) { + data.mask.contents().filter(function() { + return this.nodeType === 3; + }).remove(); + } + + // Run first render + render(i, el); + } + + function configure(data) { + var config = {}; + + config.crossOver = 0; + + // Set config options from data attributes + config.animation = data.el.attr('data-animation') || 'slide'; + if (config.animation == 'outin') { + config.animation = 'cross'; + config.crossOver = 0.5; + } + config.easing = data.el.attr('data-easing') || 'ease'; + + var duration = data.el.attr('data-duration'); + config.duration = duration != null ? +duration : 500; + + if (+data.el.attr('data-infinite')) config.infinite = true; + + if (+data.el.attr('data-hide-arrows')) { + config.hideArrows = true; + } else if (data.config.hideArrows) { + data.left.show(); + data.right.show(); + } + + if (+data.el.attr('data-autoplay')) { + config.autoplay = true; + config.delay = +data.el.attr('data-delay') || 2000; + config.timerMax = +data.el.attr('data-autoplay-limit'); + // Disable timer on first touch or mouse down + var touchEvents = 'mousedown' + namespace + ' touchstart' + namespace; + if (!designer) data.el.off(touchEvents).one(touchEvents, function() { + stopTimer(data); + }); + } + + // Use edge buffer to help calculate page count + var arrowWidth = data.right.width(); + config.edge = arrowWidth ? arrowWidth + 40 : 100; + + // Store config in data + data.config = config; + } + + function previous(data) { + return function(evt) { + change(data, { + index: data.index - 1, + vector: -1 + }); + }; + } + + function next(data) { + return function(evt) { + change(data, { + index: data.index + 1, + vector: 1 + }); + }; + } + + function select(data, value) { + // Select page based on slide element index + var found = null; + if (value === data.slides.length) { + init(); + layout(data); // Rebuild and find new slides + } + _.each(data.anchors, function(anchor, index) { + $(anchor.els).each(function(i, el) { + if ($(el).index() === value) found = index; + }); + }); + if (found != null) change(data, { + index: found, + immediate: true + }); + } + + function startTimer(data) { + stopTimer(data); + var config = data.config; + var timerMax = config.timerMax; + if (timerMax && data.timerCount++ > timerMax) return; + data.timerId = window.setTimeout(function() { + if (data.timerId == null || designer) return; + next(data)(); + startTimer(data); + }, config.delay); + } + + function stopTimer(data) { + window.clearTimeout(data.timerId); + data.timerId = null; + } + + function handler(data) { + return function(evt, options) { + options = options || {}; + + // Designer settings + if (designer && evt.type == 'setting') { + if (options.select == 'prev') return previous(data)(); + if (options.select == 'next') return next(data)(); + configure(data); + layout(data); + if (options.select == null) return; + select(data, options.select); + return; + } + + // Swipe event + if (evt.type == 'swipe') { + if (options.direction == 'left') return next(data)(); + if (options.direction == 'right') return previous(data)(); + return; + } + + // Page buttons + if (data.nav.has(evt.target).length) { + change(data, { + index: $(evt.target).index() + }); + } + }; + } + + function change(data, options) { + options = options || {}; + var config = data.config; + var anchors = data.anchors; + + // Set new index + data.previous = data.index; + var index = options.index; + var shift = {}; + if (index < 0) { + index = anchors.length - 1; + if (config.infinite) { + // Shift first slide to the end + shift.x = -data.endX; + shift.from = 0; + shift.to = anchors[0].width; + } + } else if (index >= anchors.length) { + index = 0; + if (config.infinite) { + // Shift last slide to the start + shift.x = anchors[anchors.length - 1].width; + shift.from = -anchors[anchors.length - 1].x; + shift.to = shift.from - shift.x; + } + } + data.index = index; + + // Select page nav + var active = data.nav.children().eq(data.index).addClass('w-active'); + data.nav.children().not(active).removeClass('w-active'); + + // Hide arrows + if (config.hideArrows) { + data.index === anchors.length - 1 ? data.right.hide() : data.right.show(); + data.index === 0 ? data.left.hide() : data.left.show(); + } + + // Get page offset from anchors + var lastOffsetX = data.offsetX || 0; + var offsetX = data.offsetX = -anchors[data.index].x; + var resetConfig = { + x: offsetX, + opacity: 1, + visibility: '' + }; + + // Transition slides + var targets = $(anchors[data.index].els); + var previous = $(anchors[data.previous] && anchors[data.previous].els); + var others = data.slides.not(targets); + var animation = config.animation; + var easing = config.easing; + var duration = Math.round(config.duration); + var vector = options.vector || (data.index > data.previous ? 1 : -1); + var fadeRule = 'opacity ' + duration + 'ms ' + easing; + var slideRule = 'transform ' + duration + 'ms ' + easing; + + // Trigger IX events + if (!designer) { + targets.each(ix.intro); + others.each(ix.outro); + } + + // Set immediately after layout changes (but not during redraw) + if (options.immediate && !redraw) { + tram(targets).set(resetConfig); + resetOthers(); + return; + } + + // Exit early if index is unchanged + if (data.index == data.previous) return; + + // Cross Fade / Out-In + if (animation == 'cross') { + var reduced = Math.round(duration - duration * config.crossOver); + var wait = Math.round(duration - reduced); + fadeRule = 'opacity ' + reduced + 'ms ' + easing; + tram(previous) + .set({ + visibility: '' + }) + .add(fadeRule) + .start({ + opacity: 0 + }); + tram(targets) + .set({ + visibility: '', + x: offsetX, + opacity: 0, + zIndex: data.depth++ + }) + .add(fadeRule) + .wait(wait) + .then({ + opacity: 1 + }) + .then(resetOthers); + return; + } + + // Fade Over + if (animation == 'fade') { + tram(previous) + .set({ + visibility: '' + }) + .stop(); + tram(targets) + .set({ + visibility: '', + x: offsetX, + opacity: 0, + zIndex: data.depth++ + }) + .add(fadeRule) + .start({ + opacity: 1 + }) + .then(resetOthers); + return; + } + + // Slide Over + if (animation == 'over') { + resetConfig = { + x: data.endX + }; + tram(previous) + .set({ + visibility: '' + }) + .stop(); + tram(targets) + .set({ + visibility: '', + zIndex: data.depth++, + x: offsetX + anchors[data.index].width * vector + }) + .add(slideRule) + .start({ + x: offsetX + }) + .then(resetOthers); + return; + } + + // Slide - infinite scroll + if (config.infinite && shift.x) { + tram(data.slides.not(previous)) + .set({ + visibility: '', + x: shift.x + }) + .add(slideRule) + .start({ + x: offsetX + }); + tram(previous) + .set({ + visibility: '', + x: shift.from + }) + .add(slideRule) + .start({ + x: shift.to + }); + data.shifted = previous; + + } else { + if (config.infinite && data.shifted) { + tram(data.shifted).set({ + visibility: '', + x: lastOffsetX + }); + data.shifted = null; + } + + // Slide - basic scroll + tram(data.slides) + .set({ + visibility: '' + }) + .add(slideRule) + .start({ + x: offsetX + }); + } + + // Helper to move others out of view + function resetOthers() { + var targets = $(anchors[data.index].els); + var others = data.slides.not(targets); + if (animation != 'slide') resetConfig.visibility = 'hidden'; + tram(others).set(resetConfig); + } + } + + function render(i, el) { + var data = $.data(el, namespace); + if (maskChanged(data)) return layout(data); + if (designer && slidesChanged(data)) layout(data); + } + + function layout(data) { + // Determine page count from width of slides + var pages = 1; + var offset = 0; + var anchor = 0; + var width = 0; + data.anchors = [{ + els: [], + x: 0, + width: 0 + }]; + data.slides.each(function(i, el) { + if (anchor - offset > data.maskWidth - data.config.edge) { + pages++; + offset += data.maskWidth; + // Store page anchor for transition + data.anchors[pages - 1] = { + els: [], + x: anchor, + width: 0 + }; + } + // Set next anchor using current width + margin + width = $(el).outerWidth(true); + anchor += width; + data.anchors[pages - 1].width += width; + data.anchors[pages - 1].els.push(el); + }); + data.endX = anchor; + + // Build dots if nav exists and needs updating + if (designer) data.pages = null; + if (data.nav.length && data.pages !== pages) { + data.pages = pages; + buildNav(data); + } + + // Make sure index is still within range and call change handler + var index = data.index; + if (index >= pages) index = pages - 1; + change(data, { + immediate: true, + index: index + }); + } + + function buildNav(data) { + var dots = []; + var $dot; + var spacing = data.el.attr('data-nav-spacing'); + if (spacing) spacing = parseFloat(spacing) + 'px'; + for (var i = 0; i < data.pages; i++) { + $dot = $(dot); + if (data.nav.hasClass('w-num')) $dot.text(i + 1); + if (spacing != null) $dot.css({ + 'margin-left': spacing, + 'margin-right': spacing + }); + dots.push($dot); + } + data.nav.empty().append(dots); + } + + function maskChanged(data) { + var maskWidth = data.mask.width(); + if (data.maskWidth !== maskWidth) { + data.maskWidth = maskWidth; + return true; + } + return false; + } + + function slidesChanged(data) { + var slidesWidth = 0; + data.slides.each(function(i, el) { + slidesWidth += $(el).outerWidth(true); + }); + if (data.slidesWidth !== slidesWidth) { + data.slidesWidth = slidesWidth; + return true; + } + return false; + } + + // Export module + return api; +}); +/** + * ---------------------------------------------------------------------- + * Webflow: Lightbox component + */ +var lightbox = (function(window, document, $, tram, undefined) { + 'use strict'; + + var isArray = Array.isArray; + var namespace = 'w-lightbox'; + var prefix = namespace + '-'; + var prefixRegex = /(^|\s+)/g; + var matchMedia = window.matchMedia || function(media) { + // IE9 polyfill + return { + matches: window.styleMedia.matchMedium(media) + }; + }; + var pixelRatio = window.devicePixelRatio || 1; + var breakpoint = '(min-width: 1025px)'; + + // Array of objects describing items to be displayed. + var items = []; + + // Index of the currently displayed item. + var currentIndex; + + // Object holding references to jQuery wrapped nodes. + var $refs; + + // Instance of Spinner + var spinner; + + function lightbox(thing, index) { + items = isArray(thing) ? thing : [thing]; + + if (!$refs) { + lightbox.build(); + } + + if (items.length > 1) { + $refs.items = $refs.empty; + + items.forEach(function(item) { + var $thumbnail = dom('thumbnail'); + var $item = dom('item').append($thumbnail); + + $refs.items = $refs.items.add($item); + + loadImage(item.url, function($image) { + if ($image.prop('width') > $image.prop('height')) { + addClass($image, 'wide'); + } else { + addClass($image, 'tall'); + } + $thumbnail.append(addClass($image, 'thumbnail-image')); + }); + }); + + $refs.strip.empty().append($refs.items); + addClass($refs.content, 'group'); + } + + tram( + // Focus the lightbox to receive keyboard events. + removeClass($refs.lightbox, 'hide').focus() + ) + .add('opacity .3s') + .start({ + opacity: 1 + }); + + // Prevent document from scrolling while lightbox is active. + addClass($refs.html, 'noscroll'); + + return lightbox.show(index || 0); + } + + /** + * Creates the DOM structure required by the lightbox. + */ + lightbox.build = function() { + // In case `build` is called more than once. + lightbox.destroy(); + + $refs = { + html: $(document.documentElement), + // Empty jQuery object can be used to build new ones using `.add`. + empty: $() + }; + + $refs.arrowLeft = dom('control left inactive'); + $refs.arrowRight = dom('control right inactive'); + $refs.close = dom('control close'); + $refs.controls = $refs.empty.add($refs.arrowLeft).add($refs.arrowRight).add($refs.close); + + $refs.spinner = dom('spinner'); + $refs.strip = dom('strip'); + + spinner = new Spinner($refs.spinner, prefixed('hide')); + + $refs.content = dom('content') + .append($refs.spinner, $refs.controls); + + $refs.container = dom('container') + .append($refs.content, $refs.strip); + + $refs.lightbox = dom('backdrop hide') + .append($refs.container); + + // We are delegating events for performance reasons and also + // to not have to reattach handlers when images change. + $refs.strip.on('tap', selector('item'), itemTapHandler); + $refs.content + .on('swipe', swipeHandler) + .on('tap', selector('left'), preventDefaultAnd(lightbox.prev)) + .on('tap', selector('right'), preventDefaultAnd(lightbox.next)) + .on('tap', selector('close'), preventDefaultAnd(lightbox.hide)) + .on('tap', selector('image, caption'), toggleControlsOr(lightbox.next)); + $refs.container.on( + 'tap', selector('view, strip'), toggleControlsOr(lightbox.hide) + ) + // Prevent images from being dragged around. + .on('dragstart', selector('img'), preventDefault); + $refs.lightbox + .on('keydown', keyHandler) + // While visible, prevent lightbox from loosing focus to other nodes. + // IE looses focus without letting us know. + .on('focusin', focusThis) + // Unfortunately setTimeout is needed because of a 14 year old + // Firefox bug (https://bugzilla.mozilla.org/show_bug.cgi?id=53579#c4). + .on('blur', function() { + setTimeout(focusThis.bind(this), 0); + }); + + // The `tabindex` attribute is needed to enable non-input elements + // to receive keyboard events. + $('body').append($refs.lightbox.prop('tabIndex', 0)); + + return lightbox; + }; + + /** + * Dispose of DOM nodes created by the lightbox. + */ + lightbox.destroy = function() { + if (!$refs) { + return; + } + + // Event handlers are also removed. + $refs.lightbox.remove(); + $refs = undefined; + }; + + /** + * Show a specific item. + */ + lightbox.show = function(index) { + // Bail if we are already showing this item. + if (index === currentIndex) { + return; + } + + var item = items[index]; + var previousIndex = currentIndex; + currentIndex = index; + spinner.show(); + + // For videos, load an empty SVG with the video dimensions to preserve + // the video’s aspect ratio while being responsive. + var url = item.html && svgDataUri(item.width, item.height) || item.url; + loadImage(url, function($image) { + // Make sure this is the last item requested to be shown since + // images can finish loading in a different order than they were + // requested in. + if (index != currentIndex) { + return; + } + + var $figure = dom('figure', 'figure').append(addClass($image, 'image')); + var $frame = dom('frame').append($figure); + var $newView = dom('view').append($frame); + + if (item.html) { + $figure.append(addClass($(item.html), 'embed')); + } + + if (item.caption) { + $figure.append(dom('caption', 'figcaption').text(item.caption)); + } + + spinner.hide(); + + toggleClass($refs.arrowLeft, 'inactive', index <= 0); + toggleClass($refs.arrowRight, 'inactive', index >= items.length - 1); + + $refs.spinner.before($newView); + + if ($refs.view) { + tram($refs.view) + .add('opacity .3s') + .start({ + opacity: 0 + }) + .then(remover($refs.view)); + + tram($newView) + .add('opacity .3s') + .add('transform .3s') + .set({ + opacity: 0, + x: index > previousIndex ? '80px' : '-80px' + }) + .start({ + opacity: 1, + x: 0 + }); + } + + $refs.view = $newView; + + if ($refs.items) { + // Mark proper thumbnail as active + addClass(removeClass($refs.items, 'active').eq(index), 'active'); + } + }); + + return lightbox; + }; + + /** + * Hides the lightbox. + */ + lightbox.hide = function() { + tram($refs.lightbox) + .add('opacity .3s') + .start({ + opacity: 0 + }) + .then(hideLightbox); + + return lightbox; + }; + + lightbox.prev = function() { + if (currentIndex > 0) { + lightbox.show(currentIndex - 1); + } + }; + + lightbox.next = function() { + if (currentIndex < items.length - 1) { + lightbox.show(currentIndex + 1); + } + }; + + function toggleControlsOr(callback) { + return function(event) { + // We only care about events triggered directly on the bound selectors. + if (this != event.target) { + return; + } + + event.stopPropagation(); + event.preventDefault(); + + if (matchMedia(breakpoint).matches) { + callback(); + } else { + toggleClass($refs.controls, 'visible'); + } + }; + } + + var itemTapHandler = function(event) { + var index = $(this).index(); + + event.preventDefault(); + lightbox.show(index); + }; + + var swipeHandler = function(event, data) { + // Prevent scrolling. + event.preventDefault(); + + if (data.direction == 'left') { + lightbox.next(); + } else if (data.direction == 'right') { + lightbox.prev(); + } + }; + + function preventDefaultAnd(action) { + return function(event) { + // Prevents click events and zooming. + event.preventDefault(); + action(); + }; + } + + var focusThis = function() { + this.focus(); + }; + + function preventDefault(event) { + event.preventDefault(); + } + + function keyHandler(event) { + var keyCode = event.keyCode; + + // [esc] + if (keyCode == 27) { + lightbox.hide(); + } + + // [◀] + else if (keyCode == 37) { + lightbox.prev(); + } + + // [▶] + else if (keyCode == 39) { + lightbox.next(); + } + } + + function hideLightbox() { + removeClass($refs.html, 'noscroll'); + addClass($refs.lightbox, 'hide'); + $refs.strip.empty(); + $refs.view && $refs.view.remove(); + + // Reset some stuff + removeClass($refs.content, 'group'); + removeClass($refs.controls, 'visible'); + addClass($refs.arrowLeft, 'inactive'); + addClass($refs.arrowRight, 'inactive'); + + currentIndex = $refs.view = undefined; + } + + function loadImage(url, callback) { + var $image = dom('img', 'img'); + + $image.one('load', function() { + callback($image); + }); + + // Start loading image. + $image.attr('src', url); + + return $image; + } + + function remover($element) { + return function() { + $element.remove(); + }; + } + + /** + * Spinner + */ + function Spinner($spinner, className, delay) { + this.$element = $spinner; + this.className = className; + this.delay = delay || 200; + this.hide(); + } + + Spinner.prototype.show = function() { + var spinner = this; + + // Bail if we are already showing the spinner. + if (spinner.timeoutId) { + return; + } + + spinner.timeoutId = setTimeout(function() { + spinner.$element.removeClass(spinner.className); + delete spinner.timeoutId; + }, spinner.delay); + }; + + Spinner.prototype.hide = function() { + var spinner = this; + if (spinner.timeoutId) { + clearTimeout(spinner.timeoutId); + delete spinner.timeoutId; + return; + } + + spinner.$element.addClass(spinner.className); + }; + + function prefixed(string, isSelector) { + return string.replace(prefixRegex, (isSelector ? ' .' : ' ') + prefix); + } + + function selector(string) { + return prefixed(string, true); + } + + /** + * jQuery.addClass with auto-prefixing + * @param {jQuery} Element to add class to + * @param {string} Class name that will be prefixed and added to element + * @return {jQuery} + */ + function addClass($element, className) { + return $element.addClass(prefixed(className)); + } + + /** + * jQuery.removeClass with auto-prefixing + * @param {jQuery} Element to remove class from + * @param {string} Class name that will be prefixed and removed from element + * @return {jQuery} + */ + function removeClass($element, className) { + return $element.removeClass(prefixed(className)); + } + + /** + * jQuery.toggleClass with auto-prefixing + * @param {jQuery} Element where class will be toggled + * @param {string} Class name that will be prefixed and toggled + * @param {boolean} Optional boolean that determines if class will be added or removed + * @return {jQuery} + */ + function toggleClass($element, className, shouldAdd) { + return $element.toggleClass(prefixed(className), shouldAdd); + } + + /** + * Create a new DOM element wrapped in a jQuery object, + * decorated with our custom methods. + * @param {string} className + * @param {string} [tag] + * @return {jQuery} + */ + function dom(className, tag) { + return addClass($(document.createElement(tag || 'div')), className); + } + + function isObject(value) { + return typeof value == 'object' && null != value && !isArray(value); + } + + function svgDataUri(width, height) { + var svg = ''; + return 'data:image/svg+xml;charset=utf-8,' + encodeURI(svg); + } + + // Compute some dimensions manually for iOS, because of buggy support for VH. + // Also, Android built-in browser does not support viewport units. + (function() { + var ua = window.navigator.userAgent; + var iOS = /(iPhone|iPod|iPad).+AppleWebKit/i.test(ua); + var android = ua.indexOf('Android ') > -1 && ua.indexOf('Chrome') == -1; + + if (!iOS && !android) { + return; + } + + var styleNode = document.createElement('style'); + document.head.appendChild(styleNode); + window.addEventListener('orientationchange', refresh, true); + + function refresh() { + var vh = window.innerHeight; + var vw = window.innerWidth; + var content = + '.w-lightbox-content, .w-lightbox-view, .w-lightbox-view:before {' + + 'height:' + vh + 'px' + + '}' + + '.w-lightbox-view {' + + 'width:' + vw + 'px' + + '}' + + '.w-lightbox-group, .w-lightbox-group .w-lightbox-view, .w-lightbox-group .w-lightbox-view:before {' + + 'height:' + (0.86 * vh) + 'px' + + '}' + + '.w-lightbox-image {' + + 'max-width:' + vw + 'px;' + + 'max-height:' + vh + 'px' + + '}' + + '.w-lightbox-group .w-lightbox-image {' + + 'max-height:' + (0.86 * vh) + 'px' + + '}' + + '.w-lightbox-strip {' + + 'padding: 0 ' + (0.01 * vh) + 'px' + + '}' + + '.w-lightbox-item {' + + 'width:' + (0.1 * vh) + 'px;' + + 'padding:' + (0.02 * vh) + 'px ' + (0.01 * vh) + 'px' + + '}' + + '.w-lightbox-thumbnail {' + + 'height:' + (0.1 * vh) + 'px' + + '}'; + + styleNode.textContent = content; + } + + refresh(); + })(); + + return lightbox; +})(window, document, jQuery, window.tram); + +Webflow.define('lightbox', function($, _) { + 'use strict'; + + var api = {}; + var $doc = $(document); + var $body; + var $lightboxes; + var designer; + var inApp = Webflow.env(); + var namespace = '.w-lightbox'; + var groups; + + // ----------------------------------- + // Module methods + + api.ready = api.design = api.preview = init; + + // ----------------------------------- + // Private methods + + function init() { + designer = inApp && Webflow.env('design'); + $body = $(document.body); + + // Reset Lightbox + lightbox.destroy(); + + // Reset groups + groups = {}; + + // Find all instances on the page + $lightboxes = $doc.find(namespace); + $lightboxes.each(build); + } + + function build(i, el) { + var $el = $(el); + + // Store state in data + var data = $.data(el, namespace); + if (!data) data = $.data(el, namespace, { + el: $el, + mode: 'images', + images: [], + embed: '' + }); + + // Remove old events + data.el.off(namespace); + + // Set config from json script tag + configure(data); + + // Add events based on mode + if (designer) { + data.el.on('setting' + namespace, configure.bind(null, data)); + } else { + data.el + .on('tap' + namespace, tapHandler(data)) + // Prevent page scrolling to top when clicking on lightbox triggers. + .on('click' + namespace, function(e) { + e.preventDefault(); + }); + } + } + + function configure(data) { + var json = data.el.children('.w-json').html(); + var groupId, group; + + if (!json) { + data.images = []; + return; + } + + try { + json = JSON.parse(json); + data.mode = json.mode; + + if (json.mode == 'video') { + data.embed = json.embed; + } else { + groupId = json.groupId; + if (groupId) { + group = groups[groupId]; + if (!group) { + group = groups[groupId] = []; + } + + data.images = group; + + if (json.images.length) { + data.index = group.length; + group.push.apply(group, json.images); + } + } else { + data.images = json.images; + } + } + } catch (e) { + console.error('Malformed lightbox JSON configuration.', e.message); + } + } + + function tapHandler(data) { + return function() { + if (data.mode == 'video') { + data.embed && lightbox(data.embed); + } else { + data.images.length && lightbox(data.images, data.index || 0); + } + }; + } + + // Export module + return api; +}); +/** + * ---------------------------------------------------------------------- + * Webflow: Navbar component + */ +Webflow.define('navbar', function($, _) { + 'use strict'; + + var api = {}; + var tram = window.tram; + var $win = $(window); + var $doc = $(document); + var $body; + var $navbars; + var designer; + var inApp = Webflow.env(); + var overlay = '
'; + var namespace = '.w-nav'; + var buttonOpen = 'w--open'; + var menuOpen = 'w--nav-menu-open'; + var linkOpen = 'w--nav-link-open'; + var ix = Webflow.ixEvents(); + + // ----------------------------------- + // Module methods + + api.ready = api.design = api.preview = init; + api.destroy = removeListeners; + + // ----------------------------------- + // Private methods + + function init() { + designer = inApp && Webflow.env('design'); + $body = $(document.body); + + // Find all instances on the page + $navbars = $doc.find(namespace); + if (!$navbars.length) return; + $navbars.each(build); + + // Wire events + removeListeners(); + addListeners(); + } + + function removeListeners() { + Webflow.resize.off(resizeAll); + } + + function addListeners() { + Webflow.resize.on(resizeAll); + } + + function resizeAll() { + $navbars.each(resize); + } + + function build(i, el) { + var $el = $(el); + + // Store state in data + var data = $.data(el, namespace); + if (!data) data = $.data(el, namespace, { + open: false, + el: $el, + config: {} + }); + data.menu = $el.find('.w-nav-menu'); + data.links = data.menu.find('.w-nav-link'); + data.dropdowns = data.menu.find('.w-dropdown'); + data.button = $el.find('.w-nav-button'); + data.container = $el.find('.w-container'); + data.outside = outside(data); + + // Remove old events + data.el.off(namespace); + data.button.off(namespace); + data.menu.off(namespace); + + // Set config from data attributes + configure(data); + + // Add events based on mode + if (designer) { + removeOverlay(data); + data.el.on('setting' + namespace, handler(data)); + } else { + addOverlay(data); + data.button.on('tap' + namespace, toggle(data)); + data.menu.on('tap' + namespace, 'a', navigate(data)); + } + + // Trigger initial resize + resize(i, el); + } + + function removeOverlay(data) { + if (!data.overlay) return; + close(data, true); + data.overlay.remove(); + data.overlay = null; + } + + function addOverlay(data) { + if (data.overlay) return; + data.overlay = $(overlay).appendTo(data.el); + data.parent = data.menu.parent(); + close(data, true); + } + + function configure(data) { + var config = {}; + var old = data.config || {}; + + // Set config options from data attributes + var animation = config.animation = data.el.attr('data-animation') || 'default'; + config.animOver = /^over/.test(animation); + config.animDirect = /left$/.test(animation) ? -1 : 1; + + // Re-open menu if the animation type changed + if (old.animation != animation) { + data.open && _.defer(reopen, data); + } + + config.easing = data.el.attr('data-easing') || 'ease'; + config.easing2 = data.el.attr('data-easing2') || 'ease'; + + var duration = data.el.attr('data-duration'); + config.duration = duration != null ? +duration : 400; + + config.docHeight = data.el.attr('data-doc-height'); + + // Store config in data + data.config = config; + } + + function handler(data) { + return function(evt, options) { + options = options || {}; + var winWidth = $win.width(); + configure(data); + options.open === true && open(data, true); + options.open === false && close(data, true); + // Reopen if media query changed after setting + data.open && _.defer(function() { + if (winWidth != $win.width()) reopen(data); + }); + }; + } + + function closeEach(i, el) { + var data = $.data(el, namespace); + data.open && close(data); + } + + function reopen(data) { + if (!data.open) return; + close(data, true); + open(data, true); + } + + function toggle(data) { + return _.debounce(function(evt) { + data.open ? close(data) : open(data); + }); + } + + function navigate(data) { + return function(evt) { + var link = $(this); + var href = link.attr('href'); + + // Close when navigating to an in-page anchor + if (href && href.indexOf('#') === 0 && data.open) { + // Avoid empty hash links + if (href.length === 1) evt.preventDefault(); + close(data); + } + }; + } + + function outside(data) { + // Unbind previous tap handler if it exists + if (data.outside) $doc.off('tap' + namespace, data.outside); + + // Close menu when tapped outside + return _.debounce(function(evt) { + if (!data.open) return; + var menu = $(evt.target).closest('.w-nav-menu'); + if (!data.menu.is(menu)) { + close(data); + } + }); + } + + function resize(i, el) { + var data = $.data(el, namespace); + // Check for collapsed state based on button display + var collapsed = data.collapsed = data.button.css('display') != 'none'; + // Close menu if button is no longer visible (and not in designer) + if (data.open && !collapsed && !designer) close(data, true); + // Set max-width of links + dropdowns to match container + if (data.container.length) { + var updateEachMax = updateMax(data); + data.links.each(updateEachMax); + data.dropdowns.each(updateEachMax); + } + // If currently open, update height to match body + if (data.open) { + setOverlayHeight(data); + } + } + + var maxWidth = 'max-width'; + + function updateMax(data) { + // Set max-width of each element to match container + var containMax = data.container.css(maxWidth); + if (containMax == 'none') containMax = ''; + return function(i, link) { + link = $(link); + link.css(maxWidth, ''); + // Don't set the max-width if an upstream value exists + if (link.css(maxWidth) == 'none') link.css(maxWidth, containMax); + }; + } + + function open(data, immediate) { + if (data.open) return; + data.open = true; + data.menu.addClass(menuOpen); + data.links.addClass(linkOpen); + data.button.addClass(buttonOpen); + var config = data.config; + var animation = config.animation; + if (animation == 'none' || !tram.support.transform) immediate = true; + var bodyHeight = setOverlayHeight(data); + var menuHeight = data.menu.outerHeight(true); + var menuWidth = data.menu.outerWidth(true); + var navHeight = data.el.height(); + var navbarEl = data.el[0]; + resize(0, navbarEl); + ix.intro(0, navbarEl); + + // Listen for tap outside events + if (!designer) $doc.on('tap' + namespace, data.outside); + + // No transition for immediate + if (immediate) return; + + var transConfig = 'transform ' + config.duration + 'ms ' + config.easing; + + // Add menu to overlay + if (data.overlay) { + data.overlay.show().append(data.menu); + } + + // Over left/right + if (config.animOver) { + tram(data.menu) + .add(transConfig) + .set({ + x: config.animDirect * menuWidth, + height: bodyHeight + }).start({ + x: 0 + }); + data.overlay && data.overlay.width(menuWidth); + return; + } + + // Drop Down + var offsetY = navHeight + menuHeight; + tram(data.menu) + .add(transConfig) + .set({ + y: -offsetY + }).start({ + y: 0 + }); + } + + function setOverlayHeight(data) { + var config = data.config; + var bodyHeight = config.docHeight ? $doc.height() : $body.height(); + if (config.animOver) { + data.menu.height(bodyHeight); + } else if (data.el.css('position') != 'fixed') { + bodyHeight -= data.el.height(); + } + data.overlay && data.overlay.height(bodyHeight); + return bodyHeight; + } + + function close(data, immediate) { + if (!data.open) return; + data.open = false; + data.button.removeClass(buttonOpen); + var config = data.config; + if (config.animation == 'none' || !tram.support.transform) immediate = true; + var animation = config.animation; + ix.outro(0, data.el[0]); + + // Stop listening for tap outside events + $doc.off('tap' + namespace, data.outside); + + if (immediate) { + tram(data.menu).stop(); + complete(); + return; + } + + var transConfig = 'transform ' + config.duration + 'ms ' + config.easing2; + var menuHeight = data.menu.outerHeight(true); + var menuWidth = data.menu.outerWidth(true); + var navHeight = data.el.height(); + + // Over left/right + if (config.animOver) { + tram(data.menu) + .add(transConfig) + .start({ + x: menuWidth * config.animDirect + }).then(complete); + return; + } + + // Drop Down + var offsetY = navHeight + menuHeight; + tram(data.menu) + .add(transConfig) + .start({ + y: -offsetY + }).then(complete); + + function complete() { + data.menu.height(''); + tram(data.menu).set({ + x: 0, + y: 0 + }); + data.menu.removeClass(menuOpen); + data.links.removeClass(linkOpen); + if (data.overlay && data.overlay.children().length) { + // Move menu back to parent + data.menu.appendTo(data.parent); + data.overlay.attr('style', '').hide(); + } + + // Trigger event so other components can hook in (dropdown) + data.el.triggerHandler('w-close'); + } + } + + // Export module + return api; +}); +/** + * ---------------------------------------------------------------------- + * Webflow: Dropdown component + */ +Webflow.define('dropdown', function($, _) { + 'use strict'; + + var api = {}; + var tram = window.tram; + var $doc = $(document); + var $dropdowns; + var designer; + var inApp = Webflow.env(); + var namespace = '.w-dropdown'; + var stateOpen = 'w--open'; + var closeEvent = 'w-close' + namespace; + var ix = Webflow.ixEvents(); + + // ----------------------------------- + // Module methods + + api.ready = api.design = api.preview = init; + + // ----------------------------------- + // Private methods + + function init() { + designer = inApp && Webflow.env('design'); + + // Find all instances on the page + $dropdowns = $doc.find(namespace); + $dropdowns.each(build); + } + + function build(i, el) { + var $el = $(el); + + // Store state in data + var data = $.data(el, namespace); + if (!data) data = $.data(el, namespace, { + open: false, + el: $el, + config: {} + }); + data.list = $el.children('.w-dropdown-list'); + data.toggle = $el.children('.w-dropdown-toggle'); + data.links = data.list.children('.w-dropdown-link'); + data.outside = outside(data); + data.complete = complete(data); + + // Remove old events + $el.off(namespace); + data.toggle.off(namespace); + + // Set config from data attributes + configure(data); + + if (data.nav) data.nav.off(namespace); + data.nav = $el.closest('.w-nav'); + data.nav.on(closeEvent, handler(data)); + + // Add events based on mode + if (designer) { + $el.on('setting' + namespace, handler(data)); + } else { + data.toggle.on('tap' + namespace, toggle(data)); + $el.on(closeEvent, handler(data)); + // Close in preview mode + inApp && close(data); + } + } + + function configure(data) { + data.config = { + hover: +data.el.attr('data-hover'), + delay: +data.el.attr('data-delay') || 0 + }; + } + + function handler(data) { + return function(evt, options) { + options = options || {}; + + if (evt.type == 'w-close') { + return close(data); + } + + if (evt.type == 'setting') { + configure(data); + options.open === true && open(data, true); + options.open === false && close(data, true); + return; + } + }; + } + + function toggle(data) { + return _.debounce(function(evt) { + data.open ? close(data) : open(data); + }); + } + + function open(data, immediate) { + if (data.open) return; + closeOthers(data); + data.open = true; + data.list.addClass(stateOpen); + data.toggle.addClass(stateOpen); + ix.intro(0, data.el[0]); + + // Listen for tap outside events + if (!designer) $doc.on('tap' + namespace, data.outside); + + // Clear previous delay + window.clearTimeout(data.delayId); + } + + function close(data, immediate) { + if (!data.open) return; + data.open = false; + var config = data.config; + ix.outro(0, data.el[0]); + + // Stop listening for tap outside events + $doc.off('tap' + namespace, data.outside); + + // Clear previous delay + window.clearTimeout(data.delayId); + + // Skip delay during immediate + if (!config.delay || immediate) return data.complete(); + + // Optionally wait for delay before close + data.delayId = window.setTimeout(data.complete, config.delay); + } + + function closeOthers(data) { + var self = data.el[0]; + $dropdowns.each(function(i, other) { + var $other = $(other); + if ($other.is(self) || $other.has(self).length) return; + $other.triggerHandler(closeEvent); + }); + } + + function outside(data) { + // Unbind previous tap handler if it exists + if (data.outside) $doc.off('tap' + namespace, data.outside); + + // Close menu when tapped outside + return _.debounce(function(evt) { + if (!data.open) return; + var $target = $(evt.target); + if ($target.closest('.w-dropdown-toggle').length) return; + if (!data.el.is($target.closest(namespace))) { + close(data); + } + }); + } + + function complete(data) { + return function() { + data.list.removeClass(stateOpen); + data.toggle.removeClass(stateOpen); + }; + } + + // Export module + return api; +}); +/** + * ---------------------------------------------------------------------- + * Webflow: Tabs component + */ +Webflow.define('tabs', function($, _) { + 'use strict'; + + var api = {}; + var tram = window.tram; + var $win = $(window); + var $doc = $(document); + var $tabs; + var design; + var env = Webflow.env; + var safari = env.safari; + var inApp = env(); + var tabAttr = 'data-w-tab'; + var namespace = '.w-tabs'; + var linkCurrent = 'w--current'; + var tabActive = 'w--tab-active'; + var ix = Webflow.ixEvents(); + + // ----------------------------------- + // Module methods + + api.ready = api.design = api.preview = init; + + // ----------------------------------- + // Private methods + + function init() { + design = inApp && Webflow.env('design'); + + // Find all instances on the page + $tabs = $doc.find(namespace); + if (!$tabs.length) return; + $tabs.each(build); + } + + function build(i, el) { + var $el = $(el); + + // Store state in data + var data = $.data(el, namespace); + if (!data) data = $.data(el, namespace, { + el: $el, + config: {} + }); + data.current = null; + data.menu = $el.children('.w-tab-menu'); + data.links = data.menu.children('.w-tab-link'); + data.content = $el.children('.w-tab-content'); + data.panes = data.content.children('.w-tab-pane'); + + // Remove old events + data.el.off(namespace); + data.links.off(namespace); + + // Set config from data attributes + configure(data); + + // Wire up events when not in design mode + if (!design) { + data.links.on('click' + namespace, linkSelect(data)); + + // Trigger first intro event from current tab + var $link = data.links.filter('.' + linkCurrent); + var tab = $link.attr(tabAttr); + tab && changeTab(data, { + tab: tab, + immediate: true + }); + } + } + + function configure(data) { + var config = {}; + var old = data.config || {}; + + // Set config options from data attributes + config.easing = data.el.attr('data-easing') || 'ease'; + + var intro = +data.el.attr('data-duration-in'); + intro = config.intro = intro === intro ? intro : 0; + + var outro = +data.el.attr('data-duration-out'); + outro = config.outro = outro === outro ? outro : 0; + + config.immediate = !intro && !outro; + + // Store config in data + data.config = config; + } + + function linkSelect(data) { + return function(evt) { + var tab = evt.currentTarget.getAttribute(tabAttr); + tab && changeTab(data, { + tab: tab + }); + }; + } + + function changeTab(data, options) { + options = options || {}; + + var config = data.config; + var easing = config.easing; + var tab = options.tab; + + // Don't select the same tab twice + if (tab === data.current) return; + data.current = tab; + + // Select the current link + data.links.each(function(i, el) { + var $el = $(el); + if (el.getAttribute(tabAttr) === tab) $el.addClass(linkCurrent).each(ix.intro); + else if ($el.hasClass(linkCurrent)) $el.removeClass(linkCurrent).each(ix.outro); + }); + + // Find the new tab panes and keep track of previous + var targets = []; + var previous = []; + data.panes.each(function(i, el) { + var $el = $(el); + if (el.getAttribute(tabAttr) === tab) { + targets.push(el); + } else if ($el.hasClass(tabActive)) { + previous.push(el); + } + }); + + var $targets = $(targets); + var $previous = $(previous); + + // Switch tabs immediately and bypass transitions + if (options.immediate || config.immediate) { + $targets.addClass(tabActive).each(ix.intro); + $previous.removeClass(tabActive); + Webflow.redraw.up(); + return; + } + + // Fade out the currently active tab before intro + if ($previous.length && config.outro) { + $previous.each(ix.outro); + tram($previous) + .add('opacity ' + config.outro + 'ms ' + easing, { + fallback: safari + }) + .start({ + opacity: 0 + }) + .then(intro); + } else { + // Skip the outro and play intro + intro(); + } + + // Fade in the new target + function intro() { + // Clear previous active class + inline style + $previous.removeClass(tabActive).removeAttr('style'); + + // Add active class to new target + $targets.addClass(tabActive).each(ix.intro); + Webflow.redraw.up(); + + // Set opacity immediately if intro is zero + if (!config.intro) return tram($targets).set({ + opacity: 1 + }); + + // Otherwise fade in opacity + tram($targets) + .set({ + opacity: 0 + }) + .redraw() + .add('opacity ' + config.intro + 'ms ' + easing, { + fallback: safari + }) + .start({ + opacity: 1 + }); + } + } + + // Export module + return api; +}); \ No newline at end of file -- cgit v1.2.3