Ocean/Admin/Assets/JSWebflow.go

3775 lines
120 KiB
Go
Raw Normal View History

2015-06-22 12:08:29 +00:00
package Assets
var JSWebflow string = `
/*!
* ----------------------------------------------------------------------
* 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 $doc = $(document);
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();
var touch = 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;
// Maintain current touch target to prevent late clicks on touch devices
var touchTarget;
// Listen for both events to support touch/mouse hybrid devices
touch && $doc.on('touchstart mousedown', function(evt) {
touchTarget = evt.target;
});
/**
* Webflow.validClick() - validate click target against current touch target
* @param {HTMLElement} clickTarget Element being clicked
* @return {Boolean} True if click target is valid (always true on non-touch)
*/
api.validClick = touch ? function(clickTarget) {
return clickTarget === touchTarget || $.contains(clickTarget, touchTarget);
} : function() { return true; };
/**
* 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-cir
/*!
* 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 destroyed;
// 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;
// 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;
// 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 state = {};
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') {
$el.on('click' + namespace, function(evt) {
// Avoid late clicks on touch devices
if (!Webflow.validClick(evt.currentTarget)) return;
// Prevent default on empty hash urls
if ($el.attr('href') === '#') evt.preventDefault();
run(trigger, $el, { group: state.clicked ? 'B' : 'A' });
if (stepsB) state.clicked = !state.clicked;
});
$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 dont 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 FORM_API_HOST = 'https://webflow.com';
var FORM_SUBMIT_HOST = 'https://webflow.com';
var FORM_OLDIE_HOST = 'http://formdata.webflow.com';
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 = FORM_API_HOST + '/api/v1/form/' + siteId;
// Work around same-protocol IE XDR limitation - without this IE9 and below forms won't submit
if (retro && url.indexOf(FORM_SUBMIT_HOST) >= 0) {
url = url.replace(FORM_SUBMIT_HOST, FORM_OLDIE_HOST);
}
$.ajax({
url: url,
type: 'POST',
data: payload,
dataType: 'json',
crossDomain: true
}).done(function() {
data.success = true;
afterSubmit(data);
}).fail(function() {
afterSubmit(data);
});
}
// Submit form to MailChimp
function submitMailChimp(data) {
reset(data);
var form = data.form;
var payload = {};
// Skip Ajax submission if http/s mismatch, fallback to POST instead
if (/^https/.test(loc.href) && !/^https/.test(data.action)) {
form.attr('method', 'post');
return;
}
preventDefault(data);
// Find & populate all fields
var status = findFields(form, payload);
if (status) return alert(status);
// Disable submit button
disableBtn(data);
// Use special format for MailChimp params
var fullName;
_.each(payload, function(value, key) {
if (emailField.test(key)) payload.EMAIL = value;
if (/^((full[ _-]?)?name)$/i.test(key)) fullName = value;
if (/^(first[ _-]?name)$/i.test(key)) payload.FNAME = value;
if (/^(last[ _-]?name)$/i.test(key)) payload.LNAME = value;
});
if (fullName && !payload.FNAME) {
fullName = fullName.split(' ');
payload.FNAME = fullName[0];
payload.LNAME = payload.LNAME || fullName[1];
}
// Use the (undocumented) MailChimp jsonp api
var url = data.action.replace('/post?', '/post-json?') + '&c=?';
// Add special param to prevent bot signups
var userId = url.indexOf('u=')+2;
userId = url.substring(userId, url.indexOf('&', userId));
var listId = url.indexOf('id=')+3;
listId = url.substring(listId, url.indexOf('&', listId));
payload['b_' + userId + '_' + listId] = '';
$.ajax({
url: url,
data: payload,
dataType: 'jsonp'
}).done(function(resp) {
data.success = (resp.result == 'success' || /already/.test(resp.msg));
if (!data.success) console.info('MailChimp error: ' + resp.msg);
afterSubmit(data);
}).fail(function() {
afterSubmit(data);
});
}
// Common callback which runs after all Ajax submissions
function afterSubmit(data) {
var form = data.form;
var wrap = form.closest('div.w-form');
var redirect = data.redirect;
var success = data.success;
// Redirect to a success url if defined
if (success && redirect) {
Webflow.location(redirect);
return;
}
// Show or hide status divs
data.done.toggle(success);
data.fail.toggle(!success);
// Hide form on success
form.toggle(!success);
// Reset data and enable submit button
reset(data);
}
function preventDefault(data) {
data.evt && data.evt.preventDefault();
data.evt = null;
}
var disconnected = _.debounce(function() {
alert('Oops! This page has a form that is powered by Webflow, but important code was removed that is required to make the form work. Please contact support@webflow.com to fix this issue.');
}, 100);
// Export module
return api;
});
/**
* ----------------------------------------------------------------------
* Webflow: 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 = inIframe() ? null : win.history;
var validHash = /^[a-zA-Z][\w:.-]*$/;
function inIframe() {
try {
return !!win.frameElement;
} catch (e) {
return true;
}
}
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 tempLink = document.createElement('a');
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 = (designer && link.getAttribute('href-disabled')) || link.getAttribute('href');
tempLink.href = 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 = (tempLink.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 (Webflow.env('editor')) return;
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 videos 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('click' + 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 reopen(data) {
if (!data.open) return;
close(data, true);
open(data, true);
}
function toggle(data) {
// Debounce toggle to wait for accurate open state
return _.debounce(function(evt) {
data.open ? close(data) : open(data);
});
}
function navigate(data) {
return function(evt) {
var link = $(this);
var href = link.attr('href');
// Avoid late clicks on touch devices
if (!Webflow.validClick(evt.currentTarget)) {
evt.preventDefault();
return;
}
// Close when navigating to an in-page anchor
if (href && href.indexOf('#') === 0 && data.open) {
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, debounced to wait for state
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: Brand pages on the subdomain
*/
Webflow.define('branding', function($, _) {
'use strict';
var api = {};
var $html = $('html');
var $body = $('body');
var location = window.location;
var inApp = Webflow.env();
// -----------------------------------
// Module methods
api.ready = function() {
var doBranding = $html.attr("data-wf-status") && location.href.match(/webflow.com|webflowtest.com/);
if (doBranding) {
var $branding = $('<div></div>');
var $link = $('<a></a>');
$link.attr('href', 'http://webflow.com');
$branding.css({
position: 'fixed',
bottom: 0,
right: 0,
borderTop: '5px solid #2b3239',
borderLeft: '5px solid #2b3239',
borderTopLeftRadius: '5px',
backgroundColor: '#2b3239',
padding: '5px 5px 5px 10px',
fontFamily: 'Arial',
fontSize: '10px',
textTransform: 'uppercase',
opacity: '0',
transition: 'opacity 0.50s ease-in-out'
});
$link.css({
color: '#AAADB0',
textDecoration: 'none'
});
var $webflowLogo = $('<img>');
$webflowLogo.attr('src', 'https://daks2k3a4ib2z.cloudfront.net/54153e6a3d25f2755b1f14ed/5445a4b1944ecdaa4df86d3e_subdomain-brand.svg');
$webflowLogo.css({
opacity: 0.9,
width: '55px',
verticalAlign: 'middle',
paddingLeft: '4px',
paddingBottom: '3px'
});
$branding.text('Built with');
$branding.append($webflowLogo);
$link.append($branding);
$body.append($link);
if (/PhantomJS/.test(window.navigator.userAgent)) {
return;
}
$branding.css({
opacity: '1.0'
});
}
};
// Export module
return api;
});
`