2015-02-03 20:40: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
|
|
|
|
|
// --------------------
|
|
|
|
|
|
2015-02-04 06:58:23 +00:00
|
|
|
|
// 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.
|
2015-02-03 20:40:29 +00:00
|
|
|
|
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.
|
2015-02-04 06:58:23 +00:00
|
|
|
|
// Delegates to **ECMAScript 5**'s native "map" if available.
|
2015-02-03 20:40:29 +00:00
|
|
|
|
_.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;
|
|
|
|
|
};
|
|
|
|
|
|
2015-02-04 06:58:23 +00:00
|
|
|
|
// Return the first value which passes a truth test. Aliased as "detect".
|
2015-02-03 20:40:29 +00:00
|
|
|
|
_.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.
|
2015-02-04 06:58:23 +00:00
|
|
|
|
// Delegates to **ECMAScript 5**'s native "filter" if available.
|
|
|
|
|
// Aliased as "select".
|
2015-02-03 20:40:29 +00:00
|
|
|
|
_.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.
|
2015-02-04 06:58:23 +00:00
|
|
|
|
// Delegates to **ECMAScript 5**'s native "some" if available.
|
|
|
|
|
// Aliased as "any".
|
2015-02-03 20:40:29 +00:00
|
|
|
|
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;
|
|
|
|
|
};
|
|
|
|
|
|
2015-02-04 06:58:23 +00:00
|
|
|
|
// Determine if the array or object contains a given value (using "===").
|
|
|
|
|
// Aliased as "include".
|
2015-02-03 20:40:29 +00:00
|
|
|
|
_.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
|
2015-02-04 06:58:23 +00:00
|
|
|
|
// N milliseconds. If "immediate" is passed, trigger the function on the
|
2015-02-03 20:40:29 +00:00
|
|
|
|
// 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.
|
2015-02-04 06:58:23 +00:00
|
|
|
|
// Delegates to **ECMAScript 5**'s native "Object.keys"
|
2015-02-03 20:40:29 +00:00
|
|
|
|
_.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 don’t handle multi-touch events yet.
|
|
|
|
|
var touches = evt.touches;
|
|
|
|
|
if (touches && touches.length > 1) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
active = true;
|
|
|
|
|
dirty = false;
|
|
|
|
|
|
|
|
|
|
if (touches) {
|
|
|
|
|
useTouch = true;
|
|
|
|
|
startX = touches[0].clientX;
|
|
|
|
|
startY = touches[0].clientY;
|
|
|
|
|
} else {
|
|
|
|
|
startX = evt.clientX;
|
|
|
|
|
startY = evt.clientY;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lastX = startX;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function move(evt) {
|
|
|
|
|
if (!active) return;
|
|
|
|
|
|
|
|
|
|
if (useTouch && evt.type === 'mousemove') {
|
|
|
|
|
evt.preventDefault();
|
|
|
|
|
evt.stopPropagation();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var touches = evt.touches;
|
|
|
|
|
var x = touches ? touches[0].clientX : evt.clientX;
|
|
|
|
|
var y = touches ? touches[0].clientY : evt.clientY;
|
|
|
|
|
|
|
|
|
|
var velocityX = x - lastX;
|
|
|
|
|
lastX = x;
|
|
|
|
|
|
|
|
|
|
// Allow swipes while pointer is down, but prevent them during text selection
|
|
|
|
|
if (Math.abs(velocityX) > thresholdX && getSelection && getSelection() + '' === '') {
|
|
|
|
|
triggerEvent('swipe', evt, { direction: velocityX > 0 ? 'right' : 'left' });
|
|
|
|
|
cancel();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If pointer moves more than 10px flag to cancel tap
|
|
|
|
|
if (Math.abs(x - startX) > 10 || Math.abs(y - startY) > 10) {
|
|
|
|
|
dirty = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function end(evt) {
|
|
|
|
|
if (!active) return;
|
|
|
|
|
active = false;
|
|
|
|
|
|
|
|
|
|
if (useTouch && evt.type === 'mouseup') {
|
|
|
|
|
evt.preventDefault();
|
|
|
|
|
evt.stopPropagation();
|
|
|
|
|
useTouch = false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!dirty) triggerEvent('tap', evt);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function cancel(evt) {
|
|
|
|
|
active = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function destroy() {
|
|
|
|
|
el.removeEventListener('touchstart', start, false);
|
|
|
|
|
el.removeEventListener('touchmove', move, false);
|
|
|
|
|
el.removeEventListener('touchend', end, false);
|
|
|
|
|
el.removeEventListener('touchcancel', cancel, false);
|
|
|
|
|
el.removeEventListener('mousedown', start, false);
|
|
|
|
|
el.removeEventListener('mousemove', move, false);
|
|
|
|
|
el.removeEventListener('mouseup', end, false);
|
|
|
|
|
el.removeEventListener('mouseout', cancel, false);
|
|
|
|
|
el = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Public instance methods
|
|
|
|
|
this.destroy = destroy;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Wrap native event to supoprt preventdefault + stopPropagation
|
|
|
|
|
function triggerEvent(type, evt, data) {
|
|
|
|
|
var newEvent = $.Event(type, { originalEvent: evt });
|
|
|
|
|
$(evt.target).trigger(newEvent, data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Listen for touch events on all nodes by default.
|
|
|
|
|
api.instance = api.init(document);
|
|
|
|
|
|
|
|
|
|
// Export module
|
|
|
|
|
return api;
|
|
|
|
|
});
|
|
|
|
|
/**
|
|
|
|
|
* ----------------------------------------------------------------------
|
|
|
|
|
* Webflow: Forms
|
|
|
|
|
*/
|
|
|
|
|
Webflow.define('forms', function($, _) {
|
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
|
|
var api = {};
|
|
|
|
|
|
|
|
|
|
var 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 () {
|
2015-02-04 06:58:23 +00:00
|
|
|
|
// In case "build" is called more than once.
|
2015-02-03 20:40:29 +00:00
|
|
|
|
lightbox.destroy();
|
|
|
|
|
|
|
|
|
|
$refs = {
|
|
|
|
|
html: $(document.documentElement),
|
2015-02-04 06:58:23 +00:00
|
|
|
|
// Empty jQuery object can be used to build new ones using ".add".
|
2015-02-03 20:40:29 +00:00
|
|
|
|
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);
|
|
|
|
|
|
2015-02-04 06:58:23 +00:00
|
|
|
|
// The "tabindex" attribute is needed to enable non-input elements
|
2015-02-03 20:40:29 +00:00
|
|
|
|
// to receive keyboard events.
|
|
|
|
|
$('body').append($refs.lightbox.prop('tabIndex', 0));
|
|
|
|
|
|
|
|
|
|
return lightbox;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Dispose of DOM nodes created by the lightbox.
|
|
|
|
|
*/
|
|
|
|
|
lightbox.destroy = function () {
|
|
|
|
|
if (!$refs) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Event handlers are also removed.
|
|
|
|
|
$refs.lightbox.remove();
|
|
|
|
|
$refs = undefined;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Show a specific item.
|
|
|
|
|
*/
|
|
|
|
|
lightbox.show = function (index) {
|
|
|
|
|
// Bail if we are already showing this item.
|
|
|
|
|
if (index === currentIndex) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var item = items[index];
|
|
|
|
|
var previousIndex = currentIndex;
|
|
|
|
|
currentIndex = index;
|
|
|
|
|
spinner.show();
|
|
|
|
|
|
|
|
|
|
// For videos, load an empty SVG with the video dimensions to preserve
|
|
|
|
|
// the video’s aspect ratio while being responsive.
|
|
|
|
|
var url = item.html && svgDataUri(item.width, item.height) || item.url;
|
|
|
|
|
loadImage(url, function ($image) {
|
|
|
|
|
// Make sure this is the last item requested to be shown since
|
|
|
|
|
// images can finish loading in a different order than they were
|
|
|
|
|
// requested in.
|
|
|
|
|
if (index != currentIndex) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var $figure = dom('figure', 'figure').append(addClass($image, 'image'));
|
|
|
|
|
var $frame = dom('frame').append($figure);
|
|
|
|
|
var $newView = dom('view').append($frame);
|
|
|
|
|
var $html, isIframe;
|
|
|
|
|
|
|
|
|
|
if (item.html) {
|
|
|
|
|
$html = $(item.html);
|
|
|
|
|
isIframe = $html.is('iframe');
|
|
|
|
|
|
|
|
|
|
if (isIframe) {
|
|
|
|
|
$html.on('load', transitionToNewView);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$figure.append(addClass($html, 'embed'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (item.caption) {
|
|
|
|
|
$figure.append(dom('caption', 'figcaption').text(item.caption));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$refs.spinner.before($newView);
|
|
|
|
|
|
|
|
|
|
if (!isIframe) {
|
|
|
|
|
transitionToNewView();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function transitionToNewView() {
|
|
|
|
|
spinner.hide();
|
|
|
|
|
|
|
|
|
|
if (index != currentIndex) {
|
|
|
|
|
$newView.remove();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
toggleClass($refs.arrowLeft, 'inactive', index <= 0);
|
|
|
|
|
toggleClass($refs.arrowRight, 'inactive', index >= items.length - 1);
|
|
|
|
|
|
|
|
|
|
if ($refs.view) {
|
|
|
|
|
tram($refs.view)
|
|
|
|
|
.add('opacity .3s')
|
|
|
|
|
.start({opacity: 0})
|
|
|
|
|
.then(remover($refs.view));
|
|
|
|
|
|
|
|
|
|
tram($newView)
|
|
|
|
|
.add('opacity .3s')
|
|
|
|
|
.add('transform .3s')
|
|
|
|
|
.set({x: index > previousIndex ? '80px' : '-80px'})
|
|
|
|
|
.start({opacity: 1, x: 0});
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
$newView.css('opacity', 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$refs.view = $newView;
|
|
|
|
|
|
|
|
|
|
if ($refs.items) {
|
|
|
|
|
// Mark proper thumbnail as active
|
|
|
|
|
addClass(removeClass($refs.items, 'active').eq(index), 'active');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return lightbox;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Hides the lightbox.
|
|
|
|
|
*/
|
|
|
|
|
lightbox.hide = function () {
|
|
|
|
|
tram($refs.lightbox)
|
|
|
|
|
.add('opacity .3s')
|
|
|
|
|
.start({opacity: 0})
|
|
|
|
|
.then(hideLightbox);
|
|
|
|
|
|
|
|
|
|
return lightbox;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
lightbox.prev = function () {
|
|
|
|
|
if (currentIndex > 0) {
|
|
|
|
|
lightbox.show(currentIndex - 1);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
lightbox.next = function () {
|
|
|
|
|
if (currentIndex < items.length - 1) {
|
|
|
|
|
lightbox.show(currentIndex + 1);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function createHandler(action) {
|
|
|
|
|
return function (event) {
|
|
|
|
|
// We only care about events triggered directly on the bound selectors.
|
|
|
|
|
if (this != event.target) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
event.stopPropagation();
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
|
|
|
|
action();
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var handlerPrev = createHandler(lightbox.prev);
|
|
|
|
|
var handlerNext = createHandler(lightbox.next);
|
|
|
|
|
var handlerHide = createHandler(lightbox.hide);
|
|
|
|
|
|
|
|
|
|
var itemTapHandler = function(event) {
|
|
|
|
|
var index = $(this).index();
|
|
|
|
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
lightbox.show(index);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var swipeHandler = function (event, data) {
|
|
|
|
|
// Prevent scrolling.
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
|
|
|
|
if (data.direction == 'left') {
|
|
|
|
|
lightbox.next();
|
|
|
|
|
}
|
|
|
|
|
else if (data.direction == 'right') {
|
|
|
|
|
lightbox.prev();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var focusThis = function () {
|
|
|
|
|
this.focus();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function preventDefault(event) {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function keyHandler(event) {
|
|
|
|
|
var keyCode = event.keyCode;
|
|
|
|
|
|
|
|
|
|
// [esc]
|
|
|
|
|
if (keyCode == 27) {
|
|
|
|
|
lightbox.hide();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// [◀]
|
|
|
|
|
else if (keyCode == 37) {
|
|
|
|
|
lightbox.prev();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// [▶]
|
|
|
|
|
else if (keyCode == 39) {
|
|
|
|
|
lightbox.next();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function hideLightbox() {
|
|
|
|
|
removeClass($refs.html, 'noscroll');
|
|
|
|
|
addClass($refs.lightbox, 'hide');
|
|
|
|
|
$refs.strip.empty();
|
|
|
|
|
$refs.view && $refs.view.remove();
|
|
|
|
|
|
|
|
|
|
// Reset some stuff
|
|
|
|
|
removeClass($refs.content, 'group');
|
|
|
|
|
addClass($refs.arrowLeft, 'inactive');
|
|
|
|
|
addClass($refs.arrowRight, 'inactive');
|
|
|
|
|
|
|
|
|
|
currentIndex = $refs.view = undefined;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function loadImage(url, callback) {
|
|
|
|
|
var $image = dom('img', 'img');
|
|
|
|
|
|
|
|
|
|
$image.one('load', function () {
|
|
|
|
|
callback($image);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Start loading image.
|
|
|
|
|
$image.attr('src', url);
|
|
|
|
|
|
|
|
|
|
return $image;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function remover($element) {
|
|
|
|
|
return function () {
|
|
|
|
|
$element.remove();
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Spinner
|
|
|
|
|
*/
|
|
|
|
|
function Spinner($spinner, className, delay) {
|
|
|
|
|
this.$element = $spinner;
|
|
|
|
|
this.className = className;
|
|
|
|
|
this.delay = delay || 200;
|
|
|
|
|
this.hide();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Spinner.prototype.show = function () {
|
|
|
|
|
var spinner = this;
|
|
|
|
|
|
|
|
|
|
// Bail if we are already showing the spinner.
|
|
|
|
|
if (spinner.timeoutId) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
spinner.timeoutId = setTimeout(function () {
|
|
|
|
|
spinner.$element.removeClass(spinner.className);
|
|
|
|
|
delete spinner.timeoutId;
|
|
|
|
|
}, spinner.delay);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Spinner.prototype.hide = function () {
|
|
|
|
|
var spinner = this;
|
|
|
|
|
if (spinner.timeoutId) {
|
|
|
|
|
clearTimeout(spinner.timeoutId);
|
|
|
|
|
delete spinner.timeoutId;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
spinner.$element.addClass(spinner.className);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function prefixed(string, isSelector) {
|
|
|
|
|
return string.replace(prefixRegex, (isSelector ? ' .' : ' ') + prefix);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function selector(string) {
|
|
|
|
|
return prefixed(string, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* jQuery.addClass with auto-prefixing
|
|
|
|
|
* @param {jQuery} Element to add class to
|
|
|
|
|
* @param {string} Class name that will be prefixed and added to element
|
|
|
|
|
* @return {jQuery}
|
|
|
|
|
*/
|
|
|
|
|
function addClass($element, className) {
|
|
|
|
|
return $element.addClass(prefixed(className));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* jQuery.removeClass with auto-prefixing
|
|
|
|
|
* @param {jQuery} Element to remove class from
|
|
|
|
|
* @param {string} Class name that will be prefixed and removed from element
|
|
|
|
|
* @return {jQuery}
|
|
|
|
|
*/
|
|
|
|
|
function removeClass($element, className) {
|
|
|
|
|
return $element.removeClass(prefixed(className));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* jQuery.toggleClass with auto-prefixing
|
|
|
|
|
* @param {jQuery} Element where class will be toggled
|
|
|
|
|
* @param {string} Class name that will be prefixed and toggled
|
|
|
|
|
* @param {boolean} Optional boolean that determines if class will be added or removed
|
|
|
|
|
* @return {jQuery}
|
|
|
|
|
*/
|
|
|
|
|
function toggleClass($element, className, shouldAdd) {
|
|
|
|
|
return $element.toggleClass(prefixed(className), shouldAdd);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Create a new DOM element wrapped in a jQuery object,
|
|
|
|
|
* decorated with our custom methods.
|
|
|
|
|
* @param {string} className
|
|
|
|
|
* @param {string} [tag]
|
|
|
|
|
* @return {jQuery}
|
|
|
|
|
*/
|
|
|
|
|
function dom(className, tag) {
|
|
|
|
|
return addClass($(document.createElement(tag || 'div')), className);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isObject(value) {
|
|
|
|
|
return typeof value == 'object' && null != value && !isArray(value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function svgDataUri(width, height) {
|
|
|
|
|
var svg = '<svg xmlns="http://www.w3.org/2000/svg" width="' + width + '" height="' + height + '"/>';
|
|
|
|
|
return 'data:image/svg+xml;charset=utf-8,' + encodeURI(svg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Compute some dimensions manually for iOS, because of buggy support for VH.
|
|
|
|
|
// Also, Android built-in browser does not support viewport units.
|
|
|
|
|
(function () {
|
|
|
|
|
var ua = window.navigator.userAgent;
|
|
|
|
|
var iOS = /(iPhone|iPod|iPad).+AppleWebKit/i.test(ua);
|
|
|
|
|
var android = ua.indexOf('Android ') > -1 && ua.indexOf('Chrome') == -1;
|
|
|
|
|
|
|
|
|
|
if (!iOS && !android) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var styleNode = document.createElement('style');
|
|
|
|
|
document.head.appendChild(styleNode);
|
|
|
|
|
window.addEventListener('orientationchange', refresh, true);
|
|
|
|
|
|
|
|
|
|
function refresh() {
|
|
|
|
|
var vh = window.innerHeight;
|
|
|
|
|
var vw = window.innerWidth;
|
|
|
|
|
var content =
|
|
|
|
|
'.w-lightbox-content, .w-lightbox-view, .w-lightbox-view:before {' +
|
|
|
|
|
'height:' + vh + 'px' +
|
|
|
|
|
'}' +
|
|
|
|
|
'.w-lightbox-view {' +
|
|
|
|
|
'width:' + vw + 'px' +
|
|
|
|
|
'}' +
|
|
|
|
|
'.w-lightbox-group, .w-lightbox-group .w-lightbox-view, .w-lightbox-group .w-lightbox-view:before {' +
|
|
|
|
|
'height:' + (0.86 * vh) + 'px' +
|
|
|
|
|
'}' +
|
|
|
|
|
'.w-lightbox-image {' +
|
|
|
|
|
'max-width:' + vw + 'px;' +
|
|
|
|
|
'max-height:' + vh + 'px' +
|
|
|
|
|
'}' +
|
|
|
|
|
'.w-lightbox-group .w-lightbox-image {' +
|
|
|
|
|
'max-height:' + (0.86 * vh) + 'px' +
|
|
|
|
|
'}' +
|
|
|
|
|
'.w-lightbox-strip {' +
|
|
|
|
|
'padding: 0 ' + (0.01 * vh) + 'px' +
|
|
|
|
|
'}' +
|
|
|
|
|
'.w-lightbox-item {' +
|
|
|
|
|
'width:' + (0.1 * vh) + 'px;' +
|
|
|
|
|
'padding:' + (0.02 * vh) + 'px ' + (0.01 * vh) + 'px' +
|
|
|
|
|
'}' +
|
|
|
|
|
'.w-lightbox-thumbnail {' +
|
|
|
|
|
'height:' + (0.1 * vh) + 'px' +
|
|
|
|
|
'}' +
|
|
|
|
|
'@media (min-width: 768px) {' +
|
|
|
|
|
'.w-lightbox-content, .w-lightbox-view, .w-lightbox-view:before {' +
|
|
|
|
|
'height:' + (0.96 * vh) + 'px' +
|
|
|
|
|
'}' +
|
|
|
|
|
'.w-lightbox-content {' +
|
|
|
|
|
'margin-top:' + (0.02 * vh) + 'px' +
|
|
|
|
|
'}' +
|
|
|
|
|
'.w-lightbox-group, .w-lightbox-group .w-lightbox-view, .w-lightbox-group .w-lightbox-view:before {' +
|
|
|
|
|
'height:' + (0.84 * vh) + 'px' +
|
|
|
|
|
'}' +
|
|
|
|
|
'.w-lightbox-image {' +
|
|
|
|
|
'max-width:' + (0.96 * vw) + 'px;' +
|
|
|
|
|
'max-height:' + (0.96 * vh) + 'px' +
|
|
|
|
|
'}' +
|
|
|
|
|
'.w-lightbox-group .w-lightbox-image {' +
|
|
|
|
|
'max-width:' + (0.823 * vw) + 'px;' +
|
|
|
|
|
'max-height:' + (0.84 * vh) + 'px' +
|
|
|
|
|
'}' +
|
|
|
|
|
'}';
|
|
|
|
|
|
|
|
|
|
styleNode.textContent = content;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
refresh();
|
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
return lightbox;
|
|
|
|
|
})(window, document, jQuery, window.tram);
|
|
|
|
|
|
|
|
|
|
Webflow.define('lightbox', function ($, _) {
|
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
|
|
var api = {};
|
|
|
|
|
var $doc = $(document);
|
|
|
|
|
var $body;
|
|
|
|
|
var $lightboxes;
|
|
|
|
|
var designer;
|
|
|
|
|
var inApp = Webflow.env();
|
|
|
|
|
var namespace = '.w-lightbox';
|
|
|
|
|
var groups;
|
|
|
|
|
|
|
|
|
|
// -----------------------------------
|
|
|
|
|
// Module methods
|
|
|
|
|
|
|
|
|
|
api.ready = api.design = api.preview = init;
|
|
|
|
|
|
|
|
|
|
// -----------------------------------
|
|
|
|
|
// Private methods
|
|
|
|
|
|
|
|
|
|
function init() {
|
|
|
|
|
designer = inApp && Webflow.env('design');
|
|
|
|
|
$body = $(document.body);
|
|
|
|
|
|
|
|
|
|
// Reset Lightbox
|
|
|
|
|
lightbox.destroy();
|
|
|
|
|
|
|
|
|
|
// Reset groups
|
|
|
|
|
groups = {};
|
|
|
|
|
|
|
|
|
|
// Find all instances on the page
|
|
|
|
|
$lightboxes = $doc.find(namespace);
|
|
|
|
|
$lightboxes.each(build);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function build(i, el) {
|
|
|
|
|
var $el = $(el);
|
|
|
|
|
|
|
|
|
|
// Store state in data
|
|
|
|
|
var data = $.data(el, namespace);
|
|
|
|
|
if (!data) data = $.data(el, namespace, {
|
|
|
|
|
el: $el,
|
|
|
|
|
mode: 'images',
|
|
|
|
|
images: [],
|
|
|
|
|
embed: ''
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Remove old events
|
|
|
|
|
data.el.off(namespace);
|
|
|
|
|
|
|
|
|
|
// Set config from json script tag
|
|
|
|
|
configure(data);
|
|
|
|
|
|
|
|
|
|
// Add events based on mode
|
|
|
|
|
if (designer) {
|
|
|
|
|
data.el.on('setting' + namespace, configure.bind(null, data));
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
data.el
|
|
|
|
|
.on('tap' + namespace, tapHandler(data))
|
|
|
|
|
// Prevent page scrolling to top when clicking on lightbox triggers.
|
|
|
|
|
.on('click' + namespace, function (e) { e.preventDefault(); });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function configure(data) {
|
|
|
|
|
var json = data.el.children('.w-json').html();
|
|
|
|
|
var groupName, groupItems;
|
|
|
|
|
|
|
|
|
|
if (!json) {
|
|
|
|
|
data.items = [];
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
json = JSON.parse(json);
|
|
|
|
|
|
|
|
|
|
supportOldLightboxJson(json);
|
|
|
|
|
|
|
|
|
|
groupName = json.group;
|
|
|
|
|
|
|
|
|
|
if (groupName) {
|
|
|
|
|
groupItems = groups[groupName];
|
|
|
|
|
if (!groupItems) {
|
|
|
|
|
groupItems = groups[groupName] = [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
data.items = groupItems;
|
|
|
|
|
|
|
|
|
|
if (json.items.length) {
|
|
|
|
|
data.index = groupItems.length;
|
|
|
|
|
groupItems.push.apply(groupItems, json.items);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
data.items = json.items;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (e) {
|
|
|
|
|
console.error('Malformed lightbox JSON configuration.', e.message);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function tapHandler(data) {
|
|
|
|
|
return function () {
|
|
|
|
|
data.items.length && lightbox(data.items, data.index || 0);
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function supportOldLightboxJson(data) {
|
|
|
|
|
if (data.images) {
|
|
|
|
|
data.images.forEach(function (item) {
|
|
|
|
|
item.type = 'image';
|
|
|
|
|
});
|
|
|
|
|
data.items = data.images;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (data.embed) {
|
|
|
|
|
data.embed.type = 'video';
|
|
|
|
|
data.items = [data.embed];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (data.groupId) {
|
|
|
|
|
data.group = data.groupId;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Export module
|
|
|
|
|
return api;
|
|
|
|
|
});
|
|
|
|
|
/**
|
|
|
|
|
* ----------------------------------------------------------------------
|
|
|
|
|
* Webflow: Navbar component
|
|
|
|
|
*/
|
|
|
|
|
Webflow.define('navbar', function($, _) {
|
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
|
|
var api = {};
|
|
|
|
|
var tram = window.tram;
|
|
|
|
|
var $win = $(window);
|
|
|
|
|
var $doc = $(document);
|
|
|
|
|
var $body;
|
|
|
|
|
var $navbars;
|
|
|
|
|
var designer;
|
|
|
|
|
var inApp = Webflow.env();
|
|
|
|
|
var overlay = '<div class="w-nav-overlay" data-wf-ignore />';
|
|
|
|
|
var namespace = '.w-nav';
|
|
|
|
|
var buttonOpen = 'w--open';
|
|
|
|
|
var menuOpen = 'w--nav-menu-open';
|
|
|
|
|
var linkOpen = 'w--nav-link-open';
|
|
|
|
|
var ix = Webflow.ixEvents();
|
|
|
|
|
|
|
|
|
|
// -----------------------------------
|
|
|
|
|
// Module methods
|
|
|
|
|
|
|
|
|
|
api.ready = api.design = api.preview = init;
|
|
|
|
|
api.destroy = removeListeners;
|
|
|
|
|
|
|
|
|
|
// -----------------------------------
|
|
|
|
|
// Private methods
|
|
|
|
|
|
|
|
|
|
function init() {
|
|
|
|
|
designer = inApp && Webflow.env('design');
|
|
|
|
|
$body = $(document.body);
|
|
|
|
|
|
|
|
|
|
// Find all instances on the page
|
|
|
|
|
$navbars = $doc.find(namespace);
|
|
|
|
|
if (!$navbars.length) return;
|
|
|
|
|
$navbars.each(build);
|
|
|
|
|
|
|
|
|
|
// Wire events
|
|
|
|
|
removeListeners();
|
|
|
|
|
addListeners();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function removeListeners() {
|
|
|
|
|
Webflow.resize.off(resizeAll);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function addListeners() {
|
|
|
|
|
Webflow.resize.on(resizeAll);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function resizeAll() {
|
|
|
|
|
$navbars.each(resize);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function build(i, el) {
|
|
|
|
|
var $el = $(el);
|
|
|
|
|
|
|
|
|
|
// Store state in data
|
|
|
|
|
var data = $.data(el, namespace);
|
|
|
|
|
if (!data) data = $.data(el, namespace, { open: false, el: $el, config: {} });
|
|
|
|
|
data.menu = $el.find('.w-nav-menu');
|
|
|
|
|
data.links = data.menu.find('.w-nav-link');
|
|
|
|
|
data.dropdowns = data.menu.find('.w-dropdown');
|
|
|
|
|
data.button = $el.find('.w-nav-button');
|
|
|
|
|
data.container = $el.find('.w-container');
|
|
|
|
|
data.outside = outside(data);
|
|
|
|
|
|
|
|
|
|
// Remove old events
|
|
|
|
|
data.el.off(namespace);
|
|
|
|
|
data.button.off(namespace);
|
|
|
|
|
data.menu.off(namespace);
|
|
|
|
|
|
|
|
|
|
// Set config from data attributes
|
|
|
|
|
configure(data);
|
|
|
|
|
|
|
|
|
|
// Add events based on mode
|
|
|
|
|
if (designer) {
|
|
|
|
|
removeOverlay(data);
|
|
|
|
|
data.el.on('setting' + namespace, handler(data));
|
|
|
|
|
} else {
|
|
|
|
|
addOverlay(data);
|
|
|
|
|
data.button.on('tap' + namespace, toggle(data));
|
|
|
|
|
data.menu.on('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;
|
|
|
|
|
});
|
2015-02-04 06:58:23 +00:00
|
|
|
|
`
|