|
/*! |
|
* ---------------------------------------------------------------------- |
|
* 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; |
|
if (mode == 'editor') return window.WebflowEditor; |
|
}; |
|
|
|
// 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 = /chrome/.test(userAgent) && /Google/.test(navigator.vendor) && 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.load() - Add a window load handler that will run even if load event has already happened |
|
* @param {function} handler |
|
*/ |
|
var deferLoad; |
|
api.load = function(handler) { |
|
deferLoad.then(handler); |
|
}; |
|
|
|
function bindLoad() { |
|
// Reject any previous deferred (to support destroy) |
|
if (deferLoad) { |
|
deferLoad.reject(); |
|
$win.off('load', deferLoad.resolve); |
|
} |
|
// Create deferred and bind window load event |
|
deferLoad = new $.Deferred(); |
|
$win.on('load', deferLoad.resolve); |
|
} |
|
|
|
// Webflow.destroy() - Trigger a cleanup event for all modules |
|
api.destroy = function() { |
|
$win.triggerHandler('__wf_destroy'); |
|
// If load event has not yet fired, replace the deferred |
|
if (deferLoad.state() == 'pending') bindLoad(); |
|
}; |
|
|
|
// Listen for domready |
|
$(api.ready); |
|
|
|
// Listen for window.onload and resolve deferred |
|
bindLoad(); |
|
|
|
/*! |
|
* 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.3 - 2014-06-06 |
|
* https://github.com/MoonScript/jQuery-ajaxTransport-XDomainRequest |
|
* Copyright (c) 2014 Jason Moon (@JSONMOON) |
|
* Licensed MIT (/blob/master/LICENSE.txt) |
|
*/ |
|
(function(a){if(typeof define==='function'&&define.amd){define(['jquery'],a)}else if(typeof exports==='object'){module.exports=a(require('jquery'))}else{a(jQuery)}}(function($){if($.support.cors||!$.ajaxTransport||!window.XDomainRequest){return}var n=/^https?:\/\//i;var o=/^get|post$/i;var p=new RegExp('^'+location.protocol,'i');$.ajaxTransport('* text html xml json',function(j,k,l){if(!j.crossDomain||!j.async||!o.test(j.type)||!n.test(j.url)||!p.test(j.url)){return}var m=null;return{send:function(f,g){var h='';var i=(k.dataType||'').toLowerCase();m=new XDomainRequest();if(/^\d+$/.test(k.timeout)){m.timeout=k.timeout}m.ontimeout=function(){g(500,'timeout')};m.onload=function(){var a='Content-Length: '+m.responseText.length+'\r\nContent-Type: '+m.contentType;var b={code:200,message:'success'};var c={text:m.responseText};try{if(i==='html'||/text\/html/i.test(m.contentType)){c.html=m.responseText}else if(i==='json'||(i!=='text'&&/\/json/i.test(m.contentType))){try{c.json=$.parseJSON(m.responseText)}catch(e){b.code=500;b.message='parseerror'}}else if(i==='xml'||(i!=='text'&&/\/xml/i.test(m.contentType))){var d=new ActiveXObject('Microsoft.XMLDOM');d.async=false;try{d.loadXML(m.responseText)}catch(e){d=undefined}if(!d||!d.documentElement||d.getElementsByTagName('parsererror').length){b.code=500;b.message='parseerror';throw'Invalid XML: '+m.responseText;}c.xml=d}}catch(parseMessage){throw parseMessage;}finally{g(b.code,b.message,c,a)}};m.onprogress=function(){};m.onerror=function(){g(500,'error',{text:m.responseText})};if(k.data){h=($.type(k.data)==='string')?k.data:$.param(k.data)}m.open(j.type,j.url);m.send(h)},abort:function(){if(m){m.abort()}}}})})); |
|
/* 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 destroyed; |
|
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() { configure(list); }, 1); |
|
}; |
|
|
|
api.preview = function() { |
|
designer = false; |
|
setTimeout(function() { configure(window.__wf_ix); }, 1); |
|
}; |
|
|
|
api.design = function() { |
|
designer = true; |
|
api.destroy(); |
|
}; |
|
|
|
api.destroy = function() { |
|
destroyed = true; |
|
$subs.each(teardown); |
|
Webflow.scroll.off(scroll); |
|
asyncEvents(); |
|
anchors = []; |
|
loads = []; |
|
readys = []; |
|
}; |
|
|
|
api.ready = function() { |
|
// Ready should only be used after destroy, as a way to re-init |
|
if (config && destroyed) { |
|
destroyed = false; |
|
init(); |
|
} |
|
}; |
|
|
|
api.run = run; |
|
api.events = {}; |
|
api.style = inApp ? styleApp : stylePub; |
|
|
|
// ----------------------------------- |
|
// Private methods |
|
|
|
function configure(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; |
|
}); |
|
|
|
// Init ix after config |
|
init(); |
|
} |
|
|
|
function init() { |
|
// 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) Webflow.load(runLoads); |
|
if (readys.length) setTimeout(runReadys, 1); |
|
|
|
// Trigger queued events, must happen after init |
|
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; |
|
|
|
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 - without this IE9 and below forms won't submit |
|
if (retro && url.indexOf('https://webflow.com') >= 0) { |
|
url = url.replace('https://webflow.com/', 'http://formdata.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 [email protected] to fix this issue.'); |
|
}, 100); |
|
|
|
// Export module |
|
return api; |
|
}); |
|
/** |
|
* ---------------------------------------------------------------------- |
|
* Webflow: Maps widget |
|
*/ |
|
Webflow.define('maps', function($, _) { |
|
'use strict'; |
|
|
|
var api = {}; |
|
var $doc = $(document); |
|
var google = null; |
|
var $maps; |
|
var namespace = '.w-widget-map'; |
|
|
|
// ----------------------------------- |
|
// Module methods |
|
|
|
api.ready = function() { |
|
// Init Maps on the front-end |
|
if (!Webflow.env()) initMaps(); |
|
}; |
|
|
|
api.preview = function() { |
|
// Update active map nodes |
|
$maps = $doc.find(namespace); |
|
// Listen for resize events |
|
Webflow.resize.off(triggerRedraw); |
|
if ($maps.length) { |
|
Webflow.resize.on(triggerRedraw); |
|
triggerRedraw(); |
|
} |
|
}; |
|
|
|
api.design = function(evt) { |
|
// Update active map nodes |
|
$maps = $doc.find(namespace); |
|
// Stop listening for resize events |
|
Webflow.resize.off(triggerRedraw); |
|
// Redraw to account for page changes |
|
$maps.length && _.defer(triggerRedraw); |
|
}; |
|
|
|
api.destroy = removeListeners; |
|
|
|
// ----------------------------------- |
|
// Private methods |
|
|
|
// Trigger redraw in designer or preview mode |
|
function triggerRedraw() { |
|
if ($maps.length && Webflow.app) { |
|
$maps.each(Webflow.app.redrawElement); |
|
} |
|
} |
|
|
|
function initMaps() { |
|
$maps = $doc.find(namespace); |
|
if (!$maps.length) return; |
|
|
|
if (google === null) { |
|
$.getScript('https://maps.googleapis.com/maps/api/js?v=3.exp&sensor=false&callback=_wf_maps_loaded'); |
|
window._wf_maps_loaded = mapsLoaded; |
|
} else { |
|
mapsLoaded(); |
|
} |
|
|
|
function mapsLoaded() { |
|
window._wf_maps_loaded = function() {}; |
|
google = window.google; |
|
$maps.each(renderMap); |
|
removeListeners(); |
|
addListeners(); |
|
} |
|
} |
|
|
|
function removeListeners() { |
|
Webflow.resize.off(resizeMaps); |
|
Webflow.redraw.off(resizeMaps); |
|
} |
|
|
|
function addListeners() { |
|
Webflow.resize.on(resizeMaps); |
|
Webflow.redraw.on(resizeMaps); |
|
} |
|
|
|
// Render map onto each element |
|
function renderMap(i, el) { |
|
var data = $(el).data(); |
|
getState(el, data); |
|
} |
|
|
|
function resizeMaps() { |
|
$maps.each(resizeMap); |
|
} |
|
|
|
// Resize map when window changes |
|
function resizeMap(i, el) { |
|
var state = getState(el); |
|
google.maps.event.trigger(state.map, 'resize'); |
|
state.setMapPosition(); |
|
} |
|
|
|
// Store state on element data |
|
var store = 'w-widget-map'; |
|
function getState(el, data) { |
|
|
|
var state = $.data(el, store); |
|
if (state) return state; |
|
|
|
var $el = $(el); |
|
state = $.data(el, store, { |
|
// Default options |
|
latLng: '51.511214,-0.119824', |
|
tooltip: '', |
|
style: 'roadmap', |
|
zoom: 12, |
|
|
|
// Marker |
|
marker: new google.maps.Marker({ |
|
draggable: false |
|
}), |
|
|
|
// Tooltip infowindow |
|
infowindow: new google.maps.InfoWindow({ |
|
disableAutoPan: true |
|
}) |
|
}); |
|
|
|
// LatLng center point |
|
var latLng = data.widgetLatlng || state.latLng; |
|
state.latLng = latLng; |
|
var coords = latLng.split(','); |
|
var latLngObj = new google.maps.LatLng(coords[0], coords[1]); |
|
state.latLngObj = latLngObj; |
|
|
|
// Disable touch events |
|
var mapDraggable = (Webflow.env.touch && data.disableTouch) ? false : true; |
|
|
|
// Map instance |
|
state.map = new google.maps.Map(el, { |
|
center: state.latLngObj, |
|
zoom: state.zoom, |
|
maxZoom: 18, |
|
mapTypeControl: false, |
|
panControl: false, |
|
streetViewControl: false, |
|
scrollwheel: !data.disableScroll, |
|
draggable: mapDraggable, |
|
zoomControl: true, |
|
zoomControlOptions: { |
|
style: google.maps.ZoomControlStyle.SMALL |
|
}, |
|
mapTypeId: state.style |
|
}); |
|
state.marker.setMap(state.map); |
|
|
|
// Set map position and offset |
|
state.setMapPosition = function() { |
|
state.map.setCenter(state.latLngObj); |
|
var offsetX = 0; |
|
var offsetY = 0; |
|
var padding = $el.css(['paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft']); |
|
offsetX -= parseInt(padding.paddingLeft, 10); |
|
offsetX += parseInt(padding.paddingRight, 10); |
|
offsetY -= parseInt(padding.paddingTop, 10); |
|
offsetY += parseInt(padding.paddingBottom, 10); |
|
if (offsetX || offsetY) { |
|
state.map.panBy(offsetX, offsetY); |
|
} |
|
$el.css('position', ''); // Remove injected position |
|
}; |
|
|
|
// Fix position after first tiles have loaded |
|
google.maps.event.addListener(state.map, 'tilesloaded', function() { |
|
google.maps.event.clearListeners(state.map, 'tilesloaded'); |
|
state.setMapPosition(); |
|
}); |
|
|
|
// Set initial position |
|
state.setMapPosition(); |
|
state.marker.setPosition(state.latLngObj); |
|
state.infowindow.setPosition(state.latLngObj); |
|
|
|
// Draw tooltip |
|
var tooltip = data.widgetTooltip; |
|
if (tooltip) { |
|
state.tooltip = tooltip; |
|
state.infowindow.setContent(tooltip); |
|
if (!state.infowindowOpen) { |
|
state.infowindow.open(state.map, state.marker); |
|
state.infowindowOpen = true; |
|
} |
|
} |
|
|
|
// Map style - options.style |
|
var style = data.widgetStyle; |
|
if (style) { |
|
state.map.setMapTypeId(style); |
|
} |
|
|
|
// Zoom - options.zoom |
|
var zoom = data.widgetZoom; |
|
if (zoom != null) { |
|
state.zoom = zoom; |
|
state.map.setZoom(+zoom); |
|
} |
|
|
|
// Click marker to open in google maps |
|
google.maps.event.addListener(state.marker, 'click', function() { |
|
window.open('https://maps.google.com/?z=' + state.zoom + '&daddr=' + state.latLng); |
|
}); |
|
|
|
return state; |
|
} |
|
|
|
// Export module |
|
return api; |
|
}); |
|
/** |
|
* ---------------------------------------------------------------------- |
|
* Webflow: Google+ widget |
|
*/ |
|
Webflow.define('gplus', function($) { |
|
'use strict'; |
|
|
|
var $doc = $(document); |
|
var api = {}; |
|
var loaded; |
|
|
|
api.ready = function() { |
|
// Load Google+ API on the front-end |
|
if (!Webflow.env() && !loaded) init(); |
|
}; |
|
|
|
function init() { |
|
$doc.find('.w-widget-gplus').length && load(); |
|
} |
|
|
|
function load() { |
|
loaded = true; |
|
$.getScript('https://apis.google.com/js/plusone.js'); |
|
} |
|
|
|
// 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; |
|
|
|
// Ignore empty # links |
|
if (this.getAttribute('href') === '#') { |
|
e.preventDefault(); |
|
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; |
|
} |
|
|
|
// Ignore empty # links |
|
if (href === '#') 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 = '<div class="w-slider-dot" data-wf-ignore />'; |
|
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; |
|
var maskWidth = data.maskWidth; |
|
var threshold = maskWidth - data.config.edge; |
|
if (threshold < 0) threshold = 0; |
|
data.anchors = [{ els: [], x: 0, width: 0 }]; |
|
data.slides.each(function(i, el) { |
|
if (anchor - offset > threshold) { |
|
pages++; |
|
offset += 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; |
|
|
|
// 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.thumbnailUrl || 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.spinner = dom('spinner'); |
|
$refs.strip = dom('strip'); |
|
|
|
spinner = new Spinner($refs.spinner, prefixed('hide')); |
|
|
|
$refs.content = dom('content') |
|
.append($refs.spinner, $refs.arrowLeft, $refs.arrowRight, $refs.close); |
|
|
|
$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'), handlerPrev) |
|
.on('tap', selector('right'), handlerNext) |
|
.on('tap', selector('close'), handlerHide) |
|
.on('tap', selector('image, caption'), handlerNext); |
|
$refs.container |
|
.on('tap', selector('view, strip'), handlerHide) |
|
// Prevent images from being dragged around. |
|
.on('dragstart', selector('img'), preventDefault); |
|
$refs.lightbox |
|
.on('keydown', keyHandler) |
|
// IE loses focus to inner nodes without letting us know. |
|
.on('focusin', focusThis); |
|
|
|
// 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); |
|
var $html, isIframe; |
|
|
|
if (item.html) { |
|
$html = $(item.html); |
|
isIframe = $html.is('iframe'); |
|
|
|
if (isIframe) { |
|
$html.on('load', transitionToNewView); |
|
} |
|
|
|
$figure.append(addClass($html, 'embed')); |
|
} |
|
|
|
if (item.caption) { |
|
$figure.append(dom('caption', 'figcaption').text(item.caption)); |
|
} |
|
|
|
$refs.spinner.before($newView); |
|
|
|
if (!isIframe) { |
|
transitionToNewView(); |
|
} |
|
|
|
function transitionToNewView() { |
|
spinner.hide(); |
|
|
|
if (index != currentIndex) { |
|
$newView.remove(); |
|
return; |
|
} |
|
|
|
|
|
toggleClass($refs.arrowLeft, 'inactive', index <= 0); |
|
toggleClass($refs.arrowRight, 'inactive', index >= items.length - 1); |
|
|
|
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({x: index > previousIndex ? '80px' : '-80px'}) |
|
.start({opacity: 1, x: 0}); |
|
} |
|
else { |
|
$newView.css('opacity', 1); |
|
} |
|
|
|
$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 createHandler(action) { |
|
return function (event) { |
|
// We only care about events triggered directly on the bound selectors. |
|
if (this != event.target) { |
|
return; |
|
} |
|
|
|
event.stopPropagation(); |
|
event.preventDefault(); |
|
|
|
action(); |
|
}; |
|
} |
|
|
|
var handlerPrev = createHandler(lightbox.prev); |
|
var handlerNext = createHandler(lightbox.next); |
|
var handlerHide = createHandler(lightbox.hide); |
|
|
|
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(); |
|
} |
|
}; |
|
|
|
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'); |
|
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 = '<svg xmlns="http://www.w3.org/2000/svg" width="' + width + '" height="' + height + '"/>'; |
|
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' + |
|
'}' + |
|
'@media (min-width: 768px) {' + |
|
'.w-lightbox-content, .w-lightbox-view, .w-lightbox-view:before {' + |
|
'height:' + (0.96 * vh) + 'px' + |
|
'}' + |
|
'.w-lightbox-content {' + |
|
'margin-top:' + (0.02 * vh) + 'px' + |
|
'}' + |
|
'.w-lightbox-group, .w-lightbox-group .w-lightbox-view, .w-lightbox-group .w-lightbox-view:before {' + |
|
'height:' + (0.84 * vh) + 'px' + |
|
'}' + |
|
'.w-lightbox-image {' + |
|
'max-width:' + (0.96 * vw) + 'px;' + |
|
'max-height:' + (0.96 * vh) + 'px' + |
|
'}' + |
|
'.w-lightbox-group .w-lightbox-image {' + |
|
'max-width:' + (0.823 * vw) + 'px;' + |
|
'max-height:' + (0.84 * 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 groupName, groupItems; |
|
|
|
if (!json) { |
|
data.items = []; |
|
return; |
|
} |
|
|
|
try { |
|
json = JSON.parse(json); |
|
|
|
supportOldLightboxJson(json); |
|
|
|
groupName = json.group; |
|
|
|
if (groupName) { |
|
groupItems = groups[groupName]; |
|
if (!groupItems) { |
|
groupItems = groups[groupName] = []; |
|
} |
|
|
|
data.items = groupItems; |
|
|
|
if (json.items.length) { |
|
data.index = groupItems.length; |
|
groupItems.push.apply(groupItems, json.items); |
|
} |
|
} |
|
else { |
|
data.items = json.items; |
|
} |
|
} |
|
catch (e) { |
|
console.error('Malformed lightbox JSON configuration.', e.message); |
|
} |
|
} |
|
|
|
function tapHandler(data) { |
|
return function () { |
|
data.items.length && lightbox(data.items, data.index || 0); |
|
}; |
|
} |
|
|
|
function supportOldLightboxJson(data) { |
|
if (data.images) { |
|
data.images.forEach(function (item) { |
|
item.type = 'image'; |
|
}); |
|
data.items = data.images; |
|
} |
|
|
|
if (data.embed) { |
|
data.embed.type = 'video'; |
|
data.items = [data.embed]; |
|
} |
|
|
|
if (data.groupId) { |
|
data.group = data.groupId; |
|
} |
|
} |
|
|
|
// 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 = '<div class="w-nav-overlay" data-wf-ignore />'; |
|
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) { |
|
// Trigger click before tap closes menu |
|
link.trigger('click'); |
|
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); |
|
Webflow.redraw.up(); |
|
|
|
// 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]); |
|
Webflow.redraw.up(); |
|
|
|
// 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; |
|
}); |
|
/** |
|
* ---------------------------------------------------------------------- |
|
* Webflow: Interactions: Init |
|
*/ |
|
Webflow.require('ix').init([ |
|
{"slug":"rimspinner","name":"rimspinner","value":{"style":{},"triggers":[{"type":"load","loopA":true,"stepsA":[{"transition":"transform 600ms ease 0ms","rotate":"359deg"},{"rotate":"0deg"}],"stepsB":[]}]}}, |
|
{"slug":"fullrimspinner","name":"fullrimspinner","value":{"style":{},"triggers":[{"type":"load","loopA":true,"stepsA":[{"transition":"transform 600ms ease 0ms","rotate":"360deg"}],"stepsB":[]}]}} |
|
]); |